##// END OF EJS Templates
localrepo: refer to dirstateguard by its new name
Augie Fackler -
r30492:77cd647b default
parent child Browse files
Show More
@@ -1,2000 +1,2001 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import inspect
13 13 import os
14 14 import random
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 hex,
21 21 nullid,
22 22 short,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 bookmarks,
27 27 branchmap,
28 28 bundle2,
29 29 changegroup,
30 30 changelog,
31 31 cmdutil,
32 32 context,
33 33 dirstate,
34 dirstateguard,
34 35 encoding,
35 36 error,
36 37 exchange,
37 38 extensions,
38 39 filelog,
39 40 hook,
40 41 lock as lockmod,
41 42 manifest,
42 43 match as matchmod,
43 44 merge as mergemod,
44 45 namespaces,
45 46 obsolete,
46 47 pathutil,
47 48 peer,
48 49 phases,
49 50 pushkey,
50 51 repoview,
51 52 revset,
52 53 scmutil,
53 54 store,
54 55 subrepo,
55 56 tags as tagsmod,
56 57 transaction,
57 58 util,
58 59 )
59 60
60 61 release = lockmod.release
61 62 urlerr = util.urlerr
62 63 urlreq = util.urlreq
63 64
64 65 class repofilecache(scmutil.filecache):
65 66 """All filecache usage on repo are done for logic that should be unfiltered
66 67 """
67 68
68 69 def __get__(self, repo, type=None):
69 70 if repo is None:
70 71 return self
71 72 return super(repofilecache, self).__get__(repo.unfiltered(), type)
72 73 def __set__(self, repo, value):
73 74 return super(repofilecache, self).__set__(repo.unfiltered(), value)
74 75 def __delete__(self, repo):
75 76 return super(repofilecache, self).__delete__(repo.unfiltered())
76 77
77 78 class storecache(repofilecache):
78 79 """filecache for files in the store"""
79 80 def join(self, obj, fname):
80 81 return obj.sjoin(fname)
81 82
82 83 class unfilteredpropertycache(util.propertycache):
83 84 """propertycache that apply to unfiltered repo only"""
84 85
85 86 def __get__(self, repo, type=None):
86 87 unfi = repo.unfiltered()
87 88 if unfi is repo:
88 89 return super(unfilteredpropertycache, self).__get__(unfi)
89 90 return getattr(unfi, self.name)
90 91
91 92 class filteredpropertycache(util.propertycache):
92 93 """propertycache that must take filtering in account"""
93 94
94 95 def cachevalue(self, obj, value):
95 96 object.__setattr__(obj, self.name, value)
96 97
97 98
98 99 def hasunfilteredcache(repo, name):
99 100 """check if a repo has an unfilteredpropertycache value for <name>"""
100 101 return name in vars(repo.unfiltered())
101 102
102 103 def unfilteredmethod(orig):
103 104 """decorate method that always need to be run on unfiltered version"""
104 105 def wrapper(repo, *args, **kwargs):
105 106 return orig(repo.unfiltered(), *args, **kwargs)
106 107 return wrapper
107 108
108 109 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
109 110 'unbundle'))
110 111 legacycaps = moderncaps.union(set(['changegroupsubset']))
111 112
112 113 class localpeer(peer.peerrepository):
113 114 '''peer for a local repo; reflects only the most recent API'''
114 115
115 116 def __init__(self, repo, caps=moderncaps):
116 117 peer.peerrepository.__init__(self)
117 118 self._repo = repo.filtered('served')
118 119 self.ui = repo.ui
119 120 self._caps = repo._restrictcapabilities(caps)
120 121 self.requirements = repo.requirements
121 122 self.supportedformats = repo.supportedformats
122 123
123 124 def close(self):
124 125 self._repo.close()
125 126
126 127 def _capabilities(self):
127 128 return self._caps
128 129
129 130 def local(self):
130 131 return self._repo
131 132
132 133 def canpush(self):
133 134 return True
134 135
135 136 def url(self):
136 137 return self._repo.url()
137 138
138 139 def lookup(self, key):
139 140 return self._repo.lookup(key)
140 141
141 142 def branchmap(self):
142 143 return self._repo.branchmap()
143 144
144 145 def heads(self):
145 146 return self._repo.heads()
146 147
147 148 def known(self, nodes):
148 149 return self._repo.known(nodes)
149 150
150 151 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
151 152 **kwargs):
152 153 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
153 154 common=common, bundlecaps=bundlecaps,
154 155 **kwargs)
155 156 cb = util.chunkbuffer(chunks)
156 157
157 158 if bundlecaps is not None and 'HG20' in bundlecaps:
158 159 # When requesting a bundle2, getbundle returns a stream to make the
159 160 # wire level function happier. We need to build a proper object
160 161 # from it in local peer.
161 162 return bundle2.getunbundler(self.ui, cb)
162 163 else:
163 164 return changegroup.getunbundler('01', cb, None)
164 165
165 166 # TODO We might want to move the next two calls into legacypeer and add
166 167 # unbundle instead.
167 168
168 169 def unbundle(self, cg, heads, url):
169 170 """apply a bundle on a repo
170 171
171 172 This function handles the repo locking itself."""
172 173 try:
173 174 try:
174 175 cg = exchange.readbundle(self.ui, cg, None)
175 176 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
176 177 if util.safehasattr(ret, 'getchunks'):
177 178 # This is a bundle20 object, turn it into an unbundler.
178 179 # This little dance should be dropped eventually when the
179 180 # API is finally improved.
180 181 stream = util.chunkbuffer(ret.getchunks())
181 182 ret = bundle2.getunbundler(self.ui, stream)
182 183 return ret
183 184 except Exception as exc:
184 185 # If the exception contains output salvaged from a bundle2
185 186 # reply, we need to make sure it is printed before continuing
186 187 # to fail. So we build a bundle2 with such output and consume
187 188 # it directly.
188 189 #
189 190 # This is not very elegant but allows a "simple" solution for
190 191 # issue4594
191 192 output = getattr(exc, '_bundle2salvagedoutput', ())
192 193 if output:
193 194 bundler = bundle2.bundle20(self._repo.ui)
194 195 for out in output:
195 196 bundler.addpart(out)
196 197 stream = util.chunkbuffer(bundler.getchunks())
197 198 b = bundle2.getunbundler(self.ui, stream)
198 199 bundle2.processbundle(self._repo, b)
199 200 raise
200 201 except error.PushRaced as exc:
201 202 raise error.ResponseError(_('push failed:'), str(exc))
202 203
203 204 def lock(self):
204 205 return self._repo.lock()
205 206
206 207 def addchangegroup(self, cg, source, url):
207 208 return cg.apply(self._repo, source, url)
208 209
209 210 def pushkey(self, namespace, key, old, new):
210 211 return self._repo.pushkey(namespace, key, old, new)
211 212
212 213 def listkeys(self, namespace):
213 214 return self._repo.listkeys(namespace)
214 215
215 216 def debugwireargs(self, one, two, three=None, four=None, five=None):
216 217 '''used to test argument passing over the wire'''
217 218 return "%s %s %s %s %s" % (one, two, three, four, five)
218 219
219 220 class locallegacypeer(localpeer):
220 221 '''peer extension which implements legacy methods too; used for tests with
221 222 restricted capabilities'''
222 223
223 224 def __init__(self, repo):
224 225 localpeer.__init__(self, repo, caps=legacycaps)
225 226
226 227 def branches(self, nodes):
227 228 return self._repo.branches(nodes)
228 229
229 230 def between(self, pairs):
230 231 return self._repo.between(pairs)
231 232
232 233 def changegroup(self, basenodes, source):
233 234 return changegroup.changegroup(self._repo, basenodes, source)
234 235
235 236 def changegroupsubset(self, bases, heads, source):
236 237 return changegroup.changegroupsubset(self._repo, bases, heads, source)
237 238
238 239 class localrepository(object):
239 240
240 241 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
241 242 'manifestv2'))
242 243 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
243 244 'dotencode'))
244 245 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
245 246 filtername = None
246 247
247 248 # a list of (ui, featureset) functions.
248 249 # only functions defined in module of enabled extensions are invoked
249 250 featuresetupfuncs = set()
250 251
251 252 def __init__(self, baseui, path=None, create=False):
252 253 self.requirements = set()
253 254 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
254 255 self.wopener = self.wvfs
255 256 self.root = self.wvfs.base
256 257 self.path = self.wvfs.join(".hg")
257 258 self.origroot = path
258 259 self.auditor = pathutil.pathauditor(self.root, self._checknested)
259 260 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
260 261 realfs=False)
261 262 self.vfs = scmutil.vfs(self.path)
262 263 self.opener = self.vfs
263 264 self.baseui = baseui
264 265 self.ui = baseui.copy()
265 266 self.ui.copy = baseui.copy # prevent copying repo configuration
266 267 # A list of callback to shape the phase if no data were found.
267 268 # Callback are in the form: func(repo, roots) --> processed root.
268 269 # This list it to be filled by extension during repo setup
269 270 self._phasedefaults = []
270 271 try:
271 272 self.ui.readconfig(self.join("hgrc"), self.root)
272 273 extensions.loadall(self.ui)
273 274 except IOError:
274 275 pass
275 276
276 277 if self.featuresetupfuncs:
277 278 self.supported = set(self._basesupported) # use private copy
278 279 extmods = set(m.__name__ for n, m
279 280 in extensions.extensions(self.ui))
280 281 for setupfunc in self.featuresetupfuncs:
281 282 if setupfunc.__module__ in extmods:
282 283 setupfunc(self.ui, self.supported)
283 284 else:
284 285 self.supported = self._basesupported
285 286
286 287 if not self.vfs.isdir():
287 288 if create:
288 289 self.requirements = newreporequirements(self)
289 290
290 291 if not self.wvfs.exists():
291 292 self.wvfs.makedirs()
292 293 self.vfs.makedir(notindexed=True)
293 294
294 295 if 'store' in self.requirements:
295 296 self.vfs.mkdir("store")
296 297
297 298 # create an invalid changelog
298 299 self.vfs.append(
299 300 "00changelog.i",
300 301 '\0\0\0\2' # represents revlogv2
301 302 ' dummy changelog to prevent using the old repo layout'
302 303 )
303 304 else:
304 305 raise error.RepoError(_("repository %s not found") % path)
305 306 elif create:
306 307 raise error.RepoError(_("repository %s already exists") % path)
307 308 else:
308 309 try:
309 310 self.requirements = scmutil.readrequires(
310 311 self.vfs, self.supported)
311 312 except IOError as inst:
312 313 if inst.errno != errno.ENOENT:
313 314 raise
314 315
315 316 self.sharedpath = self.path
316 317 try:
317 318 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
318 319 realpath=True)
319 320 s = vfs.base
320 321 if not vfs.exists():
321 322 raise error.RepoError(
322 323 _('.hg/sharedpath points to nonexistent directory %s') % s)
323 324 self.sharedpath = s
324 325 except IOError as inst:
325 326 if inst.errno != errno.ENOENT:
326 327 raise
327 328
328 329 self.store = store.store(
329 330 self.requirements, self.sharedpath, scmutil.vfs)
330 331 self.spath = self.store.path
331 332 self.svfs = self.store.vfs
332 333 self.sjoin = self.store.join
333 334 self.vfs.createmode = self.store.createmode
334 335 self._applyopenerreqs()
335 336 if create:
336 337 self._writerequirements()
337 338
338 339 self._dirstatevalidatewarned = False
339 340
340 341 self._branchcaches = {}
341 342 self._revbranchcache = None
342 343 self.filterpats = {}
343 344 self._datafilters = {}
344 345 self._transref = self._lockref = self._wlockref = None
345 346
346 347 # A cache for various files under .hg/ that tracks file changes,
347 348 # (used by the filecache decorator)
348 349 #
349 350 # Maps a property name to its util.filecacheentry
350 351 self._filecache = {}
351 352
352 353 # hold sets of revision to be filtered
353 354 # should be cleared when something might have changed the filter value:
354 355 # - new changesets,
355 356 # - phase change,
356 357 # - new obsolescence marker,
357 358 # - working directory parent change,
358 359 # - bookmark changes
359 360 self.filteredrevcache = {}
360 361
361 362 # generic mapping between names and nodes
362 363 self.names = namespaces.namespaces()
363 364
364 365 def close(self):
365 366 self._writecaches()
366 367
367 368 def _writecaches(self):
368 369 if self._revbranchcache:
369 370 self._revbranchcache.write()
370 371
371 372 def _restrictcapabilities(self, caps):
372 373 if self.ui.configbool('experimental', 'bundle2-advertise', True):
373 374 caps = set(caps)
374 375 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
375 376 caps.add('bundle2=' + urlreq.quote(capsblob))
376 377 return caps
377 378
378 379 def _applyopenerreqs(self):
379 380 self.svfs.options = dict((r, 1) for r in self.requirements
380 381 if r in self.openerreqs)
381 382 # experimental config: format.chunkcachesize
382 383 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
383 384 if chunkcachesize is not None:
384 385 self.svfs.options['chunkcachesize'] = chunkcachesize
385 386 # experimental config: format.maxchainlen
386 387 maxchainlen = self.ui.configint('format', 'maxchainlen')
387 388 if maxchainlen is not None:
388 389 self.svfs.options['maxchainlen'] = maxchainlen
389 390 # experimental config: format.manifestcachesize
390 391 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
391 392 if manifestcachesize is not None:
392 393 self.svfs.options['manifestcachesize'] = manifestcachesize
393 394 # experimental config: format.aggressivemergedeltas
394 395 aggressivemergedeltas = self.ui.configbool('format',
395 396 'aggressivemergedeltas', False)
396 397 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
397 398 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
398 399
399 400 def _writerequirements(self):
400 401 scmutil.writerequires(self.vfs, self.requirements)
401 402
402 403 def _checknested(self, path):
403 404 """Determine if path is a legal nested repository."""
404 405 if not path.startswith(self.root):
405 406 return False
406 407 subpath = path[len(self.root) + 1:]
407 408 normsubpath = util.pconvert(subpath)
408 409
409 410 # XXX: Checking against the current working copy is wrong in
410 411 # the sense that it can reject things like
411 412 #
412 413 # $ hg cat -r 10 sub/x.txt
413 414 #
414 415 # if sub/ is no longer a subrepository in the working copy
415 416 # parent revision.
416 417 #
417 418 # However, it can of course also allow things that would have
418 419 # been rejected before, such as the above cat command if sub/
419 420 # is a subrepository now, but was a normal directory before.
420 421 # The old path auditor would have rejected by mistake since it
421 422 # panics when it sees sub/.hg/.
422 423 #
423 424 # All in all, checking against the working copy seems sensible
424 425 # since we want to prevent access to nested repositories on
425 426 # the filesystem *now*.
426 427 ctx = self[None]
427 428 parts = util.splitpath(subpath)
428 429 while parts:
429 430 prefix = '/'.join(parts)
430 431 if prefix in ctx.substate:
431 432 if prefix == normsubpath:
432 433 return True
433 434 else:
434 435 sub = ctx.sub(prefix)
435 436 return sub.checknested(subpath[len(prefix) + 1:])
436 437 else:
437 438 parts.pop()
438 439 return False
439 440
440 441 def peer(self):
441 442 return localpeer(self) # not cached to avoid reference cycle
442 443
443 444 def unfiltered(self):
444 445 """Return unfiltered version of the repository
445 446
446 447 Intended to be overwritten by filtered repo."""
447 448 return self
448 449
449 450 def filtered(self, name):
450 451 """Return a filtered version of a repository"""
451 452 # build a new class with the mixin and the current class
452 453 # (possibly subclass of the repo)
453 454 class proxycls(repoview.repoview, self.unfiltered().__class__):
454 455 pass
455 456 return proxycls(self, name)
456 457
457 458 @repofilecache('bookmarks', 'bookmarks.current')
458 459 def _bookmarks(self):
459 460 return bookmarks.bmstore(self)
460 461
461 462 @property
462 463 def _activebookmark(self):
463 464 return self._bookmarks.active
464 465
465 466 def bookmarkheads(self, bookmark):
466 467 name = bookmark.split('@', 1)[0]
467 468 heads = []
468 469 for mark, n in self._bookmarks.iteritems():
469 470 if mark.split('@', 1)[0] == name:
470 471 heads.append(n)
471 472 return heads
472 473
473 474 # _phaserevs and _phasesets depend on changelog. what we need is to
474 475 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
475 476 # can't be easily expressed in filecache mechanism.
476 477 @storecache('phaseroots', '00changelog.i')
477 478 def _phasecache(self):
478 479 return phases.phasecache(self, self._phasedefaults)
479 480
480 481 @storecache('obsstore')
481 482 def obsstore(self):
482 483 # read default format for new obsstore.
483 484 # developer config: format.obsstore-version
484 485 defaultformat = self.ui.configint('format', 'obsstore-version', None)
485 486 # rely on obsstore class default when possible.
486 487 kwargs = {}
487 488 if defaultformat is not None:
488 489 kwargs['defaultformat'] = defaultformat
489 490 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
490 491 store = obsolete.obsstore(self.svfs, readonly=readonly,
491 492 **kwargs)
492 493 if store and readonly:
493 494 self.ui.warn(
494 495 _('obsolete feature not enabled but %i markers found!\n')
495 496 % len(list(store)))
496 497 return store
497 498
498 499 @storecache('00changelog.i')
499 500 def changelog(self):
500 501 c = changelog.changelog(self.svfs)
501 502 if 'HG_PENDING' in os.environ:
502 503 p = os.environ['HG_PENDING']
503 504 if p.startswith(self.root):
504 505 c.readpending('00changelog.i.a')
505 506 return c
506 507
507 508 def _constructmanifest(self):
508 509 # This is a temporary function while we migrate from manifest to
509 510 # manifestlog. It allows bundlerepo and unionrepo to intercept the
510 511 # manifest creation.
511 512 return manifest.manifestrevlog(self.svfs)
512 513
513 514 @storecache('00manifest.i')
514 515 def manifestlog(self):
515 516 return manifest.manifestlog(self.svfs, self)
516 517
517 518 @repofilecache('dirstate')
518 519 def dirstate(self):
519 520 return dirstate.dirstate(self.vfs, self.ui, self.root,
520 521 self._dirstatevalidate)
521 522
522 523 def _dirstatevalidate(self, node):
523 524 try:
524 525 self.changelog.rev(node)
525 526 return node
526 527 except error.LookupError:
527 528 if not self._dirstatevalidatewarned:
528 529 self._dirstatevalidatewarned = True
529 530 self.ui.warn(_("warning: ignoring unknown"
530 531 " working parent %s!\n") % short(node))
531 532 return nullid
532 533
533 534 def __getitem__(self, changeid):
534 535 if changeid is None or changeid == wdirrev:
535 536 return context.workingctx(self)
536 537 if isinstance(changeid, slice):
537 538 return [context.changectx(self, i)
538 539 for i in xrange(*changeid.indices(len(self)))
539 540 if i not in self.changelog.filteredrevs]
540 541 return context.changectx(self, changeid)
541 542
542 543 def __contains__(self, changeid):
543 544 try:
544 545 self[changeid]
545 546 return True
546 547 except error.RepoLookupError:
547 548 return False
548 549
549 550 def __nonzero__(self):
550 551 return True
551 552
552 553 def __len__(self):
553 554 return len(self.changelog)
554 555
555 556 def __iter__(self):
556 557 return iter(self.changelog)
557 558
558 559 def revs(self, expr, *args):
559 560 '''Find revisions matching a revset.
560 561
561 562 The revset is specified as a string ``expr`` that may contain
562 563 %-formatting to escape certain types. See ``revset.formatspec``.
563 564
564 565 Revset aliases from the configuration are not expanded. To expand
565 566 user aliases, consider calling ``scmutil.revrange()``.
566 567
567 568 Returns a revset.abstractsmartset, which is a list-like interface
568 569 that contains integer revisions.
569 570 '''
570 571 expr = revset.formatspec(expr, *args)
571 572 m = revset.match(None, expr)
572 573 return m(self)
573 574
574 575 def set(self, expr, *args):
575 576 '''Find revisions matching a revset and emit changectx instances.
576 577
577 578 This is a convenience wrapper around ``revs()`` that iterates the
578 579 result and is a generator of changectx instances.
579 580
580 581 Revset aliases from the configuration are not expanded. To expand
581 582 user aliases, consider calling ``scmutil.revrange()``.
582 583 '''
583 584 for r in self.revs(expr, *args):
584 585 yield self[r]
585 586
586 587 def url(self):
587 588 return 'file:' + self.root
588 589
589 590 def hook(self, name, throw=False, **args):
590 591 """Call a hook, passing this repo instance.
591 592
592 593 This a convenience method to aid invoking hooks. Extensions likely
593 594 won't call this unless they have registered a custom hook or are
594 595 replacing code that is expected to call a hook.
595 596 """
596 597 return hook.hook(self.ui, self, name, throw, **args)
597 598
598 599 @unfilteredmethod
599 600 def _tag(self, names, node, message, local, user, date, extra=None,
600 601 editor=False):
601 602 if isinstance(names, str):
602 603 names = (names,)
603 604
604 605 branches = self.branchmap()
605 606 for name in names:
606 607 self.hook('pretag', throw=True, node=hex(node), tag=name,
607 608 local=local)
608 609 if name in branches:
609 610 self.ui.warn(_("warning: tag %s conflicts with existing"
610 611 " branch name\n") % name)
611 612
612 613 def writetags(fp, names, munge, prevtags):
613 614 fp.seek(0, 2)
614 615 if prevtags and prevtags[-1] != '\n':
615 616 fp.write('\n')
616 617 for name in names:
617 618 if munge:
618 619 m = munge(name)
619 620 else:
620 621 m = name
621 622
622 623 if (self._tagscache.tagtypes and
623 624 name in self._tagscache.tagtypes):
624 625 old = self.tags().get(name, nullid)
625 626 fp.write('%s %s\n' % (hex(old), m))
626 627 fp.write('%s %s\n' % (hex(node), m))
627 628 fp.close()
628 629
629 630 prevtags = ''
630 631 if local:
631 632 try:
632 633 fp = self.vfs('localtags', 'r+')
633 634 except IOError:
634 635 fp = self.vfs('localtags', 'a')
635 636 else:
636 637 prevtags = fp.read()
637 638
638 639 # local tags are stored in the current charset
639 640 writetags(fp, names, None, prevtags)
640 641 for name in names:
641 642 self.hook('tag', node=hex(node), tag=name, local=local)
642 643 return
643 644
644 645 try:
645 646 fp = self.wfile('.hgtags', 'rb+')
646 647 except IOError as e:
647 648 if e.errno != errno.ENOENT:
648 649 raise
649 650 fp = self.wfile('.hgtags', 'ab')
650 651 else:
651 652 prevtags = fp.read()
652 653
653 654 # committed tags are stored in UTF-8
654 655 writetags(fp, names, encoding.fromlocal, prevtags)
655 656
656 657 fp.close()
657 658
658 659 self.invalidatecaches()
659 660
660 661 if '.hgtags' not in self.dirstate:
661 662 self[None].add(['.hgtags'])
662 663
663 664 m = matchmod.exact(self.root, '', ['.hgtags'])
664 665 tagnode = self.commit(message, user, date, extra=extra, match=m,
665 666 editor=editor)
666 667
667 668 for name in names:
668 669 self.hook('tag', node=hex(node), tag=name, local=local)
669 670
670 671 return tagnode
671 672
672 673 def tag(self, names, node, message, local, user, date, editor=False):
673 674 '''tag a revision with one or more symbolic names.
674 675
675 676 names is a list of strings or, when adding a single tag, names may be a
676 677 string.
677 678
678 679 if local is True, the tags are stored in a per-repository file.
679 680 otherwise, they are stored in the .hgtags file, and a new
680 681 changeset is committed with the change.
681 682
682 683 keyword arguments:
683 684
684 685 local: whether to store tags in non-version-controlled file
685 686 (default False)
686 687
687 688 message: commit message to use if committing
688 689
689 690 user: name of user to use if committing
690 691
691 692 date: date tuple to use if committing'''
692 693
693 694 if not local:
694 695 m = matchmod.exact(self.root, '', ['.hgtags'])
695 696 if any(self.status(match=m, unknown=True, ignored=True)):
696 697 raise error.Abort(_('working copy of .hgtags is changed'),
697 698 hint=_('please commit .hgtags manually'))
698 699
699 700 self.tags() # instantiate the cache
700 701 self._tag(names, node, message, local, user, date, editor=editor)
701 702
702 703 @filteredpropertycache
703 704 def _tagscache(self):
704 705 '''Returns a tagscache object that contains various tags related
705 706 caches.'''
706 707
707 708 # This simplifies its cache management by having one decorated
708 709 # function (this one) and the rest simply fetch things from it.
709 710 class tagscache(object):
710 711 def __init__(self):
711 712 # These two define the set of tags for this repository. tags
712 713 # maps tag name to node; tagtypes maps tag name to 'global' or
713 714 # 'local'. (Global tags are defined by .hgtags across all
714 715 # heads, and local tags are defined in .hg/localtags.)
715 716 # They constitute the in-memory cache of tags.
716 717 self.tags = self.tagtypes = None
717 718
718 719 self.nodetagscache = self.tagslist = None
719 720
720 721 cache = tagscache()
721 722 cache.tags, cache.tagtypes = self._findtags()
722 723
723 724 return cache
724 725
725 726 def tags(self):
726 727 '''return a mapping of tag to node'''
727 728 t = {}
728 729 if self.changelog.filteredrevs:
729 730 tags, tt = self._findtags()
730 731 else:
731 732 tags = self._tagscache.tags
732 733 for k, v in tags.iteritems():
733 734 try:
734 735 # ignore tags to unknown nodes
735 736 self.changelog.rev(v)
736 737 t[k] = v
737 738 except (error.LookupError, ValueError):
738 739 pass
739 740 return t
740 741
741 742 def _findtags(self):
742 743 '''Do the hard work of finding tags. Return a pair of dicts
743 744 (tags, tagtypes) where tags maps tag name to node, and tagtypes
744 745 maps tag name to a string like \'global\' or \'local\'.
745 746 Subclasses or extensions are free to add their own tags, but
746 747 should be aware that the returned dicts will be retained for the
747 748 duration of the localrepo object.'''
748 749
749 750 # XXX what tagtype should subclasses/extensions use? Currently
750 751 # mq and bookmarks add tags, but do not set the tagtype at all.
751 752 # Should each extension invent its own tag type? Should there
752 753 # be one tagtype for all such "virtual" tags? Or is the status
753 754 # quo fine?
754 755
755 756 alltags = {} # map tag name to (node, hist)
756 757 tagtypes = {}
757 758
758 759 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
759 760 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
760 761
761 762 # Build the return dicts. Have to re-encode tag names because
762 763 # the tags module always uses UTF-8 (in order not to lose info
763 764 # writing to the cache), but the rest of Mercurial wants them in
764 765 # local encoding.
765 766 tags = {}
766 767 for (name, (node, hist)) in alltags.iteritems():
767 768 if node != nullid:
768 769 tags[encoding.tolocal(name)] = node
769 770 tags['tip'] = self.changelog.tip()
770 771 tagtypes = dict([(encoding.tolocal(name), value)
771 772 for (name, value) in tagtypes.iteritems()])
772 773 return (tags, tagtypes)
773 774
774 775 def tagtype(self, tagname):
775 776 '''
776 777 return the type of the given tag. result can be:
777 778
778 779 'local' : a local tag
779 780 'global' : a global tag
780 781 None : tag does not exist
781 782 '''
782 783
783 784 return self._tagscache.tagtypes.get(tagname)
784 785
785 786 def tagslist(self):
786 787 '''return a list of tags ordered by revision'''
787 788 if not self._tagscache.tagslist:
788 789 l = []
789 790 for t, n in self.tags().iteritems():
790 791 l.append((self.changelog.rev(n), t, n))
791 792 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
792 793
793 794 return self._tagscache.tagslist
794 795
795 796 def nodetags(self, node):
796 797 '''return the tags associated with a node'''
797 798 if not self._tagscache.nodetagscache:
798 799 nodetagscache = {}
799 800 for t, n in self._tagscache.tags.iteritems():
800 801 nodetagscache.setdefault(n, []).append(t)
801 802 for tags in nodetagscache.itervalues():
802 803 tags.sort()
803 804 self._tagscache.nodetagscache = nodetagscache
804 805 return self._tagscache.nodetagscache.get(node, [])
805 806
806 807 def nodebookmarks(self, node):
807 808 """return the list of bookmarks pointing to the specified node"""
808 809 marks = []
809 810 for bookmark, n in self._bookmarks.iteritems():
810 811 if n == node:
811 812 marks.append(bookmark)
812 813 return sorted(marks)
813 814
814 815 def branchmap(self):
815 816 '''returns a dictionary {branch: [branchheads]} with branchheads
816 817 ordered by increasing revision number'''
817 818 branchmap.updatecache(self)
818 819 return self._branchcaches[self.filtername]
819 820
820 821 @unfilteredmethod
821 822 def revbranchcache(self):
822 823 if not self._revbranchcache:
823 824 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
824 825 return self._revbranchcache
825 826
826 827 def branchtip(self, branch, ignoremissing=False):
827 828 '''return the tip node for a given branch
828 829
829 830 If ignoremissing is True, then this method will not raise an error.
830 831 This is helpful for callers that only expect None for a missing branch
831 832 (e.g. namespace).
832 833
833 834 '''
834 835 try:
835 836 return self.branchmap().branchtip(branch)
836 837 except KeyError:
837 838 if not ignoremissing:
838 839 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
839 840 else:
840 841 pass
841 842
842 843 def lookup(self, key):
843 844 return self[key].node()
844 845
845 846 def lookupbranch(self, key, remote=None):
846 847 repo = remote or self
847 848 if key in repo.branchmap():
848 849 return key
849 850
850 851 repo = (remote and remote.local()) and remote or self
851 852 return repo[key].branch()
852 853
853 854 def known(self, nodes):
854 855 cl = self.changelog
855 856 nm = cl.nodemap
856 857 filtered = cl.filteredrevs
857 858 result = []
858 859 for n in nodes:
859 860 r = nm.get(n)
860 861 resp = not (r is None or r in filtered)
861 862 result.append(resp)
862 863 return result
863 864
864 865 def local(self):
865 866 return self
866 867
867 868 def publishing(self):
868 869 # it's safe (and desirable) to trust the publish flag unconditionally
869 870 # so that we don't finalize changes shared between users via ssh or nfs
870 871 return self.ui.configbool('phases', 'publish', True, untrusted=True)
871 872
872 873 def cancopy(self):
873 874 # so statichttprepo's override of local() works
874 875 if not self.local():
875 876 return False
876 877 if not self.publishing():
877 878 return True
878 879 # if publishing we can't copy if there is filtered content
879 880 return not self.filtered('visible').changelog.filteredrevs
880 881
881 882 def shared(self):
882 883 '''the type of shared repository (None if not shared)'''
883 884 if self.sharedpath != self.path:
884 885 return 'store'
885 886 return None
886 887
887 888 def join(self, f, *insidef):
888 889 return self.vfs.join(os.path.join(f, *insidef))
889 890
890 891 def wjoin(self, f, *insidef):
891 892 return self.vfs.reljoin(self.root, f, *insidef)
892 893
893 894 def file(self, f):
894 895 if f[0] == '/':
895 896 f = f[1:]
896 897 return filelog.filelog(self.svfs, f)
897 898
898 899 def changectx(self, changeid):
899 900 return self[changeid]
900 901
901 902 def setparents(self, p1, p2=nullid):
902 903 self.dirstate.beginparentchange()
903 904 copies = self.dirstate.setparents(p1, p2)
904 905 pctx = self[p1]
905 906 if copies:
906 907 # Adjust copy records, the dirstate cannot do it, it
907 908 # requires access to parents manifests. Preserve them
908 909 # only for entries added to first parent.
909 910 for f in copies:
910 911 if f not in pctx and copies[f] in pctx:
911 912 self.dirstate.copy(copies[f], f)
912 913 if p2 == nullid:
913 914 for f, s in sorted(self.dirstate.copies().items()):
914 915 if f not in pctx and s not in pctx:
915 916 self.dirstate.copy(None, f)
916 917 self.dirstate.endparentchange()
917 918
918 919 def filectx(self, path, changeid=None, fileid=None):
919 920 """changeid can be a changeset revision, node, or tag.
920 921 fileid can be a file revision or node."""
921 922 return context.filectx(self, path, changeid, fileid)
922 923
923 924 def getcwd(self):
924 925 return self.dirstate.getcwd()
925 926
926 927 def pathto(self, f, cwd=None):
927 928 return self.dirstate.pathto(f, cwd)
928 929
929 930 def wfile(self, f, mode='r'):
930 931 return self.wvfs(f, mode)
931 932
932 933 def _link(self, f):
933 934 return self.wvfs.islink(f)
934 935
935 936 def _loadfilter(self, filter):
936 937 if filter not in self.filterpats:
937 938 l = []
938 939 for pat, cmd in self.ui.configitems(filter):
939 940 if cmd == '!':
940 941 continue
941 942 mf = matchmod.match(self.root, '', [pat])
942 943 fn = None
943 944 params = cmd
944 945 for name, filterfn in self._datafilters.iteritems():
945 946 if cmd.startswith(name):
946 947 fn = filterfn
947 948 params = cmd[len(name):].lstrip()
948 949 break
949 950 if not fn:
950 951 fn = lambda s, c, **kwargs: util.filter(s, c)
951 952 # Wrap old filters not supporting keyword arguments
952 953 if not inspect.getargspec(fn)[2]:
953 954 oldfn = fn
954 955 fn = lambda s, c, **kwargs: oldfn(s, c)
955 956 l.append((mf, fn, params))
956 957 self.filterpats[filter] = l
957 958 return self.filterpats[filter]
958 959
959 960 def _filter(self, filterpats, filename, data):
960 961 for mf, fn, cmd in filterpats:
961 962 if mf(filename):
962 963 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
963 964 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
964 965 break
965 966
966 967 return data
967 968
968 969 @unfilteredpropertycache
969 970 def _encodefilterpats(self):
970 971 return self._loadfilter('encode')
971 972
972 973 @unfilteredpropertycache
973 974 def _decodefilterpats(self):
974 975 return self._loadfilter('decode')
975 976
976 977 def adddatafilter(self, name, filter):
977 978 self._datafilters[name] = filter
978 979
979 980 def wread(self, filename):
980 981 if self._link(filename):
981 982 data = self.wvfs.readlink(filename)
982 983 else:
983 984 data = self.wvfs.read(filename)
984 985 return self._filter(self._encodefilterpats, filename, data)
985 986
986 987 def wwrite(self, filename, data, flags, backgroundclose=False):
987 988 """write ``data`` into ``filename`` in the working directory
988 989
989 990 This returns length of written (maybe decoded) data.
990 991 """
991 992 data = self._filter(self._decodefilterpats, filename, data)
992 993 if 'l' in flags:
993 994 self.wvfs.symlink(data, filename)
994 995 else:
995 996 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
996 997 if 'x' in flags:
997 998 self.wvfs.setflags(filename, False, True)
998 999 return len(data)
999 1000
1000 1001 def wwritedata(self, filename, data):
1001 1002 return self._filter(self._decodefilterpats, filename, data)
1002 1003
1003 1004 def currenttransaction(self):
1004 1005 """return the current transaction or None if non exists"""
1005 1006 if self._transref:
1006 1007 tr = self._transref()
1007 1008 else:
1008 1009 tr = None
1009 1010
1010 1011 if tr and tr.running():
1011 1012 return tr
1012 1013 return None
1013 1014
1014 1015 def transaction(self, desc, report=None):
1015 1016 if (self.ui.configbool('devel', 'all-warnings')
1016 1017 or self.ui.configbool('devel', 'check-locks')):
1017 1018 if self._currentlock(self._lockref) is None:
1018 1019 raise RuntimeError('programming error: transaction requires '
1019 1020 'locking')
1020 1021 tr = self.currenttransaction()
1021 1022 if tr is not None:
1022 1023 return tr.nest()
1023 1024
1024 1025 # abort here if the journal already exists
1025 1026 if self.svfs.exists("journal"):
1026 1027 raise error.RepoError(
1027 1028 _("abandoned transaction found"),
1028 1029 hint=_("run 'hg recover' to clean up transaction"))
1029 1030
1030 1031 idbase = "%.40f#%f" % (random.random(), time.time())
1031 1032 txnid = 'TXN:' + hashlib.sha1(idbase).hexdigest()
1032 1033 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1033 1034
1034 1035 self._writejournal(desc)
1035 1036 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1036 1037 if report:
1037 1038 rp = report
1038 1039 else:
1039 1040 rp = self.ui.warn
1040 1041 vfsmap = {'plain': self.vfs} # root of .hg/
1041 1042 # we must avoid cyclic reference between repo and transaction.
1042 1043 reporef = weakref.ref(self)
1043 1044 def validate(tr):
1044 1045 """will run pre-closing hooks"""
1045 1046 reporef().hook('pretxnclose', throw=True,
1046 1047 txnname=desc, **tr.hookargs)
1047 1048 def releasefn(tr, success):
1048 1049 repo = reporef()
1049 1050 if success:
1050 1051 # this should be explicitly invoked here, because
1051 1052 # in-memory changes aren't written out at closing
1052 1053 # transaction, if tr.addfilegenerator (via
1053 1054 # dirstate.write or so) isn't invoked while
1054 1055 # transaction running
1055 1056 repo.dirstate.write(None)
1056 1057 else:
1057 1058 # discard all changes (including ones already written
1058 1059 # out) in this transaction
1059 1060 repo.dirstate.restorebackup(None, prefix='journal.')
1060 1061
1061 1062 repo.invalidate(clearfilecache=True)
1062 1063
1063 1064 tr = transaction.transaction(rp, self.svfs, vfsmap,
1064 1065 "journal",
1065 1066 "undo",
1066 1067 aftertrans(renames),
1067 1068 self.store.createmode,
1068 1069 validator=validate,
1069 1070 releasefn=releasefn)
1070 1071
1071 1072 tr.hookargs['txnid'] = txnid
1072 1073 # note: writing the fncache only during finalize mean that the file is
1073 1074 # outdated when running hooks. As fncache is used for streaming clone,
1074 1075 # this is not expected to break anything that happen during the hooks.
1075 1076 tr.addfinalize('flush-fncache', self.store.write)
1076 1077 def txnclosehook(tr2):
1077 1078 """To be run if transaction is successful, will schedule a hook run
1078 1079 """
1079 1080 # Don't reference tr2 in hook() so we don't hold a reference.
1080 1081 # This reduces memory consumption when there are multiple
1081 1082 # transactions per lock. This can likely go away if issue5045
1082 1083 # fixes the function accumulation.
1083 1084 hookargs = tr2.hookargs
1084 1085
1085 1086 def hook():
1086 1087 reporef().hook('txnclose', throw=False, txnname=desc,
1087 1088 **hookargs)
1088 1089 reporef()._afterlock(hook)
1089 1090 tr.addfinalize('txnclose-hook', txnclosehook)
1090 1091 def txnaborthook(tr2):
1091 1092 """To be run if transaction is aborted
1092 1093 """
1093 1094 reporef().hook('txnabort', throw=False, txnname=desc,
1094 1095 **tr2.hookargs)
1095 1096 tr.addabort('txnabort-hook', txnaborthook)
1096 1097 # avoid eager cache invalidation. in-memory data should be identical
1097 1098 # to stored data if transaction has no error.
1098 1099 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1099 1100 self._transref = weakref.ref(tr)
1100 1101 return tr
1101 1102
1102 1103 def _journalfiles(self):
1103 1104 return ((self.svfs, 'journal'),
1104 1105 (self.vfs, 'journal.dirstate'),
1105 1106 (self.vfs, 'journal.branch'),
1106 1107 (self.vfs, 'journal.desc'),
1107 1108 (self.vfs, 'journal.bookmarks'),
1108 1109 (self.svfs, 'journal.phaseroots'))
1109 1110
1110 1111 def undofiles(self):
1111 1112 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1112 1113
1113 1114 def _writejournal(self, desc):
1114 1115 self.dirstate.savebackup(None, prefix='journal.')
1115 1116 self.vfs.write("journal.branch",
1116 1117 encoding.fromlocal(self.dirstate.branch()))
1117 1118 self.vfs.write("journal.desc",
1118 1119 "%d\n%s\n" % (len(self), desc))
1119 1120 self.vfs.write("journal.bookmarks",
1120 1121 self.vfs.tryread("bookmarks"))
1121 1122 self.svfs.write("journal.phaseroots",
1122 1123 self.svfs.tryread("phaseroots"))
1123 1124
1124 1125 def recover(self):
1125 1126 with self.lock():
1126 1127 if self.svfs.exists("journal"):
1127 1128 self.ui.status(_("rolling back interrupted transaction\n"))
1128 1129 vfsmap = {'': self.svfs,
1129 1130 'plain': self.vfs,}
1130 1131 transaction.rollback(self.svfs, vfsmap, "journal",
1131 1132 self.ui.warn)
1132 1133 self.invalidate()
1133 1134 return True
1134 1135 else:
1135 1136 self.ui.warn(_("no interrupted transaction available\n"))
1136 1137 return False
1137 1138
1138 1139 def rollback(self, dryrun=False, force=False):
1139 1140 wlock = lock = dsguard = None
1140 1141 try:
1141 1142 wlock = self.wlock()
1142 1143 lock = self.lock()
1143 1144 if self.svfs.exists("undo"):
1144 dsguard = cmdutil.dirstateguard(self, 'rollback')
1145 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1145 1146
1146 1147 return self._rollback(dryrun, force, dsguard)
1147 1148 else:
1148 1149 self.ui.warn(_("no rollback information available\n"))
1149 1150 return 1
1150 1151 finally:
1151 1152 release(dsguard, lock, wlock)
1152 1153
1153 1154 @unfilteredmethod # Until we get smarter cache management
1154 1155 def _rollback(self, dryrun, force, dsguard):
1155 1156 ui = self.ui
1156 1157 try:
1157 1158 args = self.vfs.read('undo.desc').splitlines()
1158 1159 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1159 1160 if len(args) >= 3:
1160 1161 detail = args[2]
1161 1162 oldtip = oldlen - 1
1162 1163
1163 1164 if detail and ui.verbose:
1164 1165 msg = (_('repository tip rolled back to revision %s'
1165 1166 ' (undo %s: %s)\n')
1166 1167 % (oldtip, desc, detail))
1167 1168 else:
1168 1169 msg = (_('repository tip rolled back to revision %s'
1169 1170 ' (undo %s)\n')
1170 1171 % (oldtip, desc))
1171 1172 except IOError:
1172 1173 msg = _('rolling back unknown transaction\n')
1173 1174 desc = None
1174 1175
1175 1176 if not force and self['.'] != self['tip'] and desc == 'commit':
1176 1177 raise error.Abort(
1177 1178 _('rollback of last commit while not checked out '
1178 1179 'may lose data'), hint=_('use -f to force'))
1179 1180
1180 1181 ui.status(msg)
1181 1182 if dryrun:
1182 1183 return 0
1183 1184
1184 1185 parents = self.dirstate.parents()
1185 1186 self.destroying()
1186 1187 vfsmap = {'plain': self.vfs, '': self.svfs}
1187 1188 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1188 1189 if self.vfs.exists('undo.bookmarks'):
1189 1190 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1190 1191 if self.svfs.exists('undo.phaseroots'):
1191 1192 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1192 1193 self.invalidate()
1193 1194
1194 1195 parentgone = (parents[0] not in self.changelog.nodemap or
1195 1196 parents[1] not in self.changelog.nodemap)
1196 1197 if parentgone:
1197 1198 # prevent dirstateguard from overwriting already restored one
1198 1199 dsguard.close()
1199 1200
1200 1201 self.dirstate.restorebackup(None, prefix='undo.')
1201 1202 try:
1202 1203 branch = self.vfs.read('undo.branch')
1203 1204 self.dirstate.setbranch(encoding.tolocal(branch))
1204 1205 except IOError:
1205 1206 ui.warn(_('named branch could not be reset: '
1206 1207 'current branch is still \'%s\'\n')
1207 1208 % self.dirstate.branch())
1208 1209
1209 1210 parents = tuple([p.rev() for p in self[None].parents()])
1210 1211 if len(parents) > 1:
1211 1212 ui.status(_('working directory now based on '
1212 1213 'revisions %d and %d\n') % parents)
1213 1214 else:
1214 1215 ui.status(_('working directory now based on '
1215 1216 'revision %d\n') % parents)
1216 1217 mergemod.mergestate.clean(self, self['.'].node())
1217 1218
1218 1219 # TODO: if we know which new heads may result from this rollback, pass
1219 1220 # them to destroy(), which will prevent the branchhead cache from being
1220 1221 # invalidated.
1221 1222 self.destroyed()
1222 1223 return 0
1223 1224
1224 1225 def invalidatecaches(self):
1225 1226
1226 1227 if '_tagscache' in vars(self):
1227 1228 # can't use delattr on proxy
1228 1229 del self.__dict__['_tagscache']
1229 1230
1230 1231 self.unfiltered()._branchcaches.clear()
1231 1232 self.invalidatevolatilesets()
1232 1233
1233 1234 def invalidatevolatilesets(self):
1234 1235 self.filteredrevcache.clear()
1235 1236 obsolete.clearobscaches(self)
1236 1237
1237 1238 def invalidatedirstate(self):
1238 1239 '''Invalidates the dirstate, causing the next call to dirstate
1239 1240 to check if it was modified since the last time it was read,
1240 1241 rereading it if it has.
1241 1242
1242 1243 This is different to dirstate.invalidate() that it doesn't always
1243 1244 rereads the dirstate. Use dirstate.invalidate() if you want to
1244 1245 explicitly read the dirstate again (i.e. restoring it to a previous
1245 1246 known good state).'''
1246 1247 if hasunfilteredcache(self, 'dirstate'):
1247 1248 for k in self.dirstate._filecache:
1248 1249 try:
1249 1250 delattr(self.dirstate, k)
1250 1251 except AttributeError:
1251 1252 pass
1252 1253 delattr(self.unfiltered(), 'dirstate')
1253 1254
1254 1255 def invalidate(self, clearfilecache=False):
1255 1256 '''Invalidates both store and non-store parts other than dirstate
1256 1257
1257 1258 If a transaction is running, invalidation of store is omitted,
1258 1259 because discarding in-memory changes might cause inconsistency
1259 1260 (e.g. incomplete fncache causes unintentional failure, but
1260 1261 redundant one doesn't).
1261 1262 '''
1262 1263 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1263 1264 for k in self._filecache.keys():
1264 1265 # dirstate is invalidated separately in invalidatedirstate()
1265 1266 if k == 'dirstate':
1266 1267 continue
1267 1268
1268 1269 if clearfilecache:
1269 1270 del self._filecache[k]
1270 1271 try:
1271 1272 delattr(unfiltered, k)
1272 1273 except AttributeError:
1273 1274 pass
1274 1275 self.invalidatecaches()
1275 1276 if not self.currenttransaction():
1276 1277 # TODO: Changing contents of store outside transaction
1277 1278 # causes inconsistency. We should make in-memory store
1278 1279 # changes detectable, and abort if changed.
1279 1280 self.store.invalidatecaches()
1280 1281
1281 1282 def invalidateall(self):
1282 1283 '''Fully invalidates both store and non-store parts, causing the
1283 1284 subsequent operation to reread any outside changes.'''
1284 1285 # extension should hook this to invalidate its caches
1285 1286 self.invalidate()
1286 1287 self.invalidatedirstate()
1287 1288
1288 1289 @unfilteredmethod
1289 1290 def _refreshfilecachestats(self, tr):
1290 1291 """Reload stats of cached files so that they are flagged as valid"""
1291 1292 for k, ce in self._filecache.items():
1292 1293 if k == 'dirstate' or k not in self.__dict__:
1293 1294 continue
1294 1295 ce.refresh()
1295 1296
1296 1297 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1297 1298 inheritchecker=None, parentenvvar=None):
1298 1299 parentlock = None
1299 1300 # the contents of parentenvvar are used by the underlying lock to
1300 1301 # determine whether it can be inherited
1301 1302 if parentenvvar is not None:
1302 1303 parentlock = os.environ.get(parentenvvar)
1303 1304 try:
1304 1305 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1305 1306 acquirefn=acquirefn, desc=desc,
1306 1307 inheritchecker=inheritchecker,
1307 1308 parentlock=parentlock)
1308 1309 except error.LockHeld as inst:
1309 1310 if not wait:
1310 1311 raise
1311 1312 # show more details for new-style locks
1312 1313 if ':' in inst.locker:
1313 1314 host, pid = inst.locker.split(":", 1)
1314 1315 self.ui.warn(
1315 1316 _("waiting for lock on %s held by process %r "
1316 1317 "on host %r\n") % (desc, pid, host))
1317 1318 else:
1318 1319 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1319 1320 (desc, inst.locker))
1320 1321 # default to 600 seconds timeout
1321 1322 l = lockmod.lock(vfs, lockname,
1322 1323 int(self.ui.config("ui", "timeout", "600")),
1323 1324 releasefn=releasefn, acquirefn=acquirefn,
1324 1325 desc=desc)
1325 1326 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1326 1327 return l
1327 1328
1328 1329 def _afterlock(self, callback):
1329 1330 """add a callback to be run when the repository is fully unlocked
1330 1331
1331 1332 The callback will be executed when the outermost lock is released
1332 1333 (with wlock being higher level than 'lock')."""
1333 1334 for ref in (self._wlockref, self._lockref):
1334 1335 l = ref and ref()
1335 1336 if l and l.held:
1336 1337 l.postrelease.append(callback)
1337 1338 break
1338 1339 else: # no lock have been found.
1339 1340 callback()
1340 1341
1341 1342 def lock(self, wait=True):
1342 1343 '''Lock the repository store (.hg/store) and return a weak reference
1343 1344 to the lock. Use this before modifying the store (e.g. committing or
1344 1345 stripping). If you are opening a transaction, get a lock as well.)
1345 1346
1346 1347 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1347 1348 'wlock' first to avoid a dead-lock hazard.'''
1348 1349 l = self._currentlock(self._lockref)
1349 1350 if l is not None:
1350 1351 l.lock()
1351 1352 return l
1352 1353
1353 1354 l = self._lock(self.svfs, "lock", wait, None,
1354 1355 self.invalidate, _('repository %s') % self.origroot)
1355 1356 self._lockref = weakref.ref(l)
1356 1357 return l
1357 1358
1358 1359 def _wlockchecktransaction(self):
1359 1360 if self.currenttransaction() is not None:
1360 1361 raise error.LockInheritanceContractViolation(
1361 1362 'wlock cannot be inherited in the middle of a transaction')
1362 1363
1363 1364 def wlock(self, wait=True):
1364 1365 '''Lock the non-store parts of the repository (everything under
1365 1366 .hg except .hg/store) and return a weak reference to the lock.
1366 1367
1367 1368 Use this before modifying files in .hg.
1368 1369
1369 1370 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1370 1371 'wlock' first to avoid a dead-lock hazard.'''
1371 1372 l = self._wlockref and self._wlockref()
1372 1373 if l is not None and l.held:
1373 1374 l.lock()
1374 1375 return l
1375 1376
1376 1377 # We do not need to check for non-waiting lock acquisition. Such
1377 1378 # acquisition would not cause dead-lock as they would just fail.
1378 1379 if wait and (self.ui.configbool('devel', 'all-warnings')
1379 1380 or self.ui.configbool('devel', 'check-locks')):
1380 1381 if self._currentlock(self._lockref) is not None:
1381 1382 self.ui.develwarn('"wlock" acquired after "lock"')
1382 1383
1383 1384 def unlock():
1384 1385 if self.dirstate.pendingparentchange():
1385 1386 self.dirstate.invalidate()
1386 1387 else:
1387 1388 self.dirstate.write(None)
1388 1389
1389 1390 self._filecache['dirstate'].refresh()
1390 1391
1391 1392 l = self._lock(self.vfs, "wlock", wait, unlock,
1392 1393 self.invalidatedirstate, _('working directory of %s') %
1393 1394 self.origroot,
1394 1395 inheritchecker=self._wlockchecktransaction,
1395 1396 parentenvvar='HG_WLOCK_LOCKER')
1396 1397 self._wlockref = weakref.ref(l)
1397 1398 return l
1398 1399
1399 1400 def _currentlock(self, lockref):
1400 1401 """Returns the lock if it's held, or None if it's not."""
1401 1402 if lockref is None:
1402 1403 return None
1403 1404 l = lockref()
1404 1405 if l is None or not l.held:
1405 1406 return None
1406 1407 return l
1407 1408
1408 1409 def currentwlock(self):
1409 1410 """Returns the wlock if it's held, or None if it's not."""
1410 1411 return self._currentlock(self._wlockref)
1411 1412
1412 1413 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1413 1414 """
1414 1415 commit an individual file as part of a larger transaction
1415 1416 """
1416 1417
1417 1418 fname = fctx.path()
1418 1419 fparent1 = manifest1.get(fname, nullid)
1419 1420 fparent2 = manifest2.get(fname, nullid)
1420 1421 if isinstance(fctx, context.filectx):
1421 1422 node = fctx.filenode()
1422 1423 if node in [fparent1, fparent2]:
1423 1424 self.ui.debug('reusing %s filelog entry\n' % fname)
1424 1425 if manifest1.flags(fname) != fctx.flags():
1425 1426 changelist.append(fname)
1426 1427 return node
1427 1428
1428 1429 flog = self.file(fname)
1429 1430 meta = {}
1430 1431 copy = fctx.renamed()
1431 1432 if copy and copy[0] != fname:
1432 1433 # Mark the new revision of this file as a copy of another
1433 1434 # file. This copy data will effectively act as a parent
1434 1435 # of this new revision. If this is a merge, the first
1435 1436 # parent will be the nullid (meaning "look up the copy data")
1436 1437 # and the second one will be the other parent. For example:
1437 1438 #
1438 1439 # 0 --- 1 --- 3 rev1 changes file foo
1439 1440 # \ / rev2 renames foo to bar and changes it
1440 1441 # \- 2 -/ rev3 should have bar with all changes and
1441 1442 # should record that bar descends from
1442 1443 # bar in rev2 and foo in rev1
1443 1444 #
1444 1445 # this allows this merge to succeed:
1445 1446 #
1446 1447 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1447 1448 # \ / merging rev3 and rev4 should use bar@rev2
1448 1449 # \- 2 --- 4 as the merge base
1449 1450 #
1450 1451
1451 1452 cfname = copy[0]
1452 1453 crev = manifest1.get(cfname)
1453 1454 newfparent = fparent2
1454 1455
1455 1456 if manifest2: # branch merge
1456 1457 if fparent2 == nullid or crev is None: # copied on remote side
1457 1458 if cfname in manifest2:
1458 1459 crev = manifest2[cfname]
1459 1460 newfparent = fparent1
1460 1461
1461 1462 # Here, we used to search backwards through history to try to find
1462 1463 # where the file copy came from if the source of a copy was not in
1463 1464 # the parent directory. However, this doesn't actually make sense to
1464 1465 # do (what does a copy from something not in your working copy even
1465 1466 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1466 1467 # the user that copy information was dropped, so if they didn't
1467 1468 # expect this outcome it can be fixed, but this is the correct
1468 1469 # behavior in this circumstance.
1469 1470
1470 1471 if crev:
1471 1472 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1472 1473 meta["copy"] = cfname
1473 1474 meta["copyrev"] = hex(crev)
1474 1475 fparent1, fparent2 = nullid, newfparent
1475 1476 else:
1476 1477 self.ui.warn(_("warning: can't find ancestor for '%s' "
1477 1478 "copied from '%s'!\n") % (fname, cfname))
1478 1479
1479 1480 elif fparent1 == nullid:
1480 1481 fparent1, fparent2 = fparent2, nullid
1481 1482 elif fparent2 != nullid:
1482 1483 # is one parent an ancestor of the other?
1483 1484 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1484 1485 if fparent1 in fparentancestors:
1485 1486 fparent1, fparent2 = fparent2, nullid
1486 1487 elif fparent2 in fparentancestors:
1487 1488 fparent2 = nullid
1488 1489
1489 1490 # is the file changed?
1490 1491 text = fctx.data()
1491 1492 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1492 1493 changelist.append(fname)
1493 1494 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1494 1495 # are just the flags changed during merge?
1495 1496 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1496 1497 changelist.append(fname)
1497 1498
1498 1499 return fparent1
1499 1500
1500 1501 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1501 1502 """check for commit arguments that aren't committable"""
1502 1503 if match.isexact() or match.prefix():
1503 1504 matched = set(status.modified + status.added + status.removed)
1504 1505
1505 1506 for f in match.files():
1506 1507 f = self.dirstate.normalize(f)
1507 1508 if f == '.' or f in matched or f in wctx.substate:
1508 1509 continue
1509 1510 if f in status.deleted:
1510 1511 fail(f, _('file not found!'))
1511 1512 if f in vdirs: # visited directory
1512 1513 d = f + '/'
1513 1514 for mf in matched:
1514 1515 if mf.startswith(d):
1515 1516 break
1516 1517 else:
1517 1518 fail(f, _("no match under directory!"))
1518 1519 elif f not in self.dirstate:
1519 1520 fail(f, _("file not tracked!"))
1520 1521
1521 1522 @unfilteredmethod
1522 1523 def commit(self, text="", user=None, date=None, match=None, force=False,
1523 1524 editor=False, extra=None):
1524 1525 """Add a new revision to current repository.
1525 1526
1526 1527 Revision information is gathered from the working directory,
1527 1528 match can be used to filter the committed files. If editor is
1528 1529 supplied, it is called to get a commit message.
1529 1530 """
1530 1531 if extra is None:
1531 1532 extra = {}
1532 1533
1533 1534 def fail(f, msg):
1534 1535 raise error.Abort('%s: %s' % (f, msg))
1535 1536
1536 1537 if not match:
1537 1538 match = matchmod.always(self.root, '')
1538 1539
1539 1540 if not force:
1540 1541 vdirs = []
1541 1542 match.explicitdir = vdirs.append
1542 1543 match.bad = fail
1543 1544
1544 1545 wlock = lock = tr = None
1545 1546 try:
1546 1547 wlock = self.wlock()
1547 1548 lock = self.lock() # for recent changelog (see issue4368)
1548 1549
1549 1550 wctx = self[None]
1550 1551 merge = len(wctx.parents()) > 1
1551 1552
1552 1553 if not force and merge and match.ispartial():
1553 1554 raise error.Abort(_('cannot partially commit a merge '
1554 1555 '(do not specify files or patterns)'))
1555 1556
1556 1557 status = self.status(match=match, clean=force)
1557 1558 if force:
1558 1559 status.modified.extend(status.clean) # mq may commit clean files
1559 1560
1560 1561 # check subrepos
1561 1562 subs = []
1562 1563 commitsubs = set()
1563 1564 newstate = wctx.substate.copy()
1564 1565 # only manage subrepos and .hgsubstate if .hgsub is present
1565 1566 if '.hgsub' in wctx:
1566 1567 # we'll decide whether to track this ourselves, thanks
1567 1568 for c in status.modified, status.added, status.removed:
1568 1569 if '.hgsubstate' in c:
1569 1570 c.remove('.hgsubstate')
1570 1571
1571 1572 # compare current state to last committed state
1572 1573 # build new substate based on last committed state
1573 1574 oldstate = wctx.p1().substate
1574 1575 for s in sorted(newstate.keys()):
1575 1576 if not match(s):
1576 1577 # ignore working copy, use old state if present
1577 1578 if s in oldstate:
1578 1579 newstate[s] = oldstate[s]
1579 1580 continue
1580 1581 if not force:
1581 1582 raise error.Abort(
1582 1583 _("commit with new subrepo %s excluded") % s)
1583 1584 dirtyreason = wctx.sub(s).dirtyreason(True)
1584 1585 if dirtyreason:
1585 1586 if not self.ui.configbool('ui', 'commitsubrepos'):
1586 1587 raise error.Abort(dirtyreason,
1587 1588 hint=_("use --subrepos for recursive commit"))
1588 1589 subs.append(s)
1589 1590 commitsubs.add(s)
1590 1591 else:
1591 1592 bs = wctx.sub(s).basestate()
1592 1593 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1593 1594 if oldstate.get(s, (None, None, None))[1] != bs:
1594 1595 subs.append(s)
1595 1596
1596 1597 # check for removed subrepos
1597 1598 for p in wctx.parents():
1598 1599 r = [s for s in p.substate if s not in newstate]
1599 1600 subs += [s for s in r if match(s)]
1600 1601 if subs:
1601 1602 if (not match('.hgsub') and
1602 1603 '.hgsub' in (wctx.modified() + wctx.added())):
1603 1604 raise error.Abort(
1604 1605 _("can't commit subrepos without .hgsub"))
1605 1606 status.modified.insert(0, '.hgsubstate')
1606 1607
1607 1608 elif '.hgsub' in status.removed:
1608 1609 # clean up .hgsubstate when .hgsub is removed
1609 1610 if ('.hgsubstate' in wctx and
1610 1611 '.hgsubstate' not in (status.modified + status.added +
1611 1612 status.removed)):
1612 1613 status.removed.insert(0, '.hgsubstate')
1613 1614
1614 1615 # make sure all explicit patterns are matched
1615 1616 if not force:
1616 1617 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1617 1618
1618 1619 cctx = context.workingcommitctx(self, status,
1619 1620 text, user, date, extra)
1620 1621
1621 1622 # internal config: ui.allowemptycommit
1622 1623 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1623 1624 or extra.get('close') or merge or cctx.files()
1624 1625 or self.ui.configbool('ui', 'allowemptycommit'))
1625 1626 if not allowemptycommit:
1626 1627 return None
1627 1628
1628 1629 if merge and cctx.deleted():
1629 1630 raise error.Abort(_("cannot commit merge with missing files"))
1630 1631
1631 1632 ms = mergemod.mergestate.read(self)
1632 1633 cmdutil.checkunresolved(ms)
1633 1634
1634 1635 if editor:
1635 1636 cctx._text = editor(self, cctx, subs)
1636 1637 edited = (text != cctx._text)
1637 1638
1638 1639 # Save commit message in case this transaction gets rolled back
1639 1640 # (e.g. by a pretxncommit hook). Leave the content alone on
1640 1641 # the assumption that the user will use the same editor again.
1641 1642 msgfn = self.savecommitmessage(cctx._text)
1642 1643
1643 1644 # commit subs and write new state
1644 1645 if subs:
1645 1646 for s in sorted(commitsubs):
1646 1647 sub = wctx.sub(s)
1647 1648 self.ui.status(_('committing subrepository %s\n') %
1648 1649 subrepo.subrelpath(sub))
1649 1650 sr = sub.commit(cctx._text, user, date)
1650 1651 newstate[s] = (newstate[s][0], sr)
1651 1652 subrepo.writestate(self, newstate)
1652 1653
1653 1654 p1, p2 = self.dirstate.parents()
1654 1655 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1655 1656 try:
1656 1657 self.hook("precommit", throw=True, parent1=hookp1,
1657 1658 parent2=hookp2)
1658 1659 tr = self.transaction('commit')
1659 1660 ret = self.commitctx(cctx, True)
1660 1661 except: # re-raises
1661 1662 if edited:
1662 1663 self.ui.write(
1663 1664 _('note: commit message saved in %s\n') % msgfn)
1664 1665 raise
1665 1666 # update bookmarks, dirstate and mergestate
1666 1667 bookmarks.update(self, [p1, p2], ret)
1667 1668 cctx.markcommitted(ret)
1668 1669 ms.reset()
1669 1670 tr.close()
1670 1671
1671 1672 finally:
1672 1673 lockmod.release(tr, lock, wlock)
1673 1674
1674 1675 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1675 1676 # hack for command that use a temporary commit (eg: histedit)
1676 1677 # temporary commit got stripped before hook release
1677 1678 if self.changelog.hasnode(ret):
1678 1679 self.hook("commit", node=node, parent1=parent1,
1679 1680 parent2=parent2)
1680 1681 self._afterlock(commithook)
1681 1682 return ret
1682 1683
1683 1684 @unfilteredmethod
1684 1685 def commitctx(self, ctx, error=False):
1685 1686 """Add a new revision to current repository.
1686 1687 Revision information is passed via the context argument.
1687 1688 """
1688 1689
1689 1690 tr = None
1690 1691 p1, p2 = ctx.p1(), ctx.p2()
1691 1692 user = ctx.user()
1692 1693
1693 1694 lock = self.lock()
1694 1695 try:
1695 1696 tr = self.transaction("commit")
1696 1697 trp = weakref.proxy(tr)
1697 1698
1698 1699 if ctx.files():
1699 1700 m1ctx = p1.manifestctx()
1700 1701 m2ctx = p2.manifestctx()
1701 1702 mctx = m1ctx.copy()
1702 1703
1703 1704 m = mctx.read()
1704 1705 m1 = m1ctx.read()
1705 1706 m2 = m2ctx.read()
1706 1707
1707 1708 # check in files
1708 1709 added = []
1709 1710 changed = []
1710 1711 removed = list(ctx.removed())
1711 1712 linkrev = len(self)
1712 1713 self.ui.note(_("committing files:\n"))
1713 1714 for f in sorted(ctx.modified() + ctx.added()):
1714 1715 self.ui.note(f + "\n")
1715 1716 try:
1716 1717 fctx = ctx[f]
1717 1718 if fctx is None:
1718 1719 removed.append(f)
1719 1720 else:
1720 1721 added.append(f)
1721 1722 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1722 1723 trp, changed)
1723 1724 m.setflag(f, fctx.flags())
1724 1725 except OSError as inst:
1725 1726 self.ui.warn(_("trouble committing %s!\n") % f)
1726 1727 raise
1727 1728 except IOError as inst:
1728 1729 errcode = getattr(inst, 'errno', errno.ENOENT)
1729 1730 if error or errcode and errcode != errno.ENOENT:
1730 1731 self.ui.warn(_("trouble committing %s!\n") % f)
1731 1732 raise
1732 1733
1733 1734 # update manifest
1734 1735 self.ui.note(_("committing manifest\n"))
1735 1736 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1736 1737 drop = [f for f in removed if f in m]
1737 1738 for f in drop:
1738 1739 del m[f]
1739 1740 mn = mctx.write(trp, linkrev,
1740 1741 p1.manifestnode(), p2.manifestnode(),
1741 1742 added, drop)
1742 1743 files = changed + removed
1743 1744 else:
1744 1745 mn = p1.manifestnode()
1745 1746 files = []
1746 1747
1747 1748 # update changelog
1748 1749 self.ui.note(_("committing changelog\n"))
1749 1750 self.changelog.delayupdate(tr)
1750 1751 n = self.changelog.add(mn, files, ctx.description(),
1751 1752 trp, p1.node(), p2.node(),
1752 1753 user, ctx.date(), ctx.extra().copy())
1753 1754 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1754 1755 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1755 1756 parent2=xp2)
1756 1757 # set the new commit is proper phase
1757 1758 targetphase = subrepo.newcommitphase(self.ui, ctx)
1758 1759 if targetphase:
1759 1760 # retract boundary do not alter parent changeset.
1760 1761 # if a parent have higher the resulting phase will
1761 1762 # be compliant anyway
1762 1763 #
1763 1764 # if minimal phase was 0 we don't need to retract anything
1764 1765 phases.retractboundary(self, tr, targetphase, [n])
1765 1766 tr.close()
1766 1767 branchmap.updatecache(self.filtered('served'))
1767 1768 return n
1768 1769 finally:
1769 1770 if tr:
1770 1771 tr.release()
1771 1772 lock.release()
1772 1773
1773 1774 @unfilteredmethod
1774 1775 def destroying(self):
1775 1776 '''Inform the repository that nodes are about to be destroyed.
1776 1777 Intended for use by strip and rollback, so there's a common
1777 1778 place for anything that has to be done before destroying history.
1778 1779
1779 1780 This is mostly useful for saving state that is in memory and waiting
1780 1781 to be flushed when the current lock is released. Because a call to
1781 1782 destroyed is imminent, the repo will be invalidated causing those
1782 1783 changes to stay in memory (waiting for the next unlock), or vanish
1783 1784 completely.
1784 1785 '''
1785 1786 # When using the same lock to commit and strip, the phasecache is left
1786 1787 # dirty after committing. Then when we strip, the repo is invalidated,
1787 1788 # causing those changes to disappear.
1788 1789 if '_phasecache' in vars(self):
1789 1790 self._phasecache.write()
1790 1791
1791 1792 @unfilteredmethod
1792 1793 def destroyed(self):
1793 1794 '''Inform the repository that nodes have been destroyed.
1794 1795 Intended for use by strip and rollback, so there's a common
1795 1796 place for anything that has to be done after destroying history.
1796 1797 '''
1797 1798 # When one tries to:
1798 1799 # 1) destroy nodes thus calling this method (e.g. strip)
1799 1800 # 2) use phasecache somewhere (e.g. commit)
1800 1801 #
1801 1802 # then 2) will fail because the phasecache contains nodes that were
1802 1803 # removed. We can either remove phasecache from the filecache,
1803 1804 # causing it to reload next time it is accessed, or simply filter
1804 1805 # the removed nodes now and write the updated cache.
1805 1806 self._phasecache.filterunknown(self)
1806 1807 self._phasecache.write()
1807 1808
1808 1809 # update the 'served' branch cache to help read only server process
1809 1810 # Thanks to branchcache collaboration this is done from the nearest
1810 1811 # filtered subset and it is expected to be fast.
1811 1812 branchmap.updatecache(self.filtered('served'))
1812 1813
1813 1814 # Ensure the persistent tag cache is updated. Doing it now
1814 1815 # means that the tag cache only has to worry about destroyed
1815 1816 # heads immediately after a strip/rollback. That in turn
1816 1817 # guarantees that "cachetip == currenttip" (comparing both rev
1817 1818 # and node) always means no nodes have been added or destroyed.
1818 1819
1819 1820 # XXX this is suboptimal when qrefresh'ing: we strip the current
1820 1821 # head, refresh the tag cache, then immediately add a new head.
1821 1822 # But I think doing it this way is necessary for the "instant
1822 1823 # tag cache retrieval" case to work.
1823 1824 self.invalidate()
1824 1825
1825 1826 def walk(self, match, node=None):
1826 1827 '''
1827 1828 walk recursively through the directory tree or a given
1828 1829 changeset, finding all files matched by the match
1829 1830 function
1830 1831 '''
1831 1832 return self[node].walk(match)
1832 1833
1833 1834 def status(self, node1='.', node2=None, match=None,
1834 1835 ignored=False, clean=False, unknown=False,
1835 1836 listsubrepos=False):
1836 1837 '''a convenience method that calls node1.status(node2)'''
1837 1838 return self[node1].status(node2, match, ignored, clean, unknown,
1838 1839 listsubrepos)
1839 1840
1840 1841 def heads(self, start=None):
1841 1842 heads = self.changelog.heads(start)
1842 1843 # sort the output in rev descending order
1843 1844 return sorted(heads, key=self.changelog.rev, reverse=True)
1844 1845
1845 1846 def branchheads(self, branch=None, start=None, closed=False):
1846 1847 '''return a (possibly filtered) list of heads for the given branch
1847 1848
1848 1849 Heads are returned in topological order, from newest to oldest.
1849 1850 If branch is None, use the dirstate branch.
1850 1851 If start is not None, return only heads reachable from start.
1851 1852 If closed is True, return heads that are marked as closed as well.
1852 1853 '''
1853 1854 if branch is None:
1854 1855 branch = self[None].branch()
1855 1856 branches = self.branchmap()
1856 1857 if branch not in branches:
1857 1858 return []
1858 1859 # the cache returns heads ordered lowest to highest
1859 1860 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1860 1861 if start is not None:
1861 1862 # filter out the heads that cannot be reached from startrev
1862 1863 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1863 1864 bheads = [h for h in bheads if h in fbheads]
1864 1865 return bheads
1865 1866
1866 1867 def branches(self, nodes):
1867 1868 if not nodes:
1868 1869 nodes = [self.changelog.tip()]
1869 1870 b = []
1870 1871 for n in nodes:
1871 1872 t = n
1872 1873 while True:
1873 1874 p = self.changelog.parents(n)
1874 1875 if p[1] != nullid or p[0] == nullid:
1875 1876 b.append((t, n, p[0], p[1]))
1876 1877 break
1877 1878 n = p[0]
1878 1879 return b
1879 1880
1880 1881 def between(self, pairs):
1881 1882 r = []
1882 1883
1883 1884 for top, bottom in pairs:
1884 1885 n, l, i = top, [], 0
1885 1886 f = 1
1886 1887
1887 1888 while n != bottom and n != nullid:
1888 1889 p = self.changelog.parents(n)[0]
1889 1890 if i == f:
1890 1891 l.append(n)
1891 1892 f = f * 2
1892 1893 n = p
1893 1894 i += 1
1894 1895
1895 1896 r.append(l)
1896 1897
1897 1898 return r
1898 1899
1899 1900 def checkpush(self, pushop):
1900 1901 """Extensions can override this function if additional checks have
1901 1902 to be performed before pushing, or call it if they override push
1902 1903 command.
1903 1904 """
1904 1905 pass
1905 1906
1906 1907 @unfilteredpropertycache
1907 1908 def prepushoutgoinghooks(self):
1908 1909 """Return util.hooks consists of a pushop with repo, remote, outgoing
1909 1910 methods, which are called before pushing changesets.
1910 1911 """
1911 1912 return util.hooks()
1912 1913
1913 1914 def pushkey(self, namespace, key, old, new):
1914 1915 try:
1915 1916 tr = self.currenttransaction()
1916 1917 hookargs = {}
1917 1918 if tr is not None:
1918 1919 hookargs.update(tr.hookargs)
1919 1920 hookargs['namespace'] = namespace
1920 1921 hookargs['key'] = key
1921 1922 hookargs['old'] = old
1922 1923 hookargs['new'] = new
1923 1924 self.hook('prepushkey', throw=True, **hookargs)
1924 1925 except error.HookAbort as exc:
1925 1926 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1926 1927 if exc.hint:
1927 1928 self.ui.write_err(_("(%s)\n") % exc.hint)
1928 1929 return False
1929 1930 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1930 1931 ret = pushkey.push(self, namespace, key, old, new)
1931 1932 def runhook():
1932 1933 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1933 1934 ret=ret)
1934 1935 self._afterlock(runhook)
1935 1936 return ret
1936 1937
1937 1938 def listkeys(self, namespace):
1938 1939 self.hook('prelistkeys', throw=True, namespace=namespace)
1939 1940 self.ui.debug('listing keys for "%s"\n' % namespace)
1940 1941 values = pushkey.list(self, namespace)
1941 1942 self.hook('listkeys', namespace=namespace, values=values)
1942 1943 return values
1943 1944
1944 1945 def debugwireargs(self, one, two, three=None, four=None, five=None):
1945 1946 '''used to test argument passing over the wire'''
1946 1947 return "%s %s %s %s %s" % (one, two, three, four, five)
1947 1948
1948 1949 def savecommitmessage(self, text):
1949 1950 fp = self.vfs('last-message.txt', 'wb')
1950 1951 try:
1951 1952 fp.write(text)
1952 1953 finally:
1953 1954 fp.close()
1954 1955 return self.pathto(fp.name[len(self.root) + 1:])
1955 1956
1956 1957 # used to avoid circular references so destructors work
1957 1958 def aftertrans(files):
1958 1959 renamefiles = [tuple(t) for t in files]
1959 1960 def a():
1960 1961 for vfs, src, dest in renamefiles:
1961 1962 try:
1962 1963 vfs.rename(src, dest)
1963 1964 except OSError: # journal file does not yet exist
1964 1965 pass
1965 1966 return a
1966 1967
1967 1968 def undoname(fn):
1968 1969 base, name = os.path.split(fn)
1969 1970 assert name.startswith('journal')
1970 1971 return os.path.join(base, name.replace('journal', 'undo', 1))
1971 1972
1972 1973 def instance(ui, path, create):
1973 1974 return localrepository(ui, util.urllocalpath(path), create)
1974 1975
1975 1976 def islocal(path):
1976 1977 return True
1977 1978
1978 1979 def newreporequirements(repo):
1979 1980 """Determine the set of requirements for a new local repository.
1980 1981
1981 1982 Extensions can wrap this function to specify custom requirements for
1982 1983 new repositories.
1983 1984 """
1984 1985 ui = repo.ui
1985 1986 requirements = set(['revlogv1'])
1986 1987 if ui.configbool('format', 'usestore', True):
1987 1988 requirements.add('store')
1988 1989 if ui.configbool('format', 'usefncache', True):
1989 1990 requirements.add('fncache')
1990 1991 if ui.configbool('format', 'dotencode', True):
1991 1992 requirements.add('dotencode')
1992 1993
1993 1994 if scmutil.gdinitconfig(ui):
1994 1995 requirements.add('generaldelta')
1995 1996 if ui.configbool('experimental', 'treemanifest', False):
1996 1997 requirements.add('treemanifest')
1997 1998 if ui.configbool('experimental', 'manifestv2', False):
1998 1999 requirements.add('manifestv2')
1999 2000
2000 2001 return requirements
General Comments 0
You need to be logged in to leave comments. Login now