##// END OF EJS Templates
obsolete: move obsstore creation logic from localrepo...
Gregory Szorc -
r32729:c8177792 default
parent child Browse files
Show More
@@ -1,2089 +1,2075 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 )
24 24 from . import (
25 25 bookmarks,
26 26 branchmap,
27 27 bundle2,
28 28 changegroup,
29 29 changelog,
30 30 color,
31 31 context,
32 32 dirstate,
33 33 dirstateguard,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 filelog,
39 39 hook,
40 40 lock as lockmod,
41 41 manifest,
42 42 match as matchmod,
43 43 merge as mergemod,
44 44 mergeutil,
45 45 namespaces,
46 46 obsolete,
47 47 pathutil,
48 48 peer,
49 49 phases,
50 50 pushkey,
51 51 pycompat,
52 52 repoview,
53 53 revset,
54 54 revsetlang,
55 55 scmutil,
56 56 store,
57 57 subrepo,
58 58 tags as tagsmod,
59 59 transaction,
60 60 txnutil,
61 61 util,
62 62 vfs as vfsmod,
63 63 )
64 64
65 65 release = lockmod.release
66 66 urlerr = util.urlerr
67 67 urlreq = util.urlreq
68 68
69 69 class repofilecache(scmutil.filecache):
70 70 """All filecache usage on repo are done for logic that should be unfiltered
71 71 """
72 72
73 73 def join(self, obj, fname):
74 74 return obj.vfs.join(fname)
75 75 def __get__(self, repo, type=None):
76 76 if repo is None:
77 77 return self
78 78 return super(repofilecache, self).__get__(repo.unfiltered(), type)
79 79 def __set__(self, repo, value):
80 80 return super(repofilecache, self).__set__(repo.unfiltered(), value)
81 81 def __delete__(self, repo):
82 82 return super(repofilecache, self).__delete__(repo.unfiltered())
83 83
84 84 class storecache(repofilecache):
85 85 """filecache for files in the store"""
86 86 def join(self, obj, fname):
87 87 return obj.sjoin(fname)
88 88
89 89 class unfilteredpropertycache(util.propertycache):
90 90 """propertycache that apply to unfiltered repo only"""
91 91
92 92 def __get__(self, repo, type=None):
93 93 unfi = repo.unfiltered()
94 94 if unfi is repo:
95 95 return super(unfilteredpropertycache, self).__get__(unfi)
96 96 return getattr(unfi, self.name)
97 97
98 98 class filteredpropertycache(util.propertycache):
99 99 """propertycache that must take filtering in account"""
100 100
101 101 def cachevalue(self, obj, value):
102 102 object.__setattr__(obj, self.name, value)
103 103
104 104
105 105 def hasunfilteredcache(repo, name):
106 106 """check if a repo has an unfilteredpropertycache value for <name>"""
107 107 return name in vars(repo.unfiltered())
108 108
109 109 def unfilteredmethod(orig):
110 110 """decorate method that always need to be run on unfiltered version"""
111 111 def wrapper(repo, *args, **kwargs):
112 112 return orig(repo.unfiltered(), *args, **kwargs)
113 113 return wrapper
114 114
115 115 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
116 116 'unbundle'}
117 117 legacycaps = moderncaps.union({'changegroupsubset'})
118 118
119 119 class localpeer(peer.peerrepository):
120 120 '''peer for a local repo; reflects only the most recent API'''
121 121
122 122 def __init__(self, repo, caps=None):
123 123 if caps is None:
124 124 caps = moderncaps.copy()
125 125 peer.peerrepository.__init__(self)
126 126 self._repo = repo.filtered('served')
127 127 self.ui = repo.ui
128 128 self._caps = repo._restrictcapabilities(caps)
129 129 self.requirements = repo.requirements
130 130 self.supportedformats = repo.supportedformats
131 131
132 132 def close(self):
133 133 self._repo.close()
134 134
135 135 def _capabilities(self):
136 136 return self._caps
137 137
138 138 def local(self):
139 139 return self._repo
140 140
141 141 def canpush(self):
142 142 return True
143 143
144 144 def url(self):
145 145 return self._repo.url()
146 146
147 147 def lookup(self, key):
148 148 return self._repo.lookup(key)
149 149
150 150 def branchmap(self):
151 151 return self._repo.branchmap()
152 152
153 153 def heads(self):
154 154 return self._repo.heads()
155 155
156 156 def known(self, nodes):
157 157 return self._repo.known(nodes)
158 158
159 159 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
160 160 **kwargs):
161 161 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
162 162 common=common, bundlecaps=bundlecaps,
163 163 **kwargs)
164 164 cb = util.chunkbuffer(chunks)
165 165
166 166 if exchange.bundle2requested(bundlecaps):
167 167 # When requesting a bundle2, getbundle returns a stream to make the
168 168 # wire level function happier. We need to build a proper object
169 169 # from it in local peer.
170 170 return bundle2.getunbundler(self.ui, cb)
171 171 else:
172 172 return changegroup.getunbundler('01', cb, None)
173 173
174 174 # TODO We might want to move the next two calls into legacypeer and add
175 175 # unbundle instead.
176 176
177 177 def unbundle(self, cg, heads, url):
178 178 """apply a bundle on a repo
179 179
180 180 This function handles the repo locking itself."""
181 181 try:
182 182 try:
183 183 cg = exchange.readbundle(self.ui, cg, None)
184 184 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
185 185 if util.safehasattr(ret, 'getchunks'):
186 186 # This is a bundle20 object, turn it into an unbundler.
187 187 # This little dance should be dropped eventually when the
188 188 # API is finally improved.
189 189 stream = util.chunkbuffer(ret.getchunks())
190 190 ret = bundle2.getunbundler(self.ui, stream)
191 191 return ret
192 192 except Exception as exc:
193 193 # If the exception contains output salvaged from a bundle2
194 194 # reply, we need to make sure it is printed before continuing
195 195 # to fail. So we build a bundle2 with such output and consume
196 196 # it directly.
197 197 #
198 198 # This is not very elegant but allows a "simple" solution for
199 199 # issue4594
200 200 output = getattr(exc, '_bundle2salvagedoutput', ())
201 201 if output:
202 202 bundler = bundle2.bundle20(self._repo.ui)
203 203 for out in output:
204 204 bundler.addpart(out)
205 205 stream = util.chunkbuffer(bundler.getchunks())
206 206 b = bundle2.getunbundler(self.ui, stream)
207 207 bundle2.processbundle(self._repo, b)
208 208 raise
209 209 except error.PushRaced as exc:
210 210 raise error.ResponseError(_('push failed:'), str(exc))
211 211
212 212 def lock(self):
213 213 return self._repo.lock()
214 214
215 215 def addchangegroup(self, cg, source, url):
216 216 return cg.apply(self._repo, source, url)
217 217
218 218 def pushkey(self, namespace, key, old, new):
219 219 return self._repo.pushkey(namespace, key, old, new)
220 220
221 221 def listkeys(self, namespace):
222 222 return self._repo.listkeys(namespace)
223 223
224 224 def debugwireargs(self, one, two, three=None, four=None, five=None):
225 225 '''used to test argument passing over the wire'''
226 226 return "%s %s %s %s %s" % (one, two, three, four, five)
227 227
228 228 class locallegacypeer(localpeer):
229 229 '''peer extension which implements legacy methods too; used for tests with
230 230 restricted capabilities'''
231 231
232 232 def __init__(self, repo):
233 233 localpeer.__init__(self, repo, caps=legacycaps)
234 234
235 235 def branches(self, nodes):
236 236 return self._repo.branches(nodes)
237 237
238 238 def between(self, pairs):
239 239 return self._repo.between(pairs)
240 240
241 241 def changegroup(self, basenodes, source):
242 242 return changegroup.changegroup(self._repo, basenodes, source)
243 243
244 244 def changegroupsubset(self, bases, heads, source):
245 245 return changegroup.changegroupsubset(self._repo, bases, heads, source)
246 246
247 247 # Increment the sub-version when the revlog v2 format changes to lock out old
248 248 # clients.
249 249 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
250 250
251 251 class localrepository(object):
252 252
253 253 supportedformats = {
254 254 'revlogv1',
255 255 'generaldelta',
256 256 'treemanifest',
257 257 'manifestv2',
258 258 REVLOGV2_REQUIREMENT,
259 259 }
260 260 _basesupported = supportedformats | {
261 261 'store',
262 262 'fncache',
263 263 'shared',
264 264 'relshared',
265 265 'dotencode',
266 266 }
267 267 openerreqs = {
268 268 'revlogv1',
269 269 'generaldelta',
270 270 'treemanifest',
271 271 'manifestv2',
272 272 }
273 273 filtername = None
274 274
275 275 # a list of (ui, featureset) functions.
276 276 # only functions defined in module of enabled extensions are invoked
277 277 featuresetupfuncs = set()
278 278
279 279 def __init__(self, baseui, path, create=False):
280 280 self.requirements = set()
281 281 # wvfs: rooted at the repository root, used to access the working copy
282 282 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
283 283 # vfs: rooted at .hg, used to access repo files outside of .hg/store
284 284 self.vfs = None
285 285 # svfs: usually rooted at .hg/store, used to access repository history
286 286 # If this is a shared repository, this vfs may point to another
287 287 # repository's .hg/store directory.
288 288 self.svfs = None
289 289 self.root = self.wvfs.base
290 290 self.path = self.wvfs.join(".hg")
291 291 self.origroot = path
292 292 self.auditor = pathutil.pathauditor(self.root, self._checknested)
293 293 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
294 294 realfs=False)
295 295 self.vfs = vfsmod.vfs(self.path)
296 296 self.baseui = baseui
297 297 self.ui = baseui.copy()
298 298 self.ui.copy = baseui.copy # prevent copying repo configuration
299 299 # A list of callback to shape the phase if no data were found.
300 300 # Callback are in the form: func(repo, roots) --> processed root.
301 301 # This list it to be filled by extension during repo setup
302 302 self._phasedefaults = []
303 303 try:
304 304 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
305 305 self._loadextensions()
306 306 except IOError:
307 307 pass
308 308
309 309 if self.featuresetupfuncs:
310 310 self.supported = set(self._basesupported) # use private copy
311 311 extmods = set(m.__name__ for n, m
312 312 in extensions.extensions(self.ui))
313 313 for setupfunc in self.featuresetupfuncs:
314 314 if setupfunc.__module__ in extmods:
315 315 setupfunc(self.ui, self.supported)
316 316 else:
317 317 self.supported = self._basesupported
318 318 color.setup(self.ui)
319 319
320 320 # Add compression engines.
321 321 for name in util.compengines:
322 322 engine = util.compengines[name]
323 323 if engine.revlogheader():
324 324 self.supported.add('exp-compression-%s' % name)
325 325
326 326 if not self.vfs.isdir():
327 327 if create:
328 328 self.requirements = newreporequirements(self)
329 329
330 330 if not self.wvfs.exists():
331 331 self.wvfs.makedirs()
332 332 self.vfs.makedir(notindexed=True)
333 333
334 334 if 'store' in self.requirements:
335 335 self.vfs.mkdir("store")
336 336
337 337 # create an invalid changelog
338 338 self.vfs.append(
339 339 "00changelog.i",
340 340 '\0\0\0\2' # represents revlogv2
341 341 ' dummy changelog to prevent using the old repo layout'
342 342 )
343 343 else:
344 344 raise error.RepoError(_("repository %s not found") % path)
345 345 elif create:
346 346 raise error.RepoError(_("repository %s already exists") % path)
347 347 else:
348 348 try:
349 349 self.requirements = scmutil.readrequires(
350 350 self.vfs, self.supported)
351 351 except IOError as inst:
352 352 if inst.errno != errno.ENOENT:
353 353 raise
354 354
355 355 self.sharedpath = self.path
356 356 try:
357 357 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
358 358 if 'relshared' in self.requirements:
359 359 sharedpath = self.vfs.join(sharedpath)
360 360 vfs = vfsmod.vfs(sharedpath, realpath=True)
361 361 s = vfs.base
362 362 if not vfs.exists():
363 363 raise error.RepoError(
364 364 _('.hg/sharedpath points to nonexistent directory %s') % s)
365 365 self.sharedpath = s
366 366 except IOError as inst:
367 367 if inst.errno != errno.ENOENT:
368 368 raise
369 369
370 370 self.store = store.store(
371 371 self.requirements, self.sharedpath, vfsmod.vfs)
372 372 self.spath = self.store.path
373 373 self.svfs = self.store.vfs
374 374 self.sjoin = self.store.join
375 375 self.vfs.createmode = self.store.createmode
376 376 self._applyopenerreqs()
377 377 if create:
378 378 self._writerequirements()
379 379
380 380 self._dirstatevalidatewarned = False
381 381
382 382 self._branchcaches = {}
383 383 self._revbranchcache = None
384 384 self.filterpats = {}
385 385 self._datafilters = {}
386 386 self._transref = self._lockref = self._wlockref = None
387 387
388 388 # A cache for various files under .hg/ that tracks file changes,
389 389 # (used by the filecache decorator)
390 390 #
391 391 # Maps a property name to its util.filecacheentry
392 392 self._filecache = {}
393 393
394 394 # hold sets of revision to be filtered
395 395 # should be cleared when something might have changed the filter value:
396 396 # - new changesets,
397 397 # - phase change,
398 398 # - new obsolescence marker,
399 399 # - working directory parent change,
400 400 # - bookmark changes
401 401 self.filteredrevcache = {}
402 402
403 403 # generic mapping between names and nodes
404 404 self.names = namespaces.namespaces()
405 405
406 406 def close(self):
407 407 self._writecaches()
408 408
409 409 def _loadextensions(self):
410 410 extensions.loadall(self.ui)
411 411
412 412 def _writecaches(self):
413 413 if self._revbranchcache:
414 414 self._revbranchcache.write()
415 415
416 416 def _restrictcapabilities(self, caps):
417 417 if self.ui.configbool('experimental', 'bundle2-advertise', True):
418 418 caps = set(caps)
419 419 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
420 420 caps.add('bundle2=' + urlreq.quote(capsblob))
421 421 return caps
422 422
423 423 def _applyopenerreqs(self):
424 424 self.svfs.options = dict((r, 1) for r in self.requirements
425 425 if r in self.openerreqs)
426 426 # experimental config: format.chunkcachesize
427 427 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
428 428 if chunkcachesize is not None:
429 429 self.svfs.options['chunkcachesize'] = chunkcachesize
430 430 # experimental config: format.maxchainlen
431 431 maxchainlen = self.ui.configint('format', 'maxchainlen')
432 432 if maxchainlen is not None:
433 433 self.svfs.options['maxchainlen'] = maxchainlen
434 434 # experimental config: format.manifestcachesize
435 435 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
436 436 if manifestcachesize is not None:
437 437 self.svfs.options['manifestcachesize'] = manifestcachesize
438 438 # experimental config: format.aggressivemergedeltas
439 439 aggressivemergedeltas = self.ui.configbool('format',
440 440 'aggressivemergedeltas', False)
441 441 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
442 442 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
443 443
444 444 for r in self.requirements:
445 445 if r.startswith('exp-compression-'):
446 446 self.svfs.options['compengine'] = r[len('exp-compression-'):]
447 447
448 448 # TODO move "revlogv2" to openerreqs once finalized.
449 449 if REVLOGV2_REQUIREMENT in self.requirements:
450 450 self.svfs.options['revlogv2'] = True
451 451
452 452 def _writerequirements(self):
453 453 scmutil.writerequires(self.vfs, self.requirements)
454 454
455 455 def _checknested(self, path):
456 456 """Determine if path is a legal nested repository."""
457 457 if not path.startswith(self.root):
458 458 return False
459 459 subpath = path[len(self.root) + 1:]
460 460 normsubpath = util.pconvert(subpath)
461 461
462 462 # XXX: Checking against the current working copy is wrong in
463 463 # the sense that it can reject things like
464 464 #
465 465 # $ hg cat -r 10 sub/x.txt
466 466 #
467 467 # if sub/ is no longer a subrepository in the working copy
468 468 # parent revision.
469 469 #
470 470 # However, it can of course also allow things that would have
471 471 # been rejected before, such as the above cat command if sub/
472 472 # is a subrepository now, but was a normal directory before.
473 473 # The old path auditor would have rejected by mistake since it
474 474 # panics when it sees sub/.hg/.
475 475 #
476 476 # All in all, checking against the working copy seems sensible
477 477 # since we want to prevent access to nested repositories on
478 478 # the filesystem *now*.
479 479 ctx = self[None]
480 480 parts = util.splitpath(subpath)
481 481 while parts:
482 482 prefix = '/'.join(parts)
483 483 if prefix in ctx.substate:
484 484 if prefix == normsubpath:
485 485 return True
486 486 else:
487 487 sub = ctx.sub(prefix)
488 488 return sub.checknested(subpath[len(prefix) + 1:])
489 489 else:
490 490 parts.pop()
491 491 return False
492 492
493 493 def peer(self):
494 494 return localpeer(self) # not cached to avoid reference cycle
495 495
496 496 def unfiltered(self):
497 497 """Return unfiltered version of the repository
498 498
499 499 Intended to be overwritten by filtered repo."""
500 500 return self
501 501
502 502 def filtered(self, name):
503 503 """Return a filtered version of a repository"""
504 504 # build a new class with the mixin and the current class
505 505 # (possibly subclass of the repo)
506 506 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
507 507 pass
508 508 return filteredrepo(self, name)
509 509
510 510 @repofilecache('bookmarks', 'bookmarks.current')
511 511 def _bookmarks(self):
512 512 return bookmarks.bmstore(self)
513 513
514 514 @property
515 515 def _activebookmark(self):
516 516 return self._bookmarks.active
517 517
518 518 # _phaserevs and _phasesets depend on changelog. what we need is to
519 519 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
520 520 # can't be easily expressed in filecache mechanism.
521 521 @storecache('phaseroots', '00changelog.i')
522 522 def _phasecache(self):
523 523 return phases.phasecache(self, self._phasedefaults)
524 524
525 525 @storecache('obsstore')
526 526 def obsstore(self):
527 # read default format for new obsstore.
528 # developer config: format.obsstore-version
529 defaultformat = self.ui.configint('format', 'obsstore-version', None)
530 # rely on obsstore class default when possible.
531 kwargs = {}
532 if defaultformat is not None:
533 kwargs['defaultformat'] = defaultformat
534 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
535 store = obsolete.obsstore(self.svfs, readonly=readonly,
536 **kwargs)
537 if store and readonly:
538 self.ui.warn(
539 _('obsolete feature not enabled but %i markers found!\n')
540 % len(list(store)))
541 return store
527 return obsolete.makestore(self.ui, self)
542 528
543 529 @storecache('00changelog.i')
544 530 def changelog(self):
545 531 return changelog.changelog(self.svfs,
546 532 trypending=txnutil.mayhavepending(self.root))
547 533
548 534 def _constructmanifest(self):
549 535 # This is a temporary function while we migrate from manifest to
550 536 # manifestlog. It allows bundlerepo and unionrepo to intercept the
551 537 # manifest creation.
552 538 return manifest.manifestrevlog(self.svfs)
553 539
554 540 @storecache('00manifest.i')
555 541 def manifestlog(self):
556 542 return manifest.manifestlog(self.svfs, self)
557 543
558 544 @repofilecache('dirstate')
559 545 def dirstate(self):
560 546 return dirstate.dirstate(self.vfs, self.ui, self.root,
561 547 self._dirstatevalidate)
562 548
563 549 def _dirstatevalidate(self, node):
564 550 try:
565 551 self.changelog.rev(node)
566 552 return node
567 553 except error.LookupError:
568 554 if not self._dirstatevalidatewarned:
569 555 self._dirstatevalidatewarned = True
570 556 self.ui.warn(_("warning: ignoring unknown"
571 557 " working parent %s!\n") % short(node))
572 558 return nullid
573 559
574 560 def __getitem__(self, changeid):
575 561 if changeid is None:
576 562 return context.workingctx(self)
577 563 if isinstance(changeid, slice):
578 564 # wdirrev isn't contiguous so the slice shouldn't include it
579 565 return [context.changectx(self, i)
580 566 for i in xrange(*changeid.indices(len(self)))
581 567 if i not in self.changelog.filteredrevs]
582 568 try:
583 569 return context.changectx(self, changeid)
584 570 except error.WdirUnsupported:
585 571 return context.workingctx(self)
586 572
587 573 def __contains__(self, changeid):
588 574 """True if the given changeid exists
589 575
590 576 error.LookupError is raised if an ambiguous node specified.
591 577 """
592 578 try:
593 579 self[changeid]
594 580 return True
595 581 except error.RepoLookupError:
596 582 return False
597 583
598 584 def __nonzero__(self):
599 585 return True
600 586
601 587 __bool__ = __nonzero__
602 588
603 589 def __len__(self):
604 590 return len(self.changelog)
605 591
606 592 def __iter__(self):
607 593 return iter(self.changelog)
608 594
609 595 def revs(self, expr, *args):
610 596 '''Find revisions matching a revset.
611 597
612 598 The revset is specified as a string ``expr`` that may contain
613 599 %-formatting to escape certain types. See ``revsetlang.formatspec``.
614 600
615 601 Revset aliases from the configuration are not expanded. To expand
616 602 user aliases, consider calling ``scmutil.revrange()`` or
617 603 ``repo.anyrevs([expr], user=True)``.
618 604
619 605 Returns a revset.abstractsmartset, which is a list-like interface
620 606 that contains integer revisions.
621 607 '''
622 608 expr = revsetlang.formatspec(expr, *args)
623 609 m = revset.match(None, expr)
624 610 return m(self)
625 611
626 612 def set(self, expr, *args):
627 613 '''Find revisions matching a revset and emit changectx instances.
628 614
629 615 This is a convenience wrapper around ``revs()`` that iterates the
630 616 result and is a generator of changectx instances.
631 617
632 618 Revset aliases from the configuration are not expanded. To expand
633 619 user aliases, consider calling ``scmutil.revrange()``.
634 620 '''
635 621 for r in self.revs(expr, *args):
636 622 yield self[r]
637 623
638 624 def anyrevs(self, specs, user=False):
639 625 '''Find revisions matching one of the given revsets.
640 626
641 627 Revset aliases from the configuration are not expanded by default. To
642 628 expand user aliases, specify ``user=True``.
643 629 '''
644 630 if user:
645 631 m = revset.matchany(self.ui, specs, repo=self)
646 632 else:
647 633 m = revset.matchany(None, specs)
648 634 return m(self)
649 635
650 636 def url(self):
651 637 return 'file:' + self.root
652 638
653 639 def hook(self, name, throw=False, **args):
654 640 """Call a hook, passing this repo instance.
655 641
656 642 This a convenience method to aid invoking hooks. Extensions likely
657 643 won't call this unless they have registered a custom hook or are
658 644 replacing code that is expected to call a hook.
659 645 """
660 646 return hook.hook(self.ui, self, name, throw, **args)
661 647
662 648 @filteredpropertycache
663 649 def _tagscache(self):
664 650 '''Returns a tagscache object that contains various tags related
665 651 caches.'''
666 652
667 653 # This simplifies its cache management by having one decorated
668 654 # function (this one) and the rest simply fetch things from it.
669 655 class tagscache(object):
670 656 def __init__(self):
671 657 # These two define the set of tags for this repository. tags
672 658 # maps tag name to node; tagtypes maps tag name to 'global' or
673 659 # 'local'. (Global tags are defined by .hgtags across all
674 660 # heads, and local tags are defined in .hg/localtags.)
675 661 # They constitute the in-memory cache of tags.
676 662 self.tags = self.tagtypes = None
677 663
678 664 self.nodetagscache = self.tagslist = None
679 665
680 666 cache = tagscache()
681 667 cache.tags, cache.tagtypes = self._findtags()
682 668
683 669 return cache
684 670
685 671 def tags(self):
686 672 '''return a mapping of tag to node'''
687 673 t = {}
688 674 if self.changelog.filteredrevs:
689 675 tags, tt = self._findtags()
690 676 else:
691 677 tags = self._tagscache.tags
692 678 for k, v in tags.iteritems():
693 679 try:
694 680 # ignore tags to unknown nodes
695 681 self.changelog.rev(v)
696 682 t[k] = v
697 683 except (error.LookupError, ValueError):
698 684 pass
699 685 return t
700 686
701 687 def _findtags(self):
702 688 '''Do the hard work of finding tags. Return a pair of dicts
703 689 (tags, tagtypes) where tags maps tag name to node, and tagtypes
704 690 maps tag name to a string like \'global\' or \'local\'.
705 691 Subclasses or extensions are free to add their own tags, but
706 692 should be aware that the returned dicts will be retained for the
707 693 duration of the localrepo object.'''
708 694
709 695 # XXX what tagtype should subclasses/extensions use? Currently
710 696 # mq and bookmarks add tags, but do not set the tagtype at all.
711 697 # Should each extension invent its own tag type? Should there
712 698 # be one tagtype for all such "virtual" tags? Or is the status
713 699 # quo fine?
714 700
715 701
716 702 # map tag name to (node, hist)
717 703 alltags = tagsmod.findglobaltags(self.ui, self)
718 704 # map tag name to tag type
719 705 tagtypes = dict((tag, 'global') for tag in alltags)
720 706
721 707 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
722 708
723 709 # Build the return dicts. Have to re-encode tag names because
724 710 # the tags module always uses UTF-8 (in order not to lose info
725 711 # writing to the cache), but the rest of Mercurial wants them in
726 712 # local encoding.
727 713 tags = {}
728 714 for (name, (node, hist)) in alltags.iteritems():
729 715 if node != nullid:
730 716 tags[encoding.tolocal(name)] = node
731 717 tags['tip'] = self.changelog.tip()
732 718 tagtypes = dict([(encoding.tolocal(name), value)
733 719 for (name, value) in tagtypes.iteritems()])
734 720 return (tags, tagtypes)
735 721
736 722 def tagtype(self, tagname):
737 723 '''
738 724 return the type of the given tag. result can be:
739 725
740 726 'local' : a local tag
741 727 'global' : a global tag
742 728 None : tag does not exist
743 729 '''
744 730
745 731 return self._tagscache.tagtypes.get(tagname)
746 732
747 733 def tagslist(self):
748 734 '''return a list of tags ordered by revision'''
749 735 if not self._tagscache.tagslist:
750 736 l = []
751 737 for t, n in self.tags().iteritems():
752 738 l.append((self.changelog.rev(n), t, n))
753 739 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
754 740
755 741 return self._tagscache.tagslist
756 742
757 743 def nodetags(self, node):
758 744 '''return the tags associated with a node'''
759 745 if not self._tagscache.nodetagscache:
760 746 nodetagscache = {}
761 747 for t, n in self._tagscache.tags.iteritems():
762 748 nodetagscache.setdefault(n, []).append(t)
763 749 for tags in nodetagscache.itervalues():
764 750 tags.sort()
765 751 self._tagscache.nodetagscache = nodetagscache
766 752 return self._tagscache.nodetagscache.get(node, [])
767 753
768 754 def nodebookmarks(self, node):
769 755 """return the list of bookmarks pointing to the specified node"""
770 756 marks = []
771 757 for bookmark, n in self._bookmarks.iteritems():
772 758 if n == node:
773 759 marks.append(bookmark)
774 760 return sorted(marks)
775 761
776 762 def branchmap(self):
777 763 '''returns a dictionary {branch: [branchheads]} with branchheads
778 764 ordered by increasing revision number'''
779 765 branchmap.updatecache(self)
780 766 return self._branchcaches[self.filtername]
781 767
782 768 @unfilteredmethod
783 769 def revbranchcache(self):
784 770 if not self._revbranchcache:
785 771 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
786 772 return self._revbranchcache
787 773
788 774 def branchtip(self, branch, ignoremissing=False):
789 775 '''return the tip node for a given branch
790 776
791 777 If ignoremissing is True, then this method will not raise an error.
792 778 This is helpful for callers that only expect None for a missing branch
793 779 (e.g. namespace).
794 780
795 781 '''
796 782 try:
797 783 return self.branchmap().branchtip(branch)
798 784 except KeyError:
799 785 if not ignoremissing:
800 786 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
801 787 else:
802 788 pass
803 789
804 790 def lookup(self, key):
805 791 return self[key].node()
806 792
807 793 def lookupbranch(self, key, remote=None):
808 794 repo = remote or self
809 795 if key in repo.branchmap():
810 796 return key
811 797
812 798 repo = (remote and remote.local()) and remote or self
813 799 return repo[key].branch()
814 800
815 801 def known(self, nodes):
816 802 cl = self.changelog
817 803 nm = cl.nodemap
818 804 filtered = cl.filteredrevs
819 805 result = []
820 806 for n in nodes:
821 807 r = nm.get(n)
822 808 resp = not (r is None or r in filtered)
823 809 result.append(resp)
824 810 return result
825 811
826 812 def local(self):
827 813 return self
828 814
829 815 def publishing(self):
830 816 # it's safe (and desirable) to trust the publish flag unconditionally
831 817 # so that we don't finalize changes shared between users via ssh or nfs
832 818 return self.ui.configbool('phases', 'publish', True, untrusted=True)
833 819
834 820 def cancopy(self):
835 821 # so statichttprepo's override of local() works
836 822 if not self.local():
837 823 return False
838 824 if not self.publishing():
839 825 return True
840 826 # if publishing we can't copy if there is filtered content
841 827 return not self.filtered('visible').changelog.filteredrevs
842 828
843 829 def shared(self):
844 830 '''the type of shared repository (None if not shared)'''
845 831 if self.sharedpath != self.path:
846 832 return 'store'
847 833 return None
848 834
849 835 def wjoin(self, f, *insidef):
850 836 return self.vfs.reljoin(self.root, f, *insidef)
851 837
852 838 def file(self, f):
853 839 if f[0] == '/':
854 840 f = f[1:]
855 841 return filelog.filelog(self.svfs, f)
856 842
857 843 def changectx(self, changeid):
858 844 return self[changeid]
859 845
860 846 def setparents(self, p1, p2=nullid):
861 847 with self.dirstate.parentchange():
862 848 copies = self.dirstate.setparents(p1, p2)
863 849 pctx = self[p1]
864 850 if copies:
865 851 # Adjust copy records, the dirstate cannot do it, it
866 852 # requires access to parents manifests. Preserve them
867 853 # only for entries added to first parent.
868 854 for f in copies:
869 855 if f not in pctx and copies[f] in pctx:
870 856 self.dirstate.copy(copies[f], f)
871 857 if p2 == nullid:
872 858 for f, s in sorted(self.dirstate.copies().items()):
873 859 if f not in pctx and s not in pctx:
874 860 self.dirstate.copy(None, f)
875 861
876 862 def filectx(self, path, changeid=None, fileid=None):
877 863 """changeid can be a changeset revision, node, or tag.
878 864 fileid can be a file revision or node."""
879 865 return context.filectx(self, path, changeid, fileid)
880 866
881 867 def getcwd(self):
882 868 return self.dirstate.getcwd()
883 869
884 870 def pathto(self, f, cwd=None):
885 871 return self.dirstate.pathto(f, cwd)
886 872
887 873 def _loadfilter(self, filter):
888 874 if filter not in self.filterpats:
889 875 l = []
890 876 for pat, cmd in self.ui.configitems(filter):
891 877 if cmd == '!':
892 878 continue
893 879 mf = matchmod.match(self.root, '', [pat])
894 880 fn = None
895 881 params = cmd
896 882 for name, filterfn in self._datafilters.iteritems():
897 883 if cmd.startswith(name):
898 884 fn = filterfn
899 885 params = cmd[len(name):].lstrip()
900 886 break
901 887 if not fn:
902 888 fn = lambda s, c, **kwargs: util.filter(s, c)
903 889 # Wrap old filters not supporting keyword arguments
904 890 if not inspect.getargspec(fn)[2]:
905 891 oldfn = fn
906 892 fn = lambda s, c, **kwargs: oldfn(s, c)
907 893 l.append((mf, fn, params))
908 894 self.filterpats[filter] = l
909 895 return self.filterpats[filter]
910 896
911 897 def _filter(self, filterpats, filename, data):
912 898 for mf, fn, cmd in filterpats:
913 899 if mf(filename):
914 900 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
915 901 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
916 902 break
917 903
918 904 return data
919 905
920 906 @unfilteredpropertycache
921 907 def _encodefilterpats(self):
922 908 return self._loadfilter('encode')
923 909
924 910 @unfilteredpropertycache
925 911 def _decodefilterpats(self):
926 912 return self._loadfilter('decode')
927 913
928 914 def adddatafilter(self, name, filter):
929 915 self._datafilters[name] = filter
930 916
931 917 def wread(self, filename):
932 918 if self.wvfs.islink(filename):
933 919 data = self.wvfs.readlink(filename)
934 920 else:
935 921 data = self.wvfs.read(filename)
936 922 return self._filter(self._encodefilterpats, filename, data)
937 923
938 924 def wwrite(self, filename, data, flags, backgroundclose=False):
939 925 """write ``data`` into ``filename`` in the working directory
940 926
941 927 This returns length of written (maybe decoded) data.
942 928 """
943 929 data = self._filter(self._decodefilterpats, filename, data)
944 930 if 'l' in flags:
945 931 self.wvfs.symlink(data, filename)
946 932 else:
947 933 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
948 934 if 'x' in flags:
949 935 self.wvfs.setflags(filename, False, True)
950 936 return len(data)
951 937
952 938 def wwritedata(self, filename, data):
953 939 return self._filter(self._decodefilterpats, filename, data)
954 940
955 941 def currenttransaction(self):
956 942 """return the current transaction or None if non exists"""
957 943 if self._transref:
958 944 tr = self._transref()
959 945 else:
960 946 tr = None
961 947
962 948 if tr and tr.running():
963 949 return tr
964 950 return None
965 951
966 952 def transaction(self, desc, report=None):
967 953 if (self.ui.configbool('devel', 'all-warnings')
968 954 or self.ui.configbool('devel', 'check-locks')):
969 955 if self._currentlock(self._lockref) is None:
970 956 raise error.ProgrammingError('transaction requires locking')
971 957 tr = self.currenttransaction()
972 958 if tr is not None:
973 959 return tr.nest()
974 960
975 961 # abort here if the journal already exists
976 962 if self.svfs.exists("journal"):
977 963 raise error.RepoError(
978 964 _("abandoned transaction found"),
979 965 hint=_("run 'hg recover' to clean up transaction"))
980 966
981 967 idbase = "%.40f#%f" % (random.random(), time.time())
982 968 ha = hex(hashlib.sha1(idbase).digest())
983 969 txnid = 'TXN:' + ha
984 970 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
985 971
986 972 self._writejournal(desc)
987 973 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
988 974 if report:
989 975 rp = report
990 976 else:
991 977 rp = self.ui.warn
992 978 vfsmap = {'plain': self.vfs} # root of .hg/
993 979 # we must avoid cyclic reference between repo and transaction.
994 980 reporef = weakref.ref(self)
995 981 # Code to track tag movement
996 982 #
997 983 # Since tags are all handled as file content, it is actually quite hard
998 984 # to track these movement from a code perspective. So we fallback to a
999 985 # tracking at the repository level. One could envision to track changes
1000 986 # to the '.hgtags' file through changegroup apply but that fails to
1001 987 # cope with case where transaction expose new heads without changegroup
1002 988 # being involved (eg: phase movement).
1003 989 #
1004 990 # For now, We gate the feature behind a flag since this likely comes
1005 991 # with performance impacts. The current code run more often than needed
1006 992 # and do not use caches as much as it could. The current focus is on
1007 993 # the behavior of the feature so we disable it by default. The flag
1008 994 # will be removed when we are happy with the performance impact.
1009 995 #
1010 996 # Once this feature is no longer experimental move the following
1011 997 # documentation to the appropriate help section:
1012 998 #
1013 999 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1014 1000 # tags (new or changed or deleted tags). In addition the details of
1015 1001 # these changes are made available in a file at:
1016 1002 # ``REPOROOT/.hg/changes/tags.changes``.
1017 1003 # Make sure you check for HG_TAG_MOVED before reading that file as it
1018 1004 # might exist from a previous transaction even if no tag were touched
1019 1005 # in this one. Changes are recorded in a line base format::
1020 1006 #
1021 1007 # <action> <hex-node> <tag-name>\n
1022 1008 #
1023 1009 # Actions are defined as follow:
1024 1010 # "-R": tag is removed,
1025 1011 # "+A": tag is added,
1026 1012 # "-M": tag is moved (old value),
1027 1013 # "+M": tag is moved (new value),
1028 1014 tracktags = lambda x: None
1029 1015 # experimental config: experimental.hook-track-tags
1030 1016 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1031 1017 False)
1032 1018 if desc != 'strip' and shouldtracktags:
1033 1019 oldheads = self.changelog.headrevs()
1034 1020 def tracktags(tr2):
1035 1021 repo = reporef()
1036 1022 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1037 1023 newheads = repo.changelog.headrevs()
1038 1024 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1039 1025 # notes: we compare lists here.
1040 1026 # As we do it only once buiding set would not be cheaper
1041 1027 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1042 1028 if changes:
1043 1029 tr2.hookargs['tag_moved'] = '1'
1044 1030 with repo.vfs('changes/tags.changes', 'w',
1045 1031 atomictemp=True) as changesfile:
1046 1032 # note: we do not register the file to the transaction
1047 1033 # because we needs it to still exist on the transaction
1048 1034 # is close (for txnclose hooks)
1049 1035 tagsmod.writediff(changesfile, changes)
1050 1036 def validate(tr2):
1051 1037 """will run pre-closing hooks"""
1052 1038 # XXX the transaction API is a bit lacking here so we take a hacky
1053 1039 # path for now
1054 1040 #
1055 1041 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1056 1042 # dict is copied before these run. In addition we needs the data
1057 1043 # available to in memory hooks too.
1058 1044 #
1059 1045 # Moreover, we also need to make sure this runs before txnclose
1060 1046 # hooks and there is no "pending" mechanism that would execute
1061 1047 # logic only if hooks are about to run.
1062 1048 #
1063 1049 # Fixing this limitation of the transaction is also needed to track
1064 1050 # other families of changes (bookmarks, phases, obsolescence).
1065 1051 #
1066 1052 # This will have to be fixed before we remove the experimental
1067 1053 # gating.
1068 1054 tracktags(tr2)
1069 1055 reporef().hook('pretxnclose', throw=True,
1070 1056 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1071 1057 def releasefn(tr, success):
1072 1058 repo = reporef()
1073 1059 if success:
1074 1060 # this should be explicitly invoked here, because
1075 1061 # in-memory changes aren't written out at closing
1076 1062 # transaction, if tr.addfilegenerator (via
1077 1063 # dirstate.write or so) isn't invoked while
1078 1064 # transaction running
1079 1065 repo.dirstate.write(None)
1080 1066 else:
1081 1067 # discard all changes (including ones already written
1082 1068 # out) in this transaction
1083 1069 repo.dirstate.restorebackup(None, prefix='journal.')
1084 1070
1085 1071 repo.invalidate(clearfilecache=True)
1086 1072
1087 1073 tr = transaction.transaction(rp, self.svfs, vfsmap,
1088 1074 "journal",
1089 1075 "undo",
1090 1076 aftertrans(renames),
1091 1077 self.store.createmode,
1092 1078 validator=validate,
1093 1079 releasefn=releasefn)
1094 1080 tr.changes['revs'] = set()
1095 1081
1096 1082 tr.hookargs['txnid'] = txnid
1097 1083 # note: writing the fncache only during finalize mean that the file is
1098 1084 # outdated when running hooks. As fncache is used for streaming clone,
1099 1085 # this is not expected to break anything that happen during the hooks.
1100 1086 tr.addfinalize('flush-fncache', self.store.write)
1101 1087 def txnclosehook(tr2):
1102 1088 """To be run if transaction is successful, will schedule a hook run
1103 1089 """
1104 1090 # Don't reference tr2 in hook() so we don't hold a reference.
1105 1091 # This reduces memory consumption when there are multiple
1106 1092 # transactions per lock. This can likely go away if issue5045
1107 1093 # fixes the function accumulation.
1108 1094 hookargs = tr2.hookargs
1109 1095
1110 1096 def hook():
1111 1097 reporef().hook('txnclose', throw=False, txnname=desc,
1112 1098 **pycompat.strkwargs(hookargs))
1113 1099 reporef()._afterlock(hook)
1114 1100 tr.addfinalize('txnclose-hook', txnclosehook)
1115 1101 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1116 1102 def txnaborthook(tr2):
1117 1103 """To be run if transaction is aborted
1118 1104 """
1119 1105 reporef().hook('txnabort', throw=False, txnname=desc,
1120 1106 **tr2.hookargs)
1121 1107 tr.addabort('txnabort-hook', txnaborthook)
1122 1108 # avoid eager cache invalidation. in-memory data should be identical
1123 1109 # to stored data if transaction has no error.
1124 1110 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1125 1111 self._transref = weakref.ref(tr)
1126 1112 return tr
1127 1113
1128 1114 def _journalfiles(self):
1129 1115 return ((self.svfs, 'journal'),
1130 1116 (self.vfs, 'journal.dirstate'),
1131 1117 (self.vfs, 'journal.branch'),
1132 1118 (self.vfs, 'journal.desc'),
1133 1119 (self.vfs, 'journal.bookmarks'),
1134 1120 (self.svfs, 'journal.phaseroots'))
1135 1121
1136 1122 def undofiles(self):
1137 1123 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1138 1124
1139 1125 @unfilteredmethod
1140 1126 def _writejournal(self, desc):
1141 1127 self.dirstate.savebackup(None, prefix='journal.')
1142 1128 self.vfs.write("journal.branch",
1143 1129 encoding.fromlocal(self.dirstate.branch()))
1144 1130 self.vfs.write("journal.desc",
1145 1131 "%d\n%s\n" % (len(self), desc))
1146 1132 self.vfs.write("journal.bookmarks",
1147 1133 self.vfs.tryread("bookmarks"))
1148 1134 self.svfs.write("journal.phaseroots",
1149 1135 self.svfs.tryread("phaseroots"))
1150 1136
1151 1137 def recover(self):
1152 1138 with self.lock():
1153 1139 if self.svfs.exists("journal"):
1154 1140 self.ui.status(_("rolling back interrupted transaction\n"))
1155 1141 vfsmap = {'': self.svfs,
1156 1142 'plain': self.vfs,}
1157 1143 transaction.rollback(self.svfs, vfsmap, "journal",
1158 1144 self.ui.warn)
1159 1145 self.invalidate()
1160 1146 return True
1161 1147 else:
1162 1148 self.ui.warn(_("no interrupted transaction available\n"))
1163 1149 return False
1164 1150
1165 1151 def rollback(self, dryrun=False, force=False):
1166 1152 wlock = lock = dsguard = None
1167 1153 try:
1168 1154 wlock = self.wlock()
1169 1155 lock = self.lock()
1170 1156 if self.svfs.exists("undo"):
1171 1157 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1172 1158
1173 1159 return self._rollback(dryrun, force, dsguard)
1174 1160 else:
1175 1161 self.ui.warn(_("no rollback information available\n"))
1176 1162 return 1
1177 1163 finally:
1178 1164 release(dsguard, lock, wlock)
1179 1165
1180 1166 @unfilteredmethod # Until we get smarter cache management
1181 1167 def _rollback(self, dryrun, force, dsguard):
1182 1168 ui = self.ui
1183 1169 try:
1184 1170 args = self.vfs.read('undo.desc').splitlines()
1185 1171 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1186 1172 if len(args) >= 3:
1187 1173 detail = args[2]
1188 1174 oldtip = oldlen - 1
1189 1175
1190 1176 if detail and ui.verbose:
1191 1177 msg = (_('repository tip rolled back to revision %s'
1192 1178 ' (undo %s: %s)\n')
1193 1179 % (oldtip, desc, detail))
1194 1180 else:
1195 1181 msg = (_('repository tip rolled back to revision %s'
1196 1182 ' (undo %s)\n')
1197 1183 % (oldtip, desc))
1198 1184 except IOError:
1199 1185 msg = _('rolling back unknown transaction\n')
1200 1186 desc = None
1201 1187
1202 1188 if not force and self['.'] != self['tip'] and desc == 'commit':
1203 1189 raise error.Abort(
1204 1190 _('rollback of last commit while not checked out '
1205 1191 'may lose data'), hint=_('use -f to force'))
1206 1192
1207 1193 ui.status(msg)
1208 1194 if dryrun:
1209 1195 return 0
1210 1196
1211 1197 parents = self.dirstate.parents()
1212 1198 self.destroying()
1213 1199 vfsmap = {'plain': self.vfs, '': self.svfs}
1214 1200 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1215 1201 if self.vfs.exists('undo.bookmarks'):
1216 1202 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1217 1203 if self.svfs.exists('undo.phaseroots'):
1218 1204 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1219 1205 self.invalidate()
1220 1206
1221 1207 parentgone = (parents[0] not in self.changelog.nodemap or
1222 1208 parents[1] not in self.changelog.nodemap)
1223 1209 if parentgone:
1224 1210 # prevent dirstateguard from overwriting already restored one
1225 1211 dsguard.close()
1226 1212
1227 1213 self.dirstate.restorebackup(None, prefix='undo.')
1228 1214 try:
1229 1215 branch = self.vfs.read('undo.branch')
1230 1216 self.dirstate.setbranch(encoding.tolocal(branch))
1231 1217 except IOError:
1232 1218 ui.warn(_('named branch could not be reset: '
1233 1219 'current branch is still \'%s\'\n')
1234 1220 % self.dirstate.branch())
1235 1221
1236 1222 parents = tuple([p.rev() for p in self[None].parents()])
1237 1223 if len(parents) > 1:
1238 1224 ui.status(_('working directory now based on '
1239 1225 'revisions %d and %d\n') % parents)
1240 1226 else:
1241 1227 ui.status(_('working directory now based on '
1242 1228 'revision %d\n') % parents)
1243 1229 mergemod.mergestate.clean(self, self['.'].node())
1244 1230
1245 1231 # TODO: if we know which new heads may result from this rollback, pass
1246 1232 # them to destroy(), which will prevent the branchhead cache from being
1247 1233 # invalidated.
1248 1234 self.destroyed()
1249 1235 return 0
1250 1236
1251 1237 def _buildcacheupdater(self, newtransaction):
1252 1238 """called during transaction to build the callback updating cache
1253 1239
1254 1240 Lives on the repository to help extension who might want to augment
1255 1241 this logic. For this purpose, the created transaction is passed to the
1256 1242 method.
1257 1243 """
1258 1244 # we must avoid cyclic reference between repo and transaction.
1259 1245 reporef = weakref.ref(self)
1260 1246 def updater(tr):
1261 1247 repo = reporef()
1262 1248 repo.updatecaches(tr)
1263 1249 return updater
1264 1250
1265 1251 @unfilteredmethod
1266 1252 def updatecaches(self, tr=None):
1267 1253 """warm appropriate caches
1268 1254
1269 1255 If this function is called after a transaction closed. The transaction
1270 1256 will be available in the 'tr' argument. This can be used to selectively
1271 1257 update caches relevant to the changes in that transaction.
1272 1258 """
1273 1259 if tr is not None and tr.hookargs.get('source') == 'strip':
1274 1260 # During strip, many caches are invalid but
1275 1261 # later call to `destroyed` will refresh them.
1276 1262 return
1277 1263
1278 1264 if tr is None or tr.changes['revs']:
1279 1265 # updating the unfiltered branchmap should refresh all the others,
1280 1266 self.ui.debug('updating the branch cache\n')
1281 1267 branchmap.updatecache(self.filtered('served'))
1282 1268
1283 1269 def invalidatecaches(self):
1284 1270
1285 1271 if '_tagscache' in vars(self):
1286 1272 # can't use delattr on proxy
1287 1273 del self.__dict__['_tagscache']
1288 1274
1289 1275 self.unfiltered()._branchcaches.clear()
1290 1276 self.invalidatevolatilesets()
1291 1277
1292 1278 def invalidatevolatilesets(self):
1293 1279 self.filteredrevcache.clear()
1294 1280 obsolete.clearobscaches(self)
1295 1281
1296 1282 def invalidatedirstate(self):
1297 1283 '''Invalidates the dirstate, causing the next call to dirstate
1298 1284 to check if it was modified since the last time it was read,
1299 1285 rereading it if it has.
1300 1286
1301 1287 This is different to dirstate.invalidate() that it doesn't always
1302 1288 rereads the dirstate. Use dirstate.invalidate() if you want to
1303 1289 explicitly read the dirstate again (i.e. restoring it to a previous
1304 1290 known good state).'''
1305 1291 if hasunfilteredcache(self, 'dirstate'):
1306 1292 for k in self.dirstate._filecache:
1307 1293 try:
1308 1294 delattr(self.dirstate, k)
1309 1295 except AttributeError:
1310 1296 pass
1311 1297 delattr(self.unfiltered(), 'dirstate')
1312 1298
1313 1299 def invalidate(self, clearfilecache=False):
1314 1300 '''Invalidates both store and non-store parts other than dirstate
1315 1301
1316 1302 If a transaction is running, invalidation of store is omitted,
1317 1303 because discarding in-memory changes might cause inconsistency
1318 1304 (e.g. incomplete fncache causes unintentional failure, but
1319 1305 redundant one doesn't).
1320 1306 '''
1321 1307 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1322 1308 for k in list(self._filecache.keys()):
1323 1309 # dirstate is invalidated separately in invalidatedirstate()
1324 1310 if k == 'dirstate':
1325 1311 continue
1326 1312
1327 1313 if clearfilecache:
1328 1314 del self._filecache[k]
1329 1315 try:
1330 1316 delattr(unfiltered, k)
1331 1317 except AttributeError:
1332 1318 pass
1333 1319 self.invalidatecaches()
1334 1320 if not self.currenttransaction():
1335 1321 # TODO: Changing contents of store outside transaction
1336 1322 # causes inconsistency. We should make in-memory store
1337 1323 # changes detectable, and abort if changed.
1338 1324 self.store.invalidatecaches()
1339 1325
1340 1326 def invalidateall(self):
1341 1327 '''Fully invalidates both store and non-store parts, causing the
1342 1328 subsequent operation to reread any outside changes.'''
1343 1329 # extension should hook this to invalidate its caches
1344 1330 self.invalidate()
1345 1331 self.invalidatedirstate()
1346 1332
1347 1333 @unfilteredmethod
1348 1334 def _refreshfilecachestats(self, tr):
1349 1335 """Reload stats of cached files so that they are flagged as valid"""
1350 1336 for k, ce in self._filecache.items():
1351 1337 if k == 'dirstate' or k not in self.__dict__:
1352 1338 continue
1353 1339 ce.refresh()
1354 1340
1355 1341 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1356 1342 inheritchecker=None, parentenvvar=None):
1357 1343 parentlock = None
1358 1344 # the contents of parentenvvar are used by the underlying lock to
1359 1345 # determine whether it can be inherited
1360 1346 if parentenvvar is not None:
1361 1347 parentlock = encoding.environ.get(parentenvvar)
1362 1348 try:
1363 1349 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1364 1350 acquirefn=acquirefn, desc=desc,
1365 1351 inheritchecker=inheritchecker,
1366 1352 parentlock=parentlock)
1367 1353 except error.LockHeld as inst:
1368 1354 if not wait:
1369 1355 raise
1370 1356 # show more details for new-style locks
1371 1357 if ':' in inst.locker:
1372 1358 host, pid = inst.locker.split(":", 1)
1373 1359 self.ui.warn(
1374 1360 _("waiting for lock on %s held by process %r "
1375 1361 "on host %r\n") % (desc, pid, host))
1376 1362 else:
1377 1363 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1378 1364 (desc, inst.locker))
1379 1365 # default to 600 seconds timeout
1380 1366 l = lockmod.lock(vfs, lockname,
1381 1367 int(self.ui.config("ui", "timeout", "600")),
1382 1368 releasefn=releasefn, acquirefn=acquirefn,
1383 1369 desc=desc)
1384 1370 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1385 1371 return l
1386 1372
1387 1373 def _afterlock(self, callback):
1388 1374 """add a callback to be run when the repository is fully unlocked
1389 1375
1390 1376 The callback will be executed when the outermost lock is released
1391 1377 (with wlock being higher level than 'lock')."""
1392 1378 for ref in (self._wlockref, self._lockref):
1393 1379 l = ref and ref()
1394 1380 if l and l.held:
1395 1381 l.postrelease.append(callback)
1396 1382 break
1397 1383 else: # no lock have been found.
1398 1384 callback()
1399 1385
1400 1386 def lock(self, wait=True):
1401 1387 '''Lock the repository store (.hg/store) and return a weak reference
1402 1388 to the lock. Use this before modifying the store (e.g. committing or
1403 1389 stripping). If you are opening a transaction, get a lock as well.)
1404 1390
1405 1391 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1406 1392 'wlock' first to avoid a dead-lock hazard.'''
1407 1393 l = self._currentlock(self._lockref)
1408 1394 if l is not None:
1409 1395 l.lock()
1410 1396 return l
1411 1397
1412 1398 l = self._lock(self.svfs, "lock", wait, None,
1413 1399 self.invalidate, _('repository %s') % self.origroot)
1414 1400 self._lockref = weakref.ref(l)
1415 1401 return l
1416 1402
1417 1403 def _wlockchecktransaction(self):
1418 1404 if self.currenttransaction() is not None:
1419 1405 raise error.LockInheritanceContractViolation(
1420 1406 'wlock cannot be inherited in the middle of a transaction')
1421 1407
1422 1408 def wlock(self, wait=True):
1423 1409 '''Lock the non-store parts of the repository (everything under
1424 1410 .hg except .hg/store) and return a weak reference to the lock.
1425 1411
1426 1412 Use this before modifying files in .hg.
1427 1413
1428 1414 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1429 1415 'wlock' first to avoid a dead-lock hazard.'''
1430 1416 l = self._wlockref and self._wlockref()
1431 1417 if l is not None and l.held:
1432 1418 l.lock()
1433 1419 return l
1434 1420
1435 1421 # We do not need to check for non-waiting lock acquisition. Such
1436 1422 # acquisition would not cause dead-lock as they would just fail.
1437 1423 if wait and (self.ui.configbool('devel', 'all-warnings')
1438 1424 or self.ui.configbool('devel', 'check-locks')):
1439 1425 if self._currentlock(self._lockref) is not None:
1440 1426 self.ui.develwarn('"wlock" acquired after "lock"')
1441 1427
1442 1428 def unlock():
1443 1429 if self.dirstate.pendingparentchange():
1444 1430 self.dirstate.invalidate()
1445 1431 else:
1446 1432 self.dirstate.write(None)
1447 1433
1448 1434 self._filecache['dirstate'].refresh()
1449 1435
1450 1436 l = self._lock(self.vfs, "wlock", wait, unlock,
1451 1437 self.invalidatedirstate, _('working directory of %s') %
1452 1438 self.origroot,
1453 1439 inheritchecker=self._wlockchecktransaction,
1454 1440 parentenvvar='HG_WLOCK_LOCKER')
1455 1441 self._wlockref = weakref.ref(l)
1456 1442 return l
1457 1443
1458 1444 def _currentlock(self, lockref):
1459 1445 """Returns the lock if it's held, or None if it's not."""
1460 1446 if lockref is None:
1461 1447 return None
1462 1448 l = lockref()
1463 1449 if l is None or not l.held:
1464 1450 return None
1465 1451 return l
1466 1452
1467 1453 def currentwlock(self):
1468 1454 """Returns the wlock if it's held, or None if it's not."""
1469 1455 return self._currentlock(self._wlockref)
1470 1456
1471 1457 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1472 1458 """
1473 1459 commit an individual file as part of a larger transaction
1474 1460 """
1475 1461
1476 1462 fname = fctx.path()
1477 1463 fparent1 = manifest1.get(fname, nullid)
1478 1464 fparent2 = manifest2.get(fname, nullid)
1479 1465 if isinstance(fctx, context.filectx):
1480 1466 node = fctx.filenode()
1481 1467 if node in [fparent1, fparent2]:
1482 1468 self.ui.debug('reusing %s filelog entry\n' % fname)
1483 1469 if manifest1.flags(fname) != fctx.flags():
1484 1470 changelist.append(fname)
1485 1471 return node
1486 1472
1487 1473 flog = self.file(fname)
1488 1474 meta = {}
1489 1475 copy = fctx.renamed()
1490 1476 if copy and copy[0] != fname:
1491 1477 # Mark the new revision of this file as a copy of another
1492 1478 # file. This copy data will effectively act as a parent
1493 1479 # of this new revision. If this is a merge, the first
1494 1480 # parent will be the nullid (meaning "look up the copy data")
1495 1481 # and the second one will be the other parent. For example:
1496 1482 #
1497 1483 # 0 --- 1 --- 3 rev1 changes file foo
1498 1484 # \ / rev2 renames foo to bar and changes it
1499 1485 # \- 2 -/ rev3 should have bar with all changes and
1500 1486 # should record that bar descends from
1501 1487 # bar in rev2 and foo in rev1
1502 1488 #
1503 1489 # this allows this merge to succeed:
1504 1490 #
1505 1491 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1506 1492 # \ / merging rev3 and rev4 should use bar@rev2
1507 1493 # \- 2 --- 4 as the merge base
1508 1494 #
1509 1495
1510 1496 cfname = copy[0]
1511 1497 crev = manifest1.get(cfname)
1512 1498 newfparent = fparent2
1513 1499
1514 1500 if manifest2: # branch merge
1515 1501 if fparent2 == nullid or crev is None: # copied on remote side
1516 1502 if cfname in manifest2:
1517 1503 crev = manifest2[cfname]
1518 1504 newfparent = fparent1
1519 1505
1520 1506 # Here, we used to search backwards through history to try to find
1521 1507 # where the file copy came from if the source of a copy was not in
1522 1508 # the parent directory. However, this doesn't actually make sense to
1523 1509 # do (what does a copy from something not in your working copy even
1524 1510 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1525 1511 # the user that copy information was dropped, so if they didn't
1526 1512 # expect this outcome it can be fixed, but this is the correct
1527 1513 # behavior in this circumstance.
1528 1514
1529 1515 if crev:
1530 1516 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1531 1517 meta["copy"] = cfname
1532 1518 meta["copyrev"] = hex(crev)
1533 1519 fparent1, fparent2 = nullid, newfparent
1534 1520 else:
1535 1521 self.ui.warn(_("warning: can't find ancestor for '%s' "
1536 1522 "copied from '%s'!\n") % (fname, cfname))
1537 1523
1538 1524 elif fparent1 == nullid:
1539 1525 fparent1, fparent2 = fparent2, nullid
1540 1526 elif fparent2 != nullid:
1541 1527 # is one parent an ancestor of the other?
1542 1528 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1543 1529 if fparent1 in fparentancestors:
1544 1530 fparent1, fparent2 = fparent2, nullid
1545 1531 elif fparent2 in fparentancestors:
1546 1532 fparent2 = nullid
1547 1533
1548 1534 # is the file changed?
1549 1535 text = fctx.data()
1550 1536 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1551 1537 changelist.append(fname)
1552 1538 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1553 1539 # are just the flags changed during merge?
1554 1540 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1555 1541 changelist.append(fname)
1556 1542
1557 1543 return fparent1
1558 1544
1559 1545 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1560 1546 """check for commit arguments that aren't committable"""
1561 1547 if match.isexact() or match.prefix():
1562 1548 matched = set(status.modified + status.added + status.removed)
1563 1549
1564 1550 for f in match.files():
1565 1551 f = self.dirstate.normalize(f)
1566 1552 if f == '.' or f in matched or f in wctx.substate:
1567 1553 continue
1568 1554 if f in status.deleted:
1569 1555 fail(f, _('file not found!'))
1570 1556 if f in vdirs: # visited directory
1571 1557 d = f + '/'
1572 1558 for mf in matched:
1573 1559 if mf.startswith(d):
1574 1560 break
1575 1561 else:
1576 1562 fail(f, _("no match under directory!"))
1577 1563 elif f not in self.dirstate:
1578 1564 fail(f, _("file not tracked!"))
1579 1565
1580 1566 @unfilteredmethod
1581 1567 def commit(self, text="", user=None, date=None, match=None, force=False,
1582 1568 editor=False, extra=None):
1583 1569 """Add a new revision to current repository.
1584 1570
1585 1571 Revision information is gathered from the working directory,
1586 1572 match can be used to filter the committed files. If editor is
1587 1573 supplied, it is called to get a commit message.
1588 1574 """
1589 1575 if extra is None:
1590 1576 extra = {}
1591 1577
1592 1578 def fail(f, msg):
1593 1579 raise error.Abort('%s: %s' % (f, msg))
1594 1580
1595 1581 if not match:
1596 1582 match = matchmod.always(self.root, '')
1597 1583
1598 1584 if not force:
1599 1585 vdirs = []
1600 1586 match.explicitdir = vdirs.append
1601 1587 match.bad = fail
1602 1588
1603 1589 wlock = lock = tr = None
1604 1590 try:
1605 1591 wlock = self.wlock()
1606 1592 lock = self.lock() # for recent changelog (see issue4368)
1607 1593
1608 1594 wctx = self[None]
1609 1595 merge = len(wctx.parents()) > 1
1610 1596
1611 1597 if not force and merge and not match.always():
1612 1598 raise error.Abort(_('cannot partially commit a merge '
1613 1599 '(do not specify files or patterns)'))
1614 1600
1615 1601 status = self.status(match=match, clean=force)
1616 1602 if force:
1617 1603 status.modified.extend(status.clean) # mq may commit clean files
1618 1604
1619 1605 # check subrepos
1620 1606 subs = []
1621 1607 commitsubs = set()
1622 1608 newstate = wctx.substate.copy()
1623 1609 # only manage subrepos and .hgsubstate if .hgsub is present
1624 1610 if '.hgsub' in wctx:
1625 1611 # we'll decide whether to track this ourselves, thanks
1626 1612 for c in status.modified, status.added, status.removed:
1627 1613 if '.hgsubstate' in c:
1628 1614 c.remove('.hgsubstate')
1629 1615
1630 1616 # compare current state to last committed state
1631 1617 # build new substate based on last committed state
1632 1618 oldstate = wctx.p1().substate
1633 1619 for s in sorted(newstate.keys()):
1634 1620 if not match(s):
1635 1621 # ignore working copy, use old state if present
1636 1622 if s in oldstate:
1637 1623 newstate[s] = oldstate[s]
1638 1624 continue
1639 1625 if not force:
1640 1626 raise error.Abort(
1641 1627 _("commit with new subrepo %s excluded") % s)
1642 1628 dirtyreason = wctx.sub(s).dirtyreason(True)
1643 1629 if dirtyreason:
1644 1630 if not self.ui.configbool('ui', 'commitsubrepos'):
1645 1631 raise error.Abort(dirtyreason,
1646 1632 hint=_("use --subrepos for recursive commit"))
1647 1633 subs.append(s)
1648 1634 commitsubs.add(s)
1649 1635 else:
1650 1636 bs = wctx.sub(s).basestate()
1651 1637 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1652 1638 if oldstate.get(s, (None, None, None))[1] != bs:
1653 1639 subs.append(s)
1654 1640
1655 1641 # check for removed subrepos
1656 1642 for p in wctx.parents():
1657 1643 r = [s for s in p.substate if s not in newstate]
1658 1644 subs += [s for s in r if match(s)]
1659 1645 if subs:
1660 1646 if (not match('.hgsub') and
1661 1647 '.hgsub' in (wctx.modified() + wctx.added())):
1662 1648 raise error.Abort(
1663 1649 _("can't commit subrepos without .hgsub"))
1664 1650 status.modified.insert(0, '.hgsubstate')
1665 1651
1666 1652 elif '.hgsub' in status.removed:
1667 1653 # clean up .hgsubstate when .hgsub is removed
1668 1654 if ('.hgsubstate' in wctx and
1669 1655 '.hgsubstate' not in (status.modified + status.added +
1670 1656 status.removed)):
1671 1657 status.removed.insert(0, '.hgsubstate')
1672 1658
1673 1659 # make sure all explicit patterns are matched
1674 1660 if not force:
1675 1661 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1676 1662
1677 1663 cctx = context.workingcommitctx(self, status,
1678 1664 text, user, date, extra)
1679 1665
1680 1666 # internal config: ui.allowemptycommit
1681 1667 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1682 1668 or extra.get('close') or merge or cctx.files()
1683 1669 or self.ui.configbool('ui', 'allowemptycommit'))
1684 1670 if not allowemptycommit:
1685 1671 return None
1686 1672
1687 1673 if merge and cctx.deleted():
1688 1674 raise error.Abort(_("cannot commit merge with missing files"))
1689 1675
1690 1676 ms = mergemod.mergestate.read(self)
1691 1677 mergeutil.checkunresolved(ms)
1692 1678
1693 1679 if editor:
1694 1680 cctx._text = editor(self, cctx, subs)
1695 1681 edited = (text != cctx._text)
1696 1682
1697 1683 # Save commit message in case this transaction gets rolled back
1698 1684 # (e.g. by a pretxncommit hook). Leave the content alone on
1699 1685 # the assumption that the user will use the same editor again.
1700 1686 msgfn = self.savecommitmessage(cctx._text)
1701 1687
1702 1688 # commit subs and write new state
1703 1689 if subs:
1704 1690 for s in sorted(commitsubs):
1705 1691 sub = wctx.sub(s)
1706 1692 self.ui.status(_('committing subrepository %s\n') %
1707 1693 subrepo.subrelpath(sub))
1708 1694 sr = sub.commit(cctx._text, user, date)
1709 1695 newstate[s] = (newstate[s][0], sr)
1710 1696 subrepo.writestate(self, newstate)
1711 1697
1712 1698 p1, p2 = self.dirstate.parents()
1713 1699 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1714 1700 try:
1715 1701 self.hook("precommit", throw=True, parent1=hookp1,
1716 1702 parent2=hookp2)
1717 1703 tr = self.transaction('commit')
1718 1704 ret = self.commitctx(cctx, True)
1719 1705 except: # re-raises
1720 1706 if edited:
1721 1707 self.ui.write(
1722 1708 _('note: commit message saved in %s\n') % msgfn)
1723 1709 raise
1724 1710 # update bookmarks, dirstate and mergestate
1725 1711 bookmarks.update(self, [p1, p2], ret)
1726 1712 cctx.markcommitted(ret)
1727 1713 ms.reset()
1728 1714 tr.close()
1729 1715
1730 1716 finally:
1731 1717 lockmod.release(tr, lock, wlock)
1732 1718
1733 1719 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1734 1720 # hack for command that use a temporary commit (eg: histedit)
1735 1721 # temporary commit got stripped before hook release
1736 1722 if self.changelog.hasnode(ret):
1737 1723 self.hook("commit", node=node, parent1=parent1,
1738 1724 parent2=parent2)
1739 1725 self._afterlock(commithook)
1740 1726 return ret
1741 1727
1742 1728 @unfilteredmethod
1743 1729 def commitctx(self, ctx, error=False):
1744 1730 """Add a new revision to current repository.
1745 1731 Revision information is passed via the context argument.
1746 1732 """
1747 1733
1748 1734 tr = None
1749 1735 p1, p2 = ctx.p1(), ctx.p2()
1750 1736 user = ctx.user()
1751 1737
1752 1738 lock = self.lock()
1753 1739 try:
1754 1740 tr = self.transaction("commit")
1755 1741 trp = weakref.proxy(tr)
1756 1742
1757 1743 if ctx.manifestnode():
1758 1744 # reuse an existing manifest revision
1759 1745 mn = ctx.manifestnode()
1760 1746 files = ctx.files()
1761 1747 elif ctx.files():
1762 1748 m1ctx = p1.manifestctx()
1763 1749 m2ctx = p2.manifestctx()
1764 1750 mctx = m1ctx.copy()
1765 1751
1766 1752 m = mctx.read()
1767 1753 m1 = m1ctx.read()
1768 1754 m2 = m2ctx.read()
1769 1755
1770 1756 # check in files
1771 1757 added = []
1772 1758 changed = []
1773 1759 removed = list(ctx.removed())
1774 1760 linkrev = len(self)
1775 1761 self.ui.note(_("committing files:\n"))
1776 1762 for f in sorted(ctx.modified() + ctx.added()):
1777 1763 self.ui.note(f + "\n")
1778 1764 try:
1779 1765 fctx = ctx[f]
1780 1766 if fctx is None:
1781 1767 removed.append(f)
1782 1768 else:
1783 1769 added.append(f)
1784 1770 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1785 1771 trp, changed)
1786 1772 m.setflag(f, fctx.flags())
1787 1773 except OSError as inst:
1788 1774 self.ui.warn(_("trouble committing %s!\n") % f)
1789 1775 raise
1790 1776 except IOError as inst:
1791 1777 errcode = getattr(inst, 'errno', errno.ENOENT)
1792 1778 if error or errcode and errcode != errno.ENOENT:
1793 1779 self.ui.warn(_("trouble committing %s!\n") % f)
1794 1780 raise
1795 1781
1796 1782 # update manifest
1797 1783 self.ui.note(_("committing manifest\n"))
1798 1784 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1799 1785 drop = [f for f in removed if f in m]
1800 1786 for f in drop:
1801 1787 del m[f]
1802 1788 mn = mctx.write(trp, linkrev,
1803 1789 p1.manifestnode(), p2.manifestnode(),
1804 1790 added, drop)
1805 1791 files = changed + removed
1806 1792 else:
1807 1793 mn = p1.manifestnode()
1808 1794 files = []
1809 1795
1810 1796 # update changelog
1811 1797 self.ui.note(_("committing changelog\n"))
1812 1798 self.changelog.delayupdate(tr)
1813 1799 n = self.changelog.add(mn, files, ctx.description(),
1814 1800 trp, p1.node(), p2.node(),
1815 1801 user, ctx.date(), ctx.extra().copy())
1816 1802 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1817 1803 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1818 1804 parent2=xp2)
1819 1805 # set the new commit is proper phase
1820 1806 targetphase = subrepo.newcommitphase(self.ui, ctx)
1821 1807 if targetphase:
1822 1808 # retract boundary do not alter parent changeset.
1823 1809 # if a parent have higher the resulting phase will
1824 1810 # be compliant anyway
1825 1811 #
1826 1812 # if minimal phase was 0 we don't need to retract anything
1827 1813 phases.retractboundary(self, tr, targetphase, [n])
1828 1814 tr.close()
1829 1815 return n
1830 1816 finally:
1831 1817 if tr:
1832 1818 tr.release()
1833 1819 lock.release()
1834 1820
1835 1821 @unfilteredmethod
1836 1822 def destroying(self):
1837 1823 '''Inform the repository that nodes are about to be destroyed.
1838 1824 Intended for use by strip and rollback, so there's a common
1839 1825 place for anything that has to be done before destroying history.
1840 1826
1841 1827 This is mostly useful for saving state that is in memory and waiting
1842 1828 to be flushed when the current lock is released. Because a call to
1843 1829 destroyed is imminent, the repo will be invalidated causing those
1844 1830 changes to stay in memory (waiting for the next unlock), or vanish
1845 1831 completely.
1846 1832 '''
1847 1833 # When using the same lock to commit and strip, the phasecache is left
1848 1834 # dirty after committing. Then when we strip, the repo is invalidated,
1849 1835 # causing those changes to disappear.
1850 1836 if '_phasecache' in vars(self):
1851 1837 self._phasecache.write()
1852 1838
1853 1839 @unfilteredmethod
1854 1840 def destroyed(self):
1855 1841 '''Inform the repository that nodes have been destroyed.
1856 1842 Intended for use by strip and rollback, so there's a common
1857 1843 place for anything that has to be done after destroying history.
1858 1844 '''
1859 1845 # When one tries to:
1860 1846 # 1) destroy nodes thus calling this method (e.g. strip)
1861 1847 # 2) use phasecache somewhere (e.g. commit)
1862 1848 #
1863 1849 # then 2) will fail because the phasecache contains nodes that were
1864 1850 # removed. We can either remove phasecache from the filecache,
1865 1851 # causing it to reload next time it is accessed, or simply filter
1866 1852 # the removed nodes now and write the updated cache.
1867 1853 self._phasecache.filterunknown(self)
1868 1854 self._phasecache.write()
1869 1855
1870 1856 # refresh all repository caches
1871 1857 self.updatecaches()
1872 1858
1873 1859 # Ensure the persistent tag cache is updated. Doing it now
1874 1860 # means that the tag cache only has to worry about destroyed
1875 1861 # heads immediately after a strip/rollback. That in turn
1876 1862 # guarantees that "cachetip == currenttip" (comparing both rev
1877 1863 # and node) always means no nodes have been added or destroyed.
1878 1864
1879 1865 # XXX this is suboptimal when qrefresh'ing: we strip the current
1880 1866 # head, refresh the tag cache, then immediately add a new head.
1881 1867 # But I think doing it this way is necessary for the "instant
1882 1868 # tag cache retrieval" case to work.
1883 1869 self.invalidate()
1884 1870
1885 1871 def walk(self, match, node=None):
1886 1872 '''
1887 1873 walk recursively through the directory tree or a given
1888 1874 changeset, finding all files matched by the match
1889 1875 function
1890 1876 '''
1891 1877 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
1892 1878 return self[node].walk(match)
1893 1879
1894 1880 def status(self, node1='.', node2=None, match=None,
1895 1881 ignored=False, clean=False, unknown=False,
1896 1882 listsubrepos=False):
1897 1883 '''a convenience method that calls node1.status(node2)'''
1898 1884 return self[node1].status(node2, match, ignored, clean, unknown,
1899 1885 listsubrepos)
1900 1886
1901 1887 def heads(self, start=None):
1902 1888 if start is None:
1903 1889 cl = self.changelog
1904 1890 headrevs = reversed(cl.headrevs())
1905 1891 return [cl.node(rev) for rev in headrevs]
1906 1892
1907 1893 heads = self.changelog.heads(start)
1908 1894 # sort the output in rev descending order
1909 1895 return sorted(heads, key=self.changelog.rev, reverse=True)
1910 1896
1911 1897 def branchheads(self, branch=None, start=None, closed=False):
1912 1898 '''return a (possibly filtered) list of heads for the given branch
1913 1899
1914 1900 Heads are returned in topological order, from newest to oldest.
1915 1901 If branch is None, use the dirstate branch.
1916 1902 If start is not None, return only heads reachable from start.
1917 1903 If closed is True, return heads that are marked as closed as well.
1918 1904 '''
1919 1905 if branch is None:
1920 1906 branch = self[None].branch()
1921 1907 branches = self.branchmap()
1922 1908 if branch not in branches:
1923 1909 return []
1924 1910 # the cache returns heads ordered lowest to highest
1925 1911 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1926 1912 if start is not None:
1927 1913 # filter out the heads that cannot be reached from startrev
1928 1914 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1929 1915 bheads = [h for h in bheads if h in fbheads]
1930 1916 return bheads
1931 1917
1932 1918 def branches(self, nodes):
1933 1919 if not nodes:
1934 1920 nodes = [self.changelog.tip()]
1935 1921 b = []
1936 1922 for n in nodes:
1937 1923 t = n
1938 1924 while True:
1939 1925 p = self.changelog.parents(n)
1940 1926 if p[1] != nullid or p[0] == nullid:
1941 1927 b.append((t, n, p[0], p[1]))
1942 1928 break
1943 1929 n = p[0]
1944 1930 return b
1945 1931
1946 1932 def between(self, pairs):
1947 1933 r = []
1948 1934
1949 1935 for top, bottom in pairs:
1950 1936 n, l, i = top, [], 0
1951 1937 f = 1
1952 1938
1953 1939 while n != bottom and n != nullid:
1954 1940 p = self.changelog.parents(n)[0]
1955 1941 if i == f:
1956 1942 l.append(n)
1957 1943 f = f * 2
1958 1944 n = p
1959 1945 i += 1
1960 1946
1961 1947 r.append(l)
1962 1948
1963 1949 return r
1964 1950
1965 1951 def checkpush(self, pushop):
1966 1952 """Extensions can override this function if additional checks have
1967 1953 to be performed before pushing, or call it if they override push
1968 1954 command.
1969 1955 """
1970 1956 pass
1971 1957
1972 1958 @unfilteredpropertycache
1973 1959 def prepushoutgoinghooks(self):
1974 1960 """Return util.hooks consists of a pushop with repo, remote, outgoing
1975 1961 methods, which are called before pushing changesets.
1976 1962 """
1977 1963 return util.hooks()
1978 1964
1979 1965 def pushkey(self, namespace, key, old, new):
1980 1966 try:
1981 1967 tr = self.currenttransaction()
1982 1968 hookargs = {}
1983 1969 if tr is not None:
1984 1970 hookargs.update(tr.hookargs)
1985 1971 hookargs['namespace'] = namespace
1986 1972 hookargs['key'] = key
1987 1973 hookargs['old'] = old
1988 1974 hookargs['new'] = new
1989 1975 self.hook('prepushkey', throw=True, **hookargs)
1990 1976 except error.HookAbort as exc:
1991 1977 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1992 1978 if exc.hint:
1993 1979 self.ui.write_err(_("(%s)\n") % exc.hint)
1994 1980 return False
1995 1981 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1996 1982 ret = pushkey.push(self, namespace, key, old, new)
1997 1983 def runhook():
1998 1984 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1999 1985 ret=ret)
2000 1986 self._afterlock(runhook)
2001 1987 return ret
2002 1988
2003 1989 def listkeys(self, namespace):
2004 1990 self.hook('prelistkeys', throw=True, namespace=namespace)
2005 1991 self.ui.debug('listing keys for "%s"\n' % namespace)
2006 1992 values = pushkey.list(self, namespace)
2007 1993 self.hook('listkeys', namespace=namespace, values=values)
2008 1994 return values
2009 1995
2010 1996 def debugwireargs(self, one, two, three=None, four=None, five=None):
2011 1997 '''used to test argument passing over the wire'''
2012 1998 return "%s %s %s %s %s" % (one, two, three, four, five)
2013 1999
2014 2000 def savecommitmessage(self, text):
2015 2001 fp = self.vfs('last-message.txt', 'wb')
2016 2002 try:
2017 2003 fp.write(text)
2018 2004 finally:
2019 2005 fp.close()
2020 2006 return self.pathto(fp.name[len(self.root) + 1:])
2021 2007
2022 2008 # used to avoid circular references so destructors work
2023 2009 def aftertrans(files):
2024 2010 renamefiles = [tuple(t) for t in files]
2025 2011 def a():
2026 2012 for vfs, src, dest in renamefiles:
2027 2013 # if src and dest refer to a same file, vfs.rename is a no-op,
2028 2014 # leaving both src and dest on disk. delete dest to make sure
2029 2015 # the rename couldn't be such a no-op.
2030 2016 vfs.tryunlink(dest)
2031 2017 try:
2032 2018 vfs.rename(src, dest)
2033 2019 except OSError: # journal file does not yet exist
2034 2020 pass
2035 2021 return a
2036 2022
2037 2023 def undoname(fn):
2038 2024 base, name = os.path.split(fn)
2039 2025 assert name.startswith('journal')
2040 2026 return os.path.join(base, name.replace('journal', 'undo', 1))
2041 2027
2042 2028 def instance(ui, path, create):
2043 2029 return localrepository(ui, util.urllocalpath(path), create)
2044 2030
2045 2031 def islocal(path):
2046 2032 return True
2047 2033
2048 2034 def newreporequirements(repo):
2049 2035 """Determine the set of requirements for a new local repository.
2050 2036
2051 2037 Extensions can wrap this function to specify custom requirements for
2052 2038 new repositories.
2053 2039 """
2054 2040 ui = repo.ui
2055 2041 requirements = {'revlogv1'}
2056 2042 if ui.configbool('format', 'usestore', True):
2057 2043 requirements.add('store')
2058 2044 if ui.configbool('format', 'usefncache', True):
2059 2045 requirements.add('fncache')
2060 2046 if ui.configbool('format', 'dotencode', True):
2061 2047 requirements.add('dotencode')
2062 2048
2063 2049 compengine = ui.config('experimental', 'format.compression', 'zlib')
2064 2050 if compengine not in util.compengines:
2065 2051 raise error.Abort(_('compression engine %s defined by '
2066 2052 'experimental.format.compression not available') %
2067 2053 compengine,
2068 2054 hint=_('run "hg debuginstall" to list available '
2069 2055 'compression engines'))
2070 2056
2071 2057 # zlib is the historical default and doesn't need an explicit requirement.
2072 2058 if compengine != 'zlib':
2073 2059 requirements.add('exp-compression-%s' % compengine)
2074 2060
2075 2061 if scmutil.gdinitconfig(ui):
2076 2062 requirements.add('generaldelta')
2077 2063 if ui.configbool('experimental', 'treemanifest', False):
2078 2064 requirements.add('treemanifest')
2079 2065 if ui.configbool('experimental', 'manifestv2', False):
2080 2066 requirements.add('manifestv2')
2081 2067
2082 2068 revlogv2 = ui.config('experimental', 'revlogv2')
2083 2069 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2084 2070 requirements.remove('revlogv1')
2085 2071 # generaldelta is implied by revlogv2.
2086 2072 requirements.discard('generaldelta')
2087 2073 requirements.add(REVLOGV2_REQUIREMENT)
2088 2074
2089 2075 return requirements
@@ -1,1442 +1,1458 b''
1 1 # obsolete.py - obsolete markers handling
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Obsolete marker handling
10 10
11 11 An obsolete marker maps an old changeset to a list of new
12 12 changesets. If the list of new changesets is empty, the old changeset
13 13 is said to be "killed". Otherwise, the old changeset is being
14 14 "replaced" by the new changesets.
15 15
16 16 Obsolete markers can be used to record and distribute changeset graph
17 17 transformations performed by history rewrite operations, and help
18 18 building new tools to reconcile conflicting rewrite actions. To
19 19 facilitate conflict resolution, markers include various annotations
20 20 besides old and news changeset identifiers, such as creation date or
21 21 author name.
22 22
23 23 The old obsoleted changeset is called a "precursor" and possible
24 24 replacements are called "successors". Markers that used changeset X as
25 25 a precursor are called "successor markers of X" because they hold
26 26 information about the successors of X. Markers that use changeset Y as
27 27 a successors are call "precursor markers of Y" because they hold
28 28 information about the precursors of Y.
29 29
30 30 Examples:
31 31
32 32 - When changeset A is replaced by changeset A', one marker is stored:
33 33
34 34 (A, (A',))
35 35
36 36 - When changesets A and B are folded into a new changeset C, two markers are
37 37 stored:
38 38
39 39 (A, (C,)) and (B, (C,))
40 40
41 41 - When changeset A is simply "pruned" from the graph, a marker is created:
42 42
43 43 (A, ())
44 44
45 45 - When changeset A is split into B and C, a single marker is used:
46 46
47 47 (A, (B, C))
48 48
49 49 We use a single marker to distinguish the "split" case from the "divergence"
50 50 case. If two independent operations rewrite the same changeset A in to A' and
51 51 A'', we have an error case: divergent rewriting. We can detect it because
52 52 two markers will be created independently:
53 53
54 54 (A, (B,)) and (A, (C,))
55 55
56 56 Format
57 57 ------
58 58
59 59 Markers are stored in an append-only file stored in
60 60 '.hg/store/obsstore'.
61 61
62 62 The file starts with a version header:
63 63
64 64 - 1 unsigned byte: version number, starting at zero.
65 65
66 66 The header is followed by the markers. Marker format depend of the version. See
67 67 comment associated with each format for details.
68 68
69 69 """
70 70 from __future__ import absolute_import
71 71
72 72 import errno
73 73 import struct
74 74
75 75 from .i18n import _
76 76 from . import (
77 77 error,
78 78 node,
79 79 phases,
80 80 policy,
81 81 util,
82 82 )
83 83
84 84 parsers = policy.importmod(r'parsers')
85 85
86 86 _pack = struct.pack
87 87 _unpack = struct.unpack
88 88 _calcsize = struct.calcsize
89 89 propertycache = util.propertycache
90 90
91 91 # the obsolete feature is not mature enough to be enabled by default.
92 92 # you have to rely on third party extension extension to enable this.
93 93 _enabled = False
94 94
95 95 # Options for obsolescence
96 96 createmarkersopt = 'createmarkers'
97 97 allowunstableopt = 'allowunstable'
98 98 exchangeopt = 'exchange'
99 99
100 100 def isenabled(repo, option):
101 101 """Returns True if the given repository has the given obsolete option
102 102 enabled.
103 103 """
104 104 result = set(repo.ui.configlist('experimental', 'evolution'))
105 105 if 'all' in result:
106 106 return True
107 107
108 108 # For migration purposes, temporarily return true if the config hasn't been
109 109 # set but _enabled is true.
110 110 if len(result) == 0 and _enabled:
111 111 return True
112 112
113 113 # createmarkers must be enabled if other options are enabled
114 114 if ((allowunstableopt in result or exchangeopt in result) and
115 115 not createmarkersopt in result):
116 116 raise error.Abort(_("'createmarkers' obsolete option must be enabled "
117 117 "if other obsolete options are enabled"))
118 118
119 119 return option in result
120 120
121 121 ### obsolescence marker flag
122 122
123 123 ## bumpedfix flag
124 124 #
125 125 # When a changeset A' succeed to a changeset A which became public, we call A'
126 126 # "bumped" because it's a successors of a public changesets
127 127 #
128 128 # o A' (bumped)
129 129 # |`:
130 130 # | o A
131 131 # |/
132 132 # o Z
133 133 #
134 134 # The way to solve this situation is to create a new changeset Ad as children
135 135 # of A. This changeset have the same content than A'. So the diff from A to A'
136 136 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
137 137 #
138 138 # o Ad
139 139 # |`:
140 140 # | x A'
141 141 # |'|
142 142 # o | A
143 143 # |/
144 144 # o Z
145 145 #
146 146 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
147 147 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
148 148 # This flag mean that the successors express the changes between the public and
149 149 # bumped version and fix the situation, breaking the transitivity of
150 150 # "bumped" here.
151 151 bumpedfix = 1
152 152 usingsha256 = 2
153 153
154 154 ## Parsing and writing of version "0"
155 155 #
156 156 # The header is followed by the markers. Each marker is made of:
157 157 #
158 158 # - 1 uint8 : number of new changesets "N", can be zero.
159 159 #
160 160 # - 1 uint32: metadata size "M" in bytes.
161 161 #
162 162 # - 1 byte: a bit field. It is reserved for flags used in common
163 163 # obsolete marker operations, to avoid repeated decoding of metadata
164 164 # entries.
165 165 #
166 166 # - 20 bytes: obsoleted changeset identifier.
167 167 #
168 168 # - N*20 bytes: new changesets identifiers.
169 169 #
170 170 # - M bytes: metadata as a sequence of nul-terminated strings. Each
171 171 # string contains a key and a value, separated by a colon ':', without
172 172 # additional encoding. Keys cannot contain '\0' or ':' and values
173 173 # cannot contain '\0'.
174 174 _fm0version = 0
175 175 _fm0fixed = '>BIB20s'
176 176 _fm0node = '20s'
177 177 _fm0fsize = _calcsize(_fm0fixed)
178 178 _fm0fnodesize = _calcsize(_fm0node)
179 179
180 180 def _fm0readmarkers(data, off):
181 181 # Loop on markers
182 182 l = len(data)
183 183 while off + _fm0fsize <= l:
184 184 # read fixed part
185 185 cur = data[off:off + _fm0fsize]
186 186 off += _fm0fsize
187 187 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
188 188 # read replacement
189 189 sucs = ()
190 190 if numsuc:
191 191 s = (_fm0fnodesize * numsuc)
192 192 cur = data[off:off + s]
193 193 sucs = _unpack(_fm0node * numsuc, cur)
194 194 off += s
195 195 # read metadata
196 196 # (metadata will be decoded on demand)
197 197 metadata = data[off:off + mdsize]
198 198 if len(metadata) != mdsize:
199 199 raise error.Abort(_('parsing obsolete marker: metadata is too '
200 200 'short, %d bytes expected, got %d')
201 201 % (mdsize, len(metadata)))
202 202 off += mdsize
203 203 metadata = _fm0decodemeta(metadata)
204 204 try:
205 205 when, offset = metadata.pop('date', '0 0').split(' ')
206 206 date = float(when), int(offset)
207 207 except ValueError:
208 208 date = (0., 0)
209 209 parents = None
210 210 if 'p2' in metadata:
211 211 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
212 212 elif 'p1' in metadata:
213 213 parents = (metadata.pop('p1', None),)
214 214 elif 'p0' in metadata:
215 215 parents = ()
216 216 if parents is not None:
217 217 try:
218 218 parents = tuple(node.bin(p) for p in parents)
219 219 # if parent content is not a nodeid, drop the data
220 220 for p in parents:
221 221 if len(p) != 20:
222 222 parents = None
223 223 break
224 224 except TypeError:
225 225 # if content cannot be translated to nodeid drop the data.
226 226 parents = None
227 227
228 228 metadata = tuple(sorted(metadata.iteritems()))
229 229
230 230 yield (pre, sucs, flags, metadata, date, parents)
231 231
232 232 def _fm0encodeonemarker(marker):
233 233 pre, sucs, flags, metadata, date, parents = marker
234 234 if flags & usingsha256:
235 235 raise error.Abort(_('cannot handle sha256 with old obsstore format'))
236 236 metadata = dict(metadata)
237 237 time, tz = date
238 238 metadata['date'] = '%r %i' % (time, tz)
239 239 if parents is not None:
240 240 if not parents:
241 241 # mark that we explicitly recorded no parents
242 242 metadata['p0'] = ''
243 243 for i, p in enumerate(parents, 1):
244 244 metadata['p%i' % i] = node.hex(p)
245 245 metadata = _fm0encodemeta(metadata)
246 246 numsuc = len(sucs)
247 247 format = _fm0fixed + (_fm0node * numsuc)
248 248 data = [numsuc, len(metadata), flags, pre]
249 249 data.extend(sucs)
250 250 return _pack(format, *data) + metadata
251 251
252 252 def _fm0encodemeta(meta):
253 253 """Return encoded metadata string to string mapping.
254 254
255 255 Assume no ':' in key and no '\0' in both key and value."""
256 256 for key, value in meta.iteritems():
257 257 if ':' in key or '\0' in key:
258 258 raise ValueError("':' and '\0' are forbidden in metadata key'")
259 259 if '\0' in value:
260 260 raise ValueError("':' is forbidden in metadata value'")
261 261 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
262 262
263 263 def _fm0decodemeta(data):
264 264 """Return string to string dictionary from encoded version."""
265 265 d = {}
266 266 for l in data.split('\0'):
267 267 if l:
268 268 key, value = l.split(':')
269 269 d[key] = value
270 270 return d
271 271
272 272 ## Parsing and writing of version "1"
273 273 #
274 274 # The header is followed by the markers. Each marker is made of:
275 275 #
276 276 # - uint32: total size of the marker (including this field)
277 277 #
278 278 # - float64: date in seconds since epoch
279 279 #
280 280 # - int16: timezone offset in minutes
281 281 #
282 282 # - uint16: a bit field. It is reserved for flags used in common
283 283 # obsolete marker operations, to avoid repeated decoding of metadata
284 284 # entries.
285 285 #
286 286 # - uint8: number of successors "N", can be zero.
287 287 #
288 288 # - uint8: number of parents "P", can be zero.
289 289 #
290 290 # 0: parents data stored but no parent,
291 291 # 1: one parent stored,
292 292 # 2: two parents stored,
293 293 # 3: no parent data stored
294 294 #
295 295 # - uint8: number of metadata entries M
296 296 #
297 297 # - 20 or 32 bytes: precursor changeset identifier.
298 298 #
299 299 # - N*(20 or 32) bytes: successors changesets identifiers.
300 300 #
301 301 # - P*(20 or 32) bytes: parents of the precursors changesets.
302 302 #
303 303 # - M*(uint8, uint8): size of all metadata entries (key and value)
304 304 #
305 305 # - remaining bytes: the metadata, each (key, value) pair after the other.
306 306 _fm1version = 1
307 307 _fm1fixed = '>IdhHBBB20s'
308 308 _fm1nodesha1 = '20s'
309 309 _fm1nodesha256 = '32s'
310 310 _fm1nodesha1size = _calcsize(_fm1nodesha1)
311 311 _fm1nodesha256size = _calcsize(_fm1nodesha256)
312 312 _fm1fsize = _calcsize(_fm1fixed)
313 313 _fm1parentnone = 3
314 314 _fm1parentshift = 14
315 315 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
316 316 _fm1metapair = 'BB'
317 317 _fm1metapairsize = _calcsize('BB')
318 318
319 319 def _fm1purereadmarkers(data, off):
320 320 # make some global constants local for performance
321 321 noneflag = _fm1parentnone
322 322 sha2flag = usingsha256
323 323 sha1size = _fm1nodesha1size
324 324 sha2size = _fm1nodesha256size
325 325 sha1fmt = _fm1nodesha1
326 326 sha2fmt = _fm1nodesha256
327 327 metasize = _fm1metapairsize
328 328 metafmt = _fm1metapair
329 329 fsize = _fm1fsize
330 330 unpack = _unpack
331 331
332 332 # Loop on markers
333 333 stop = len(data) - _fm1fsize
334 334 ufixed = struct.Struct(_fm1fixed).unpack
335 335
336 336 while off <= stop:
337 337 # read fixed part
338 338 o1 = off + fsize
339 339 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
340 340
341 341 if flags & sha2flag:
342 342 # FIXME: prec was read as a SHA1, needs to be amended
343 343
344 344 # read 0 or more successors
345 345 if numsuc == 1:
346 346 o2 = o1 + sha2size
347 347 sucs = (data[o1:o2],)
348 348 else:
349 349 o2 = o1 + sha2size * numsuc
350 350 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
351 351
352 352 # read parents
353 353 if numpar == noneflag:
354 354 o3 = o2
355 355 parents = None
356 356 elif numpar == 1:
357 357 o3 = o2 + sha2size
358 358 parents = (data[o2:o3],)
359 359 else:
360 360 o3 = o2 + sha2size * numpar
361 361 parents = unpack(sha2fmt * numpar, data[o2:o3])
362 362 else:
363 363 # read 0 or more successors
364 364 if numsuc == 1:
365 365 o2 = o1 + sha1size
366 366 sucs = (data[o1:o2],)
367 367 else:
368 368 o2 = o1 + sha1size * numsuc
369 369 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
370 370
371 371 # read parents
372 372 if numpar == noneflag:
373 373 o3 = o2
374 374 parents = None
375 375 elif numpar == 1:
376 376 o3 = o2 + sha1size
377 377 parents = (data[o2:o3],)
378 378 else:
379 379 o3 = o2 + sha1size * numpar
380 380 parents = unpack(sha1fmt * numpar, data[o2:o3])
381 381
382 382 # read metadata
383 383 off = o3 + metasize * nummeta
384 384 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
385 385 metadata = []
386 386 for idx in xrange(0, len(metapairsize), 2):
387 387 o1 = off + metapairsize[idx]
388 388 o2 = o1 + metapairsize[idx + 1]
389 389 metadata.append((data[off:o1], data[o1:o2]))
390 390 off = o2
391 391
392 392 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
393 393
394 394 def _fm1encodeonemarker(marker):
395 395 pre, sucs, flags, metadata, date, parents = marker
396 396 # determine node size
397 397 _fm1node = _fm1nodesha1
398 398 if flags & usingsha256:
399 399 _fm1node = _fm1nodesha256
400 400 numsuc = len(sucs)
401 401 numextranodes = numsuc
402 402 if parents is None:
403 403 numpar = _fm1parentnone
404 404 else:
405 405 numpar = len(parents)
406 406 numextranodes += numpar
407 407 formatnodes = _fm1node * numextranodes
408 408 formatmeta = _fm1metapair * len(metadata)
409 409 format = _fm1fixed + formatnodes + formatmeta
410 410 # tz is stored in minutes so we divide by 60
411 411 tz = date[1]//60
412 412 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
413 413 data.extend(sucs)
414 414 if parents is not None:
415 415 data.extend(parents)
416 416 totalsize = _calcsize(format)
417 417 for key, value in metadata:
418 418 lk = len(key)
419 419 lv = len(value)
420 420 data.append(lk)
421 421 data.append(lv)
422 422 totalsize += lk + lv
423 423 data[0] = totalsize
424 424 data = [_pack(format, *data)]
425 425 for key, value in metadata:
426 426 data.append(key)
427 427 data.append(value)
428 428 return ''.join(data)
429 429
430 430 def _fm1readmarkers(data, off):
431 431 native = getattr(parsers, 'fm1readmarkers', None)
432 432 if not native:
433 433 return _fm1purereadmarkers(data, off)
434 434 stop = len(data) - _fm1fsize
435 435 return native(data, off, stop)
436 436
437 437 # mapping to read/write various marker formats
438 438 # <version> -> (decoder, encoder)
439 439 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
440 440 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
441 441
442 442 def _readmarkerversion(data):
443 443 return _unpack('>B', data[0:1])[0]
444 444
445 445 @util.nogc
446 446 def _readmarkers(data):
447 447 """Read and enumerate markers from raw data"""
448 448 diskversion = _readmarkerversion(data)
449 449 off = 1
450 450 if diskversion not in formats:
451 451 msg = _('parsing obsolete marker: unknown version %r') % diskversion
452 452 raise error.UnknownVersion(msg, version=diskversion)
453 453 return diskversion, formats[diskversion][0](data, off)
454 454
455 455 def encodeheader(version=_fm0version):
456 456 return _pack('>B', version)
457 457
458 458 def encodemarkers(markers, addheader=False, version=_fm0version):
459 459 # Kept separate from flushmarkers(), it will be reused for
460 460 # markers exchange.
461 461 encodeone = formats[version][1]
462 462 if addheader:
463 463 yield encodeheader(version)
464 464 for marker in markers:
465 465 yield encodeone(marker)
466 466
467 467
468 468 class marker(object):
469 469 """Wrap obsolete marker raw data"""
470 470
471 471 def __init__(self, repo, data):
472 472 # the repo argument will be used to create changectx in later version
473 473 self._repo = repo
474 474 self._data = data
475 475 self._decodedmeta = None
476 476
477 477 def __hash__(self):
478 478 return hash(self._data)
479 479
480 480 def __eq__(self, other):
481 481 if type(other) != type(self):
482 482 return False
483 483 return self._data == other._data
484 484
485 485 def precnode(self):
486 486 """Precursor changeset node identifier"""
487 487 return self._data[0]
488 488
489 489 def succnodes(self):
490 490 """List of successor changesets node identifiers"""
491 491 return self._data[1]
492 492
493 493 def parentnodes(self):
494 494 """Parents of the precursors (None if not recorded)"""
495 495 return self._data[5]
496 496
497 497 def metadata(self):
498 498 """Decoded metadata dictionary"""
499 499 return dict(self._data[3])
500 500
501 501 def date(self):
502 502 """Creation date as (unixtime, offset)"""
503 503 return self._data[4]
504 504
505 505 def flags(self):
506 506 """The flags field of the marker"""
507 507 return self._data[2]
508 508
509 509 @util.nogc
510 510 def _addsuccessors(successors, markers):
511 511 for mark in markers:
512 512 successors.setdefault(mark[0], set()).add(mark)
513 513
514 514 @util.nogc
515 515 def _addprecursors(precursors, markers):
516 516 for mark in markers:
517 517 for suc in mark[1]:
518 518 precursors.setdefault(suc, set()).add(mark)
519 519
520 520 @util.nogc
521 521 def _addchildren(children, markers):
522 522 for mark in markers:
523 523 parents = mark[5]
524 524 if parents is not None:
525 525 for p in parents:
526 526 children.setdefault(p, set()).add(mark)
527 527
528 528 def _checkinvalidmarkers(markers):
529 529 """search for marker with invalid data and raise error if needed
530 530
531 531 Exist as a separated function to allow the evolve extension for a more
532 532 subtle handling.
533 533 """
534 534 for mark in markers:
535 535 if node.nullid in mark[1]:
536 536 raise error.Abort(_('bad obsolescence marker detected: '
537 537 'invalid successors nullid'))
538 538
539 539 class obsstore(object):
540 540 """Store obsolete markers
541 541
542 542 Markers can be accessed with two mappings:
543 543 - precursors[x] -> set(markers on precursors edges of x)
544 544 - successors[x] -> set(markers on successors edges of x)
545 545 - children[x] -> set(markers on precursors edges of children(x)
546 546 """
547 547
548 548 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
549 549 # prec: nodeid, precursor changesets
550 550 # succs: tuple of nodeid, successor changesets (0-N length)
551 551 # flag: integer, flag field carrying modifier for the markers (see doc)
552 552 # meta: binary blob, encoded metadata dictionary
553 553 # date: (float, int) tuple, date of marker creation
554 554 # parents: (tuple of nodeid) or None, parents of precursors
555 555 # None is used when no data has been recorded
556 556
557 557 def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
558 558 # caches for various obsolescence related cache
559 559 self.caches = {}
560 560 self.svfs = svfs
561 561 self._defaultformat = defaultformat
562 562 self._readonly = readonly
563 563
564 564 def __iter__(self):
565 565 return iter(self._all)
566 566
567 567 def __len__(self):
568 568 return len(self._all)
569 569
570 570 def __nonzero__(self):
571 571 if not self._cached('_all'):
572 572 try:
573 573 return self.svfs.stat('obsstore').st_size > 1
574 574 except OSError as inst:
575 575 if inst.errno != errno.ENOENT:
576 576 raise
577 577 # just build an empty _all list if no obsstore exists, which
578 578 # avoids further stat() syscalls
579 579 pass
580 580 return bool(self._all)
581 581
582 582 __bool__ = __nonzero__
583 583
584 584 @property
585 585 def readonly(self):
586 586 """True if marker creation is disabled
587 587
588 588 Remove me in the future when obsolete marker is always on."""
589 589 return self._readonly
590 590
591 591 def create(self, transaction, prec, succs=(), flag=0, parents=None,
592 592 date=None, metadata=None, ui=None):
593 593 """obsolete: add a new obsolete marker
594 594
595 595 * ensuring it is hashable
596 596 * check mandatory metadata
597 597 * encode metadata
598 598
599 599 If you are a human writing code creating marker you want to use the
600 600 `createmarkers` function in this module instead.
601 601
602 602 return True if a new marker have been added, False if the markers
603 603 already existed (no op).
604 604 """
605 605 if metadata is None:
606 606 metadata = {}
607 607 if date is None:
608 608 if 'date' in metadata:
609 609 # as a courtesy for out-of-tree extensions
610 610 date = util.parsedate(metadata.pop('date'))
611 611 elif ui is not None:
612 612 date = ui.configdate('devel', 'default-date')
613 613 if date is None:
614 614 date = util.makedate()
615 615 else:
616 616 date = util.makedate()
617 617 if len(prec) != 20:
618 618 raise ValueError(prec)
619 619 for succ in succs:
620 620 if len(succ) != 20:
621 621 raise ValueError(succ)
622 622 if prec in succs:
623 623 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
624 624
625 625 metadata = tuple(sorted(metadata.iteritems()))
626 626
627 627 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
628 628 return bool(self.add(transaction, [marker]))
629 629
630 630 def add(self, transaction, markers):
631 631 """Add new markers to the store
632 632
633 633 Take care of filtering duplicate.
634 634 Return the number of new marker."""
635 635 if self._readonly:
636 636 raise error.Abort(_('creating obsolete markers is not enabled on '
637 637 'this repo'))
638 638 known = set(self._all)
639 639 new = []
640 640 for m in markers:
641 641 if m not in known:
642 642 known.add(m)
643 643 new.append(m)
644 644 if new:
645 645 f = self.svfs('obsstore', 'ab')
646 646 try:
647 647 offset = f.tell()
648 648 transaction.add('obsstore', offset)
649 649 # offset == 0: new file - add the version header
650 650 for bytes in encodemarkers(new, offset == 0, self._version):
651 651 f.write(bytes)
652 652 finally:
653 653 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
654 654 # call 'filecacheentry.refresh()' here
655 655 f.close()
656 656 self._addmarkers(new)
657 657 # new marker *may* have changed several set. invalidate the cache.
658 658 self.caches.clear()
659 659 # records the number of new markers for the transaction hooks
660 660 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
661 661 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
662 662 return len(new)
663 663
664 664 def mergemarkers(self, transaction, data):
665 665 """merge a binary stream of markers inside the obsstore
666 666
667 667 Returns the number of new markers added."""
668 668 version, markers = _readmarkers(data)
669 669 return self.add(transaction, markers)
670 670
671 671 @propertycache
672 672 def _data(self):
673 673 return self.svfs.tryread('obsstore')
674 674
675 675 @propertycache
676 676 def _version(self):
677 677 if len(self._data) >= 1:
678 678 return _readmarkerversion(self._data)
679 679 else:
680 680 return self._defaultformat
681 681
682 682 @propertycache
683 683 def _all(self):
684 684 data = self._data
685 685 if not data:
686 686 return []
687 687 self._version, markers = _readmarkers(data)
688 688 markers = list(markers)
689 689 _checkinvalidmarkers(markers)
690 690 return markers
691 691
692 692 @propertycache
693 693 def successors(self):
694 694 successors = {}
695 695 _addsuccessors(successors, self._all)
696 696 return successors
697 697
698 698 @propertycache
699 699 def precursors(self):
700 700 precursors = {}
701 701 _addprecursors(precursors, self._all)
702 702 return precursors
703 703
704 704 @propertycache
705 705 def children(self):
706 706 children = {}
707 707 _addchildren(children, self._all)
708 708 return children
709 709
710 710 def _cached(self, attr):
711 711 return attr in self.__dict__
712 712
713 713 def _addmarkers(self, markers):
714 714 markers = list(markers) # to allow repeated iteration
715 715 self._all.extend(markers)
716 716 if self._cached('successors'):
717 717 _addsuccessors(self.successors, markers)
718 718 if self._cached('precursors'):
719 719 _addprecursors(self.precursors, markers)
720 720 if self._cached('children'):
721 721 _addchildren(self.children, markers)
722 722 _checkinvalidmarkers(markers)
723 723
724 724 def relevantmarkers(self, nodes):
725 725 """return a set of all obsolescence markers relevant to a set of nodes.
726 726
727 727 "relevant" to a set of nodes mean:
728 728
729 729 - marker that use this changeset as successor
730 730 - prune marker of direct children on this changeset
731 731 - recursive application of the two rules on precursors of these markers
732 732
733 733 It is a set so you cannot rely on order."""
734 734
735 735 pendingnodes = set(nodes)
736 736 seenmarkers = set()
737 737 seennodes = set(pendingnodes)
738 738 precursorsmarkers = self.precursors
739 739 succsmarkers = self.successors
740 740 children = self.children
741 741 while pendingnodes:
742 742 direct = set()
743 743 for current in pendingnodes:
744 744 direct.update(precursorsmarkers.get(current, ()))
745 745 pruned = [m for m in children.get(current, ()) if not m[1]]
746 746 direct.update(pruned)
747 747 pruned = [m for m in succsmarkers.get(current, ()) if not m[1]]
748 748 direct.update(pruned)
749 749 direct -= seenmarkers
750 750 pendingnodes = set([m[0] for m in direct])
751 751 seenmarkers |= direct
752 752 pendingnodes -= seennodes
753 753 seennodes |= pendingnodes
754 754 return seenmarkers
755 755
756 def makestore(ui, repo):
757 """Create an obsstore instance from a repo."""
758 # read default format for new obsstore.
759 # developer config: format.obsstore-version
760 defaultformat = ui.configint('format', 'obsstore-version', None)
761 # rely on obsstore class default when possible.
762 kwargs = {}
763 if defaultformat is not None:
764 kwargs['defaultformat'] = defaultformat
765 readonly = not isenabled(repo, createmarkersopt)
766 store = obsstore(repo.svfs, readonly=readonly, **kwargs)
767 if store and readonly:
768 ui.warn(_('obsolete feature not enabled but %i markers found!\n')
769 % len(list(store)))
770 return store
771
756 772 def _filterprunes(markers):
757 773 """return a set with no prune markers"""
758 774 return set(m for m in markers if m[1])
759 775
760 776 def exclusivemarkers(repo, nodes):
761 777 """set of markers relevant to "nodes" but no other locally-known nodes
762 778
763 779 This function compute the set of markers "exclusive" to a locally-known
764 780 node. This means we walk the markers starting from <nodes> until we reach a
765 781 locally-known precursors outside of <nodes>. Element of <nodes> with
766 782 locally-known successors outside of <nodes> are ignored (since their
767 783 precursors markers are also relevant to these successors).
768 784
769 785 For example:
770 786
771 787 # (A0 rewritten as A1)
772 788 #
773 789 # A0 <-1- A1 # Marker "1" is exclusive to A1
774 790
775 791 or
776 792
777 793 # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally)
778 794 #
779 795 # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1
780 796
781 797 or
782 798
783 799 # (A0 has unknown precursors, A0 rewritten as A1 and A2 (divergence))
784 800 #
785 801 # <-2- A1 # Marker "2" is exclusive to A0,A1
786 802 # /
787 803 # <-1- A0
788 804 # \
789 805 # <-3- A2 # Marker "3" is exclusive to A0,A2
790 806 #
791 807 # in addition:
792 808 #
793 809 # Markers "2,3" are exclusive to A1,A2
794 810 # Markers "1,2,3" are exclusive to A0,A1,A2
795 811
796 812 See test/test-obsolete-bundle-strip.t for more examples.
797 813
798 814 An example usage is strip. When stripping a changeset, we also want to
799 815 strip the markers exclusive to this changeset. Otherwise we would have
800 816 "dangling"" obsolescence markers from its precursors: Obsolescence markers
801 817 marking a node as obsolete without any successors available locally.
802 818
803 819 As for relevant markers, the prune markers for children will be followed.
804 820 Of course, they will only be followed if the pruned children is
805 821 locally-known. Since the prune markers are relevant to the pruned node.
806 822 However, while prune markers are considered relevant to the parent of the
807 823 pruned changesets, prune markers for locally-known changeset (with no
808 824 successors) are considered exclusive to the pruned nodes. This allows
809 825 to strip the prune markers (with the rest of the exclusive chain) alongside
810 826 the pruned changesets.
811 827 """
812 828 # running on a filtered repository would be dangerous as markers could be
813 829 # reported as exclusive when they are relevant for other filtered nodes.
814 830 unfi = repo.unfiltered()
815 831
816 832 # shortcut to various useful item
817 833 nm = unfi.changelog.nodemap
818 834 precursorsmarkers = unfi.obsstore.precursors
819 835 successormarkers = unfi.obsstore.successors
820 836 childrenmarkers = unfi.obsstore.children
821 837
822 838 # exclusive markers (return of the function)
823 839 exclmarkers = set()
824 840 # we need fast membership testing
825 841 nodes = set(nodes)
826 842 # looking for head in the obshistory
827 843 #
828 844 # XXX we are ignoring all issues in regard with cycle for now.
829 845 stack = [n for n in nodes if not _filterprunes(successormarkers.get(n, ()))]
830 846 stack.sort()
831 847 # nodes already stacked
832 848 seennodes = set(stack)
833 849 while stack:
834 850 current = stack.pop()
835 851 # fetch precursors markers
836 852 markers = list(precursorsmarkers.get(current, ()))
837 853 # extend the list with prune markers
838 854 for mark in successormarkers.get(current, ()):
839 855 if not mark[1]:
840 856 markers.append(mark)
841 857 # and markers from children (looking for prune)
842 858 for mark in childrenmarkers.get(current, ()):
843 859 if not mark[1]:
844 860 markers.append(mark)
845 861 # traverse the markers
846 862 for mark in markers:
847 863 if mark in exclmarkers:
848 864 # markers already selected
849 865 continue
850 866
851 867 # If the markers is about the current node, select it
852 868 #
853 869 # (this delay the addition of markers from children)
854 870 if mark[1] or mark[0] == current:
855 871 exclmarkers.add(mark)
856 872
857 873 # should we keep traversing through the precursors?
858 874 prec = mark[0]
859 875
860 876 # nodes in the stack or already processed
861 877 if prec in seennodes:
862 878 continue
863 879
864 880 # is this a locally known node ?
865 881 known = prec in nm
866 882 # if locally-known and not in the <nodes> set the traversal
867 883 # stop here.
868 884 if known and prec not in nodes:
869 885 continue
870 886
871 887 # do not keep going if there are unselected markers pointing to this
872 888 # nodes. If we end up traversing these unselected markers later the
873 889 # node will be taken care of at that point.
874 890 precmarkers = _filterprunes(successormarkers.get(prec))
875 891 if precmarkers.issubset(exclmarkers):
876 892 seennodes.add(prec)
877 893 stack.append(prec)
878 894
879 895 return exclmarkers
880 896
881 897 def commonversion(versions):
882 898 """Return the newest version listed in both versions and our local formats.
883 899
884 900 Returns None if no common version exists.
885 901 """
886 902 versions.sort(reverse=True)
887 903 # search for highest version known on both side
888 904 for v in versions:
889 905 if v in formats:
890 906 return v
891 907 return None
892 908
893 909 # arbitrary picked to fit into 8K limit from HTTP server
894 910 # you have to take in account:
895 911 # - the version header
896 912 # - the base85 encoding
897 913 _maxpayload = 5300
898 914
899 915 def _pushkeyescape(markers):
900 916 """encode markers into a dict suitable for pushkey exchange
901 917
902 918 - binary data is base85 encoded
903 919 - split in chunks smaller than 5300 bytes"""
904 920 keys = {}
905 921 parts = []
906 922 currentlen = _maxpayload * 2 # ensure we create a new part
907 923 for marker in markers:
908 924 nextdata = _fm0encodeonemarker(marker)
909 925 if (len(nextdata) + currentlen > _maxpayload):
910 926 currentpart = []
911 927 currentlen = 0
912 928 parts.append(currentpart)
913 929 currentpart.append(nextdata)
914 930 currentlen += len(nextdata)
915 931 for idx, part in enumerate(reversed(parts)):
916 932 data = ''.join([_pack('>B', _fm0version)] + part)
917 933 keys['dump%i' % idx] = util.b85encode(data)
918 934 return keys
919 935
920 936 def listmarkers(repo):
921 937 """List markers over pushkey"""
922 938 if not repo.obsstore:
923 939 return {}
924 940 return _pushkeyescape(sorted(repo.obsstore))
925 941
926 942 def pushmarker(repo, key, old, new):
927 943 """Push markers over pushkey"""
928 944 if not key.startswith('dump'):
929 945 repo.ui.warn(_('unknown key: %r') % key)
930 946 return 0
931 947 if old:
932 948 repo.ui.warn(_('unexpected old value for %r') % key)
933 949 return 0
934 950 data = util.b85decode(new)
935 951 lock = repo.lock()
936 952 try:
937 953 tr = repo.transaction('pushkey: obsolete markers')
938 954 try:
939 955 repo.obsstore.mergemarkers(tr, data)
940 956 repo.invalidatevolatilesets()
941 957 tr.close()
942 958 return 1
943 959 finally:
944 960 tr.release()
945 961 finally:
946 962 lock.release()
947 963
948 964 def getmarkers(repo, nodes=None, exclusive=False):
949 965 """returns markers known in a repository
950 966
951 967 If <nodes> is specified, only markers "relevant" to those nodes are are
952 968 returned"""
953 969 if nodes is None:
954 970 rawmarkers = repo.obsstore
955 971 elif exclusive:
956 972 rawmarkers = exclusivemarkers(repo, nodes)
957 973 else:
958 974 rawmarkers = repo.obsstore.relevantmarkers(nodes)
959 975
960 976 for markerdata in rawmarkers:
961 977 yield marker(repo, markerdata)
962 978
963 979 def relevantmarkers(repo, node):
964 980 """all obsolete markers relevant to some revision"""
965 981 for markerdata in repo.obsstore.relevantmarkers(node):
966 982 yield marker(repo, markerdata)
967 983
968 984
969 985 def precursormarkers(ctx):
970 986 """obsolete marker marking this changeset as a successors"""
971 987 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
972 988 yield marker(ctx.repo(), data)
973 989
974 990 def successormarkers(ctx):
975 991 """obsolete marker making this changeset obsolete"""
976 992 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
977 993 yield marker(ctx.repo(), data)
978 994
979 995 def allsuccessors(obsstore, nodes, ignoreflags=0):
980 996 """Yield node for every successor of <nodes>.
981 997
982 998 Some successors may be unknown locally.
983 999
984 1000 This is a linear yield unsuited to detecting split changesets. It includes
985 1001 initial nodes too."""
986 1002 remaining = set(nodes)
987 1003 seen = set(remaining)
988 1004 while remaining:
989 1005 current = remaining.pop()
990 1006 yield current
991 1007 for mark in obsstore.successors.get(current, ()):
992 1008 # ignore marker flagged with specified flag
993 1009 if mark[2] & ignoreflags:
994 1010 continue
995 1011 for suc in mark[1]:
996 1012 if suc not in seen:
997 1013 seen.add(suc)
998 1014 remaining.add(suc)
999 1015
1000 1016 def allprecursors(obsstore, nodes, ignoreflags=0):
1001 1017 """Yield node for every precursors of <nodes>.
1002 1018
1003 1019 Some precursors may be unknown locally.
1004 1020
1005 1021 This is a linear yield unsuited to detecting folded changesets. It includes
1006 1022 initial nodes too."""
1007 1023
1008 1024 remaining = set(nodes)
1009 1025 seen = set(remaining)
1010 1026 while remaining:
1011 1027 current = remaining.pop()
1012 1028 yield current
1013 1029 for mark in obsstore.precursors.get(current, ()):
1014 1030 # ignore marker flagged with specified flag
1015 1031 if mark[2] & ignoreflags:
1016 1032 continue
1017 1033 suc = mark[0]
1018 1034 if suc not in seen:
1019 1035 seen.add(suc)
1020 1036 remaining.add(suc)
1021 1037
1022 1038 def foreground(repo, nodes):
1023 1039 """return all nodes in the "foreground" of other node
1024 1040
1025 1041 The foreground of a revision is anything reachable using parent -> children
1026 1042 or precursor -> successor relation. It is very similar to "descendant" but
1027 1043 augmented with obsolescence information.
1028 1044
1029 1045 Beware that possible obsolescence cycle may result if complex situation.
1030 1046 """
1031 1047 repo = repo.unfiltered()
1032 1048 foreground = set(repo.set('%ln::', nodes))
1033 1049 if repo.obsstore:
1034 1050 # We only need this complicated logic if there is obsolescence
1035 1051 # XXX will probably deserve an optimised revset.
1036 1052 nm = repo.changelog.nodemap
1037 1053 plen = -1
1038 1054 # compute the whole set of successors or descendants
1039 1055 while len(foreground) != plen:
1040 1056 plen = len(foreground)
1041 1057 succs = set(c.node() for c in foreground)
1042 1058 mutable = [c.node() for c in foreground if c.mutable()]
1043 1059 succs.update(allsuccessors(repo.obsstore, mutable))
1044 1060 known = (n for n in succs if n in nm)
1045 1061 foreground = set(repo.set('%ln::', known))
1046 1062 return set(c.node() for c in foreground)
1047 1063
1048 1064
1049 1065 def successorssets(repo, initialnode, cache=None):
1050 1066 """Return set of all latest successors of initial nodes
1051 1067
1052 1068 The successors set of a changeset A are the group of revisions that succeed
1053 1069 A. It succeeds A as a consistent whole, each revision being only a partial
1054 1070 replacement. The successors set contains non-obsolete changesets only.
1055 1071
1056 1072 This function returns the full list of successor sets which is why it
1057 1073 returns a list of tuples and not just a single tuple. Each tuple is a valid
1058 1074 successors set. Note that (A,) may be a valid successors set for changeset A
1059 1075 (see below).
1060 1076
1061 1077 In most cases, a changeset A will have a single element (e.g. the changeset
1062 1078 A is replaced by A') in its successors set. Though, it is also common for a
1063 1079 changeset A to have no elements in its successor set (e.g. the changeset
1064 1080 has been pruned). Therefore, the returned list of successors sets will be
1065 1081 [(A',)] or [], respectively.
1066 1082
1067 1083 When a changeset A is split into A' and B', however, it will result in a
1068 1084 successors set containing more than a single element, i.e. [(A',B')].
1069 1085 Divergent changesets will result in multiple successors sets, i.e. [(A',),
1070 1086 (A'')].
1071 1087
1072 1088 If a changeset A is not obsolete, then it will conceptually have no
1073 1089 successors set. To distinguish this from a pruned changeset, the successor
1074 1090 set will contain itself only, i.e. [(A,)].
1075 1091
1076 1092 Finally, successors unknown locally are considered to be pruned (obsoleted
1077 1093 without any successors).
1078 1094
1079 1095 The optional `cache` parameter is a dictionary that may contain precomputed
1080 1096 successors sets. It is meant to reuse the computation of a previous call to
1081 1097 `successorssets` when multiple calls are made at the same time. The cache
1082 1098 dictionary is updated in place. The caller is responsible for its life
1083 1099 span. Code that makes multiple calls to `successorssets` *must* use this
1084 1100 cache mechanism or suffer terrible performance.
1085 1101 """
1086 1102
1087 1103 succmarkers = repo.obsstore.successors
1088 1104
1089 1105 # Stack of nodes we search successors sets for
1090 1106 toproceed = [initialnode]
1091 1107 # set version of above list for fast loop detection
1092 1108 # element added to "toproceed" must be added here
1093 1109 stackedset = set(toproceed)
1094 1110 if cache is None:
1095 1111 cache = {}
1096 1112
1097 1113 # This while loop is the flattened version of a recursive search for
1098 1114 # successors sets
1099 1115 #
1100 1116 # def successorssets(x):
1101 1117 # successors = directsuccessors(x)
1102 1118 # ss = [[]]
1103 1119 # for succ in directsuccessors(x):
1104 1120 # # product as in itertools cartesian product
1105 1121 # ss = product(ss, successorssets(succ))
1106 1122 # return ss
1107 1123 #
1108 1124 # But we can not use plain recursive calls here:
1109 1125 # - that would blow the python call stack
1110 1126 # - obsolescence markers may have cycles, we need to handle them.
1111 1127 #
1112 1128 # The `toproceed` list act as our call stack. Every node we search
1113 1129 # successors set for are stacked there.
1114 1130 #
1115 1131 # The `stackedset` is set version of this stack used to check if a node is
1116 1132 # already stacked. This check is used to detect cycles and prevent infinite
1117 1133 # loop.
1118 1134 #
1119 1135 # successors set of all nodes are stored in the `cache` dictionary.
1120 1136 #
1121 1137 # After this while loop ends we use the cache to return the successors sets
1122 1138 # for the node requested by the caller.
1123 1139 while toproceed:
1124 1140 # Every iteration tries to compute the successors sets of the topmost
1125 1141 # node of the stack: CURRENT.
1126 1142 #
1127 1143 # There are four possible outcomes:
1128 1144 #
1129 1145 # 1) We already know the successors sets of CURRENT:
1130 1146 # -> mission accomplished, pop it from the stack.
1131 1147 # 2) Node is not obsolete:
1132 1148 # -> the node is its own successors sets. Add it to the cache.
1133 1149 # 3) We do not know successors set of direct successors of CURRENT:
1134 1150 # -> We add those successors to the stack.
1135 1151 # 4) We know successors sets of all direct successors of CURRENT:
1136 1152 # -> We can compute CURRENT successors set and add it to the
1137 1153 # cache.
1138 1154 #
1139 1155 current = toproceed[-1]
1140 1156 if current in cache:
1141 1157 # case (1): We already know the successors sets
1142 1158 stackedset.remove(toproceed.pop())
1143 1159 elif current not in succmarkers:
1144 1160 # case (2): The node is not obsolete.
1145 1161 if current in repo:
1146 1162 # We have a valid last successors.
1147 1163 cache[current] = [(current,)]
1148 1164 else:
1149 1165 # Final obsolete version is unknown locally.
1150 1166 # Do not count that as a valid successors
1151 1167 cache[current] = []
1152 1168 else:
1153 1169 # cases (3) and (4)
1154 1170 #
1155 1171 # We proceed in two phases. Phase 1 aims to distinguish case (3)
1156 1172 # from case (4):
1157 1173 #
1158 1174 # For each direct successors of CURRENT, we check whether its
1159 1175 # successors sets are known. If they are not, we stack the
1160 1176 # unknown node and proceed to the next iteration of the while
1161 1177 # loop. (case 3)
1162 1178 #
1163 1179 # During this step, we may detect obsolescence cycles: a node
1164 1180 # with unknown successors sets but already in the call stack.
1165 1181 # In such a situation, we arbitrary set the successors sets of
1166 1182 # the node to nothing (node pruned) to break the cycle.
1167 1183 #
1168 1184 # If no break was encountered we proceed to phase 2.
1169 1185 #
1170 1186 # Phase 2 computes successors sets of CURRENT (case 4); see details
1171 1187 # in phase 2 itself.
1172 1188 #
1173 1189 # Note the two levels of iteration in each phase.
1174 1190 # - The first one handles obsolescence markers using CURRENT as
1175 1191 # precursor (successors markers of CURRENT).
1176 1192 #
1177 1193 # Having multiple entry here means divergence.
1178 1194 #
1179 1195 # - The second one handles successors defined in each marker.
1180 1196 #
1181 1197 # Having none means pruned node, multiple successors means split,
1182 1198 # single successors are standard replacement.
1183 1199 #
1184 1200 for mark in sorted(succmarkers[current]):
1185 1201 for suc in mark[1]:
1186 1202 if suc not in cache:
1187 1203 if suc in stackedset:
1188 1204 # cycle breaking
1189 1205 cache[suc] = []
1190 1206 else:
1191 1207 # case (3) If we have not computed successors sets
1192 1208 # of one of those successors we add it to the
1193 1209 # `toproceed` stack and stop all work for this
1194 1210 # iteration.
1195 1211 toproceed.append(suc)
1196 1212 stackedset.add(suc)
1197 1213 break
1198 1214 else:
1199 1215 continue
1200 1216 break
1201 1217 else:
1202 1218 # case (4): we know all successors sets of all direct
1203 1219 # successors
1204 1220 #
1205 1221 # Successors set contributed by each marker depends on the
1206 1222 # successors sets of all its "successors" node.
1207 1223 #
1208 1224 # Each different marker is a divergence in the obsolescence
1209 1225 # history. It contributes successors sets distinct from other
1210 1226 # markers.
1211 1227 #
1212 1228 # Within a marker, a successor may have divergent successors
1213 1229 # sets. In such a case, the marker will contribute multiple
1214 1230 # divergent successors sets. If multiple successors have
1215 1231 # divergent successors sets, a Cartesian product is used.
1216 1232 #
1217 1233 # At the end we post-process successors sets to remove
1218 1234 # duplicated entry and successors set that are strict subset of
1219 1235 # another one.
1220 1236 succssets = []
1221 1237 for mark in sorted(succmarkers[current]):
1222 1238 # successors sets contributed by this marker
1223 1239 markss = [[]]
1224 1240 for suc in mark[1]:
1225 1241 # cardinal product with previous successors
1226 1242 productresult = []
1227 1243 for prefix in markss:
1228 1244 for suffix in cache[suc]:
1229 1245 newss = list(prefix)
1230 1246 for part in suffix:
1231 1247 # do not duplicated entry in successors set
1232 1248 # first entry wins.
1233 1249 if part not in newss:
1234 1250 newss.append(part)
1235 1251 productresult.append(newss)
1236 1252 markss = productresult
1237 1253 succssets.extend(markss)
1238 1254 # remove duplicated and subset
1239 1255 seen = []
1240 1256 final = []
1241 1257 candidate = sorted(((set(s), s) for s in succssets if s),
1242 1258 key=lambda x: len(x[1]), reverse=True)
1243 1259 for setversion, listversion in candidate:
1244 1260 for seenset in seen:
1245 1261 if setversion.issubset(seenset):
1246 1262 break
1247 1263 else:
1248 1264 final.append(listversion)
1249 1265 seen.append(setversion)
1250 1266 final.reverse() # put small successors set first
1251 1267 cache[current] = final
1252 1268 return cache[initialnode]
1253 1269
1254 1270 # mapping of 'set-name' -> <function to compute this set>
1255 1271 cachefuncs = {}
1256 1272 def cachefor(name):
1257 1273 """Decorator to register a function as computing the cache for a set"""
1258 1274 def decorator(func):
1259 1275 assert name not in cachefuncs
1260 1276 cachefuncs[name] = func
1261 1277 return func
1262 1278 return decorator
1263 1279
1264 1280 def getrevs(repo, name):
1265 1281 """Return the set of revision that belong to the <name> set
1266 1282
1267 1283 Such access may compute the set and cache it for future use"""
1268 1284 repo = repo.unfiltered()
1269 1285 if not repo.obsstore:
1270 1286 return frozenset()
1271 1287 if name not in repo.obsstore.caches:
1272 1288 repo.obsstore.caches[name] = cachefuncs[name](repo)
1273 1289 return repo.obsstore.caches[name]
1274 1290
1275 1291 # To be simple we need to invalidate obsolescence cache when:
1276 1292 #
1277 1293 # - new changeset is added:
1278 1294 # - public phase is changed
1279 1295 # - obsolescence marker are added
1280 1296 # - strip is used a repo
1281 1297 def clearobscaches(repo):
1282 1298 """Remove all obsolescence related cache from a repo
1283 1299
1284 1300 This remove all cache in obsstore is the obsstore already exist on the
1285 1301 repo.
1286 1302
1287 1303 (We could be smarter here given the exact event that trigger the cache
1288 1304 clearing)"""
1289 1305 # only clear cache is there is obsstore data in this repo
1290 1306 if 'obsstore' in repo._filecache:
1291 1307 repo.obsstore.caches.clear()
1292 1308
1293 1309 @cachefor('obsolete')
1294 1310 def _computeobsoleteset(repo):
1295 1311 """the set of obsolete revisions"""
1296 1312 getnode = repo.changelog.node
1297 1313 notpublic = repo._phasecache.getrevset(repo, (phases.draft, phases.secret))
1298 1314 isobs = repo.obsstore.successors.__contains__
1299 1315 obs = set(r for r in notpublic if isobs(getnode(r)))
1300 1316 return obs
1301 1317
1302 1318 @cachefor('unstable')
1303 1319 def _computeunstableset(repo):
1304 1320 """the set of non obsolete revisions with obsolete parents"""
1305 1321 revs = [(ctx.rev(), ctx) for ctx in
1306 1322 repo.set('(not public()) and (not obsolete())')]
1307 1323 revs.sort(key=lambda x:x[0])
1308 1324 unstable = set()
1309 1325 for rev, ctx in revs:
1310 1326 # A rev is unstable if one of its parent is obsolete or unstable
1311 1327 # this works since we traverse following growing rev order
1312 1328 if any((x.obsolete() or (x.rev() in unstable))
1313 1329 for x in ctx.parents()):
1314 1330 unstable.add(rev)
1315 1331 return unstable
1316 1332
1317 1333 @cachefor('suspended')
1318 1334 def _computesuspendedset(repo):
1319 1335 """the set of obsolete parents with non obsolete descendants"""
1320 1336 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1321 1337 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1322 1338
1323 1339 @cachefor('extinct')
1324 1340 def _computeextinctset(repo):
1325 1341 """the set of obsolete parents without non obsolete descendants"""
1326 1342 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1327 1343
1328 1344
1329 1345 @cachefor('bumped')
1330 1346 def _computebumpedset(repo):
1331 1347 """the set of revs trying to obsolete public revisions"""
1332 1348 bumped = set()
1333 1349 # util function (avoid attribute lookup in the loop)
1334 1350 phase = repo._phasecache.phase # would be faster to grab the full list
1335 1351 public = phases.public
1336 1352 cl = repo.changelog
1337 1353 torev = cl.nodemap.get
1338 1354 for ctx in repo.set('(not public()) and (not obsolete())'):
1339 1355 rev = ctx.rev()
1340 1356 # We only evaluate mutable, non-obsolete revision
1341 1357 node = ctx.node()
1342 1358 # (future) A cache of precursors may worth if split is very common
1343 1359 for pnode in allprecursors(repo.obsstore, [node],
1344 1360 ignoreflags=bumpedfix):
1345 1361 prev = torev(pnode) # unfiltered! but so is phasecache
1346 1362 if (prev is not None) and (phase(repo, prev) <= public):
1347 1363 # we have a public precursor
1348 1364 bumped.add(rev)
1349 1365 break # Next draft!
1350 1366 return bumped
1351 1367
1352 1368 @cachefor('divergent')
1353 1369 def _computedivergentset(repo):
1354 1370 """the set of rev that compete to be the final successors of some revision.
1355 1371 """
1356 1372 divergent = set()
1357 1373 obsstore = repo.obsstore
1358 1374 newermap = {}
1359 1375 for ctx in repo.set('(not public()) - obsolete()'):
1360 1376 mark = obsstore.precursors.get(ctx.node(), ())
1361 1377 toprocess = set(mark)
1362 1378 seen = set()
1363 1379 while toprocess:
1364 1380 prec = toprocess.pop()[0]
1365 1381 if prec in seen:
1366 1382 continue # emergency cycle hanging prevention
1367 1383 seen.add(prec)
1368 1384 if prec not in newermap:
1369 1385 successorssets(repo, prec, newermap)
1370 1386 newer = [n for n in newermap[prec] if n]
1371 1387 if len(newer) > 1:
1372 1388 divergent.add(ctx.rev())
1373 1389 break
1374 1390 toprocess.update(obsstore.precursors.get(prec, ()))
1375 1391 return divergent
1376 1392
1377 1393
1378 1394 def createmarkers(repo, relations, flag=0, date=None, metadata=None,
1379 1395 operation=None):
1380 1396 """Add obsolete markers between changesets in a repo
1381 1397
1382 1398 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1383 1399 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1384 1400 containing metadata for this marker only. It is merged with the global
1385 1401 metadata specified through the `metadata` argument of this function,
1386 1402
1387 1403 Trying to obsolete a public changeset will raise an exception.
1388 1404
1389 1405 Current user and date are used except if specified otherwise in the
1390 1406 metadata attribute.
1391 1407
1392 1408 This function operates within a transaction of its own, but does
1393 1409 not take any lock on the repo.
1394 1410 """
1395 1411 # prepare metadata
1396 1412 if metadata is None:
1397 1413 metadata = {}
1398 1414 if 'user' not in metadata:
1399 1415 metadata['user'] = repo.ui.username()
1400 1416 useoperation = repo.ui.configbool('experimental',
1401 1417 'evolution.track-operation',
1402 1418 False)
1403 1419 if useoperation and operation:
1404 1420 metadata['operation'] = operation
1405 1421 tr = repo.transaction('add-obsolescence-marker')
1406 1422 try:
1407 1423 markerargs = []
1408 1424 for rel in relations:
1409 1425 prec = rel[0]
1410 1426 sucs = rel[1]
1411 1427 localmetadata = metadata.copy()
1412 1428 if 2 < len(rel):
1413 1429 localmetadata.update(rel[2])
1414 1430
1415 1431 if not prec.mutable():
1416 1432 raise error.Abort(_("cannot obsolete public changeset: %s")
1417 1433 % prec,
1418 1434 hint="see 'hg help phases' for details")
1419 1435 nprec = prec.node()
1420 1436 nsucs = tuple(s.node() for s in sucs)
1421 1437 npare = None
1422 1438 if not nsucs:
1423 1439 npare = tuple(p.node() for p in prec.parents())
1424 1440 if nprec in nsucs:
1425 1441 raise error.Abort(_("changeset %s cannot obsolete itself")
1426 1442 % prec)
1427 1443
1428 1444 # Creating the marker causes the hidden cache to become invalid,
1429 1445 # which causes recomputation when we ask for prec.parents() above.
1430 1446 # Resulting in n^2 behavior. So let's prepare all of the args
1431 1447 # first, then create the markers.
1432 1448 markerargs.append((nprec, nsucs, npare, localmetadata))
1433 1449
1434 1450 for args in markerargs:
1435 1451 nprec, nsucs, npare, localmetadata = args
1436 1452 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1437 1453 date=date, metadata=localmetadata,
1438 1454 ui=repo.ui)
1439 1455 repo.filteredrevcache.clear()
1440 1456 tr.close()
1441 1457 finally:
1442 1458 tr.release()
General Comments 0
You need to be logged in to leave comments. Login now