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