311 marker[2], |
311 marker[2], |
312 marker[3]) |
312 marker[3]) |
313 yield marker |
313 yield marker |
314 |
314 |
315 |
315 |
316 ##################################################################### |
316 cachefuncs = obsolete.cachefuncs |
317 ### Obsolescence Caching Logic ### |
317 cachefor = obsolete.cachefor |
318 ##################################################################### |
318 getobscache = obsolete.getobscache |
319 |
319 clearobscaches = obsolete.clearobscaches |
320 # IN CORE fb72eec7efd8 |
|
321 |
|
322 # Obsolescence related logic can be very slow if we don't have efficient cache. |
|
323 # |
|
324 # This section implements a cache mechanism that did not make it into core for |
|
325 # time reason. It stores meaningful set of revisions related to obsolescence |
|
326 # (obsolete, unstable, etc.) |
|
327 # |
|
328 # Here is: |
|
329 # |
|
330 # - Computation of meaningful sets |
|
331 # - Cache access logic, |
|
332 # - Cache invalidation logic, |
|
333 # - revset and ctx using this cache. |
|
334 # |
|
335 |
|
336 |
|
337 ### Computation of meaningful set |
|
338 # |
|
339 # Most set can be computed with "simple" revset. |
|
340 |
|
341 #: { set name -> function to compute this set } mapping |
|
342 #: function take a single "repo" argument. |
|
343 #: |
|
344 #: Use the `cachefor` decorator to register new cache function |
|
345 try: |
|
346 cachefuncs = obsolete.cachefuncs |
|
347 cachefor = obsolete.cachefor |
|
348 getobscache = obsolete.getobscache |
|
349 clearobscaches = obsolete.clearobscaches |
|
350 except AttributeError: |
|
351 cachefuncs = {} |
|
352 |
|
353 def cachefor(name): |
|
354 """Decorator to register a function as computing the cache for a set""" |
|
355 def decorator(func): |
|
356 assert name not in cachefuncs |
|
357 cachefuncs[name] = func |
|
358 return func |
|
359 return decorator |
|
360 |
|
361 @cachefor('obsolete') |
|
362 def _computeobsoleteset(repo): |
|
363 """the set of obsolete revisions""" |
|
364 obs = set() |
|
365 nm = repo.changelog.nodemap |
|
366 for prec in repo.obsstore.precursors: |
|
367 rev = nm.get(prec) |
|
368 if rev is not None: |
|
369 obs.add(rev) |
|
370 return set(repo.revs('%ld - public()', obs)) |
|
371 |
|
372 @cachefor('unstable') |
|
373 def _computeunstableset(repo): |
|
374 """the set of non obsolete revisions with obsolete parents""" |
|
375 return set(repo.revs('(obsolete()::) - obsolete()')) |
|
376 |
|
377 @cachefor('suspended') |
|
378 def _computesuspendedset(repo): |
|
379 """the set of obsolete parents with non obsolete descendants""" |
|
380 return set(repo.revs('obsolete() and obsolete()::unstable()')) |
|
381 |
|
382 @cachefor('extinct') |
|
383 def _computeextinctset(repo): |
|
384 """the set of obsolete parents without non obsolete descendants""" |
|
385 return set(repo.revs('obsolete() - obsolete()::unstable()')) |
|
386 |
|
387 @eh.wrapfunction(obsolete.obsstore, '__init__') |
|
388 def _initobsstorecache(orig, obsstore, *args, **kwargs): |
|
389 """add a cache attribute to obsstore""" |
|
390 obsstore.caches = {} |
|
391 return orig(obsstore, *args, **kwargs) |
|
392 |
|
393 ### Cache access |
|
394 |
|
395 def getobscache(repo, name): |
|
396 """Return the set of revision that belong to the <name> set |
|
397 |
|
398 Such access may compute the set and cache it for future use""" |
|
399 if not repo.obsstore: |
|
400 return () |
|
401 if name not in repo.obsstore.caches: |
|
402 repo.obsstore.caches[name] = cachefuncs[name](repo) |
|
403 return repo.obsstore.caches[name] |
|
404 |
|
405 ### Cache clean up |
|
406 # |
|
407 # To be simple we need to invalidate obsolescence cache when: |
|
408 # |
|
409 # - new changeset is added: |
|
410 # - public phase is changed |
|
411 # - obsolescence marker are added |
|
412 # - strip is used a repo |
|
413 |
|
414 |
|
415 def clearobscaches(repo): |
|
416 """Remove all obsolescence related cache from a repo |
|
417 |
|
418 This remove all cache in obsstore is the obsstore already exist on the |
|
419 repo. |
|
420 |
|
421 (We could be smarter here)""" |
|
422 if 'obsstore' in repo._filecache: |
|
423 repo.obsstore.caches.clear() |
|
424 |
|
425 @eh.wrapfunction(localrepo.localrepository, 'addchangegroup') # new changeset |
|
426 @eh.wrapfunction(phases, 'retractboundary') # phase movement |
|
427 @eh.wrapfunction(phases, 'advanceboundary') # phase movement |
|
428 @eh.wrapfunction(localrepo.localrepository, 'destroyed') # strip |
|
429 def wrapclearcache(orig, repo, *args, **kwargs): |
|
430 try: |
|
431 return orig(repo, *args, **kwargs) |
|
432 finally: |
|
433 # we are a bit wide here |
|
434 # we could restrict to: |
|
435 # advanceboundary + phase==public |
|
436 # retractboundary + phase==draft |
|
437 clearobscaches(repo) |
|
438 |
|
439 @eh.wrapfunction(obsolete.obsstore, 'add') # new marker |
|
440 def clearonadd(orig, obsstore, *args, **kwargs): |
|
441 try: |
|
442 return orig(obsstore, *args, **kwargs) |
|
443 finally: |
|
444 obsstore.caches.clear() |
|
445 |
|
446 ### Use the case |
|
447 # Function in core that could benefic from the cache are overwritten by cache using version |
|
448 |
|
449 # changectx method |
|
450 |
|
451 @eh.addattr(context.changectx, 'unstable') |
|
452 def unstable(ctx): |
|
453 """is the changeset unstable (have obsolete ancestor)""" |
|
454 if ctx.node() is None: |
|
455 return False |
|
456 return ctx.rev() in getobscache(ctx._repo, 'unstable') |
|
457 |
|
458 |
|
459 @eh.addattr(context.changectx, 'extinct') |
|
460 def extinct(ctx): |
|
461 """is the changeset extinct by other""" |
|
462 if ctx.node() is None: |
|
463 return False |
|
464 return ctx.rev() in getobscache(ctx._repo, 'extinct') |
|
465 |
|
466 # revset |
|
467 |
|
468 @eh.revset('obsolete') |
|
469 def revsetobsolete(repo, subset, x): |
|
470 """``obsolete()`` |
|
471 Changeset is obsolete. |
|
472 """ |
|
473 args = revset.getargs(x, 0, 0, 'obsolete takes no argument') |
|
474 obsoletes = getobscache(repo, 'obsolete') |
|
475 return [r for r in subset if r in obsoletes] |
|
476 |
|
477 @eh.revset('unstable') |
|
478 def revsetunstable(repo, subset, x): |
|
479 """``unstable()`` |
|
480 Unstable changesets are non-obsolete with obsolete ancestors. |
|
481 """ |
|
482 args = revset.getargs(x, 0, 0, 'unstable takes no arguments') |
|
483 unstables = getobscache(repo, 'unstable') |
|
484 return [r for r in subset if r in unstables] |
|
485 |
|
486 @eh.revset('extinct') |
|
487 def revsetextinct(repo, subset, x): |
|
488 """``extinct()`` |
|
489 Obsolete changesets with obsolete descendants only. |
|
490 """ |
|
491 args = revset.getargs(x, 0, 0, 'extinct takes no arguments') |
|
492 extincts = getobscache(repo, 'extinct') |
|
493 return [r for r in subset if r in extincts] |
|
494 |
320 |
495 ##################################################################### |
321 ##################################################################### |
496 ### Complete troubles computation logic ### |
322 ### Complete troubles computation logic ### |
497 ##################################################################### |
323 ##################################################################### |
498 |
324 |
648 # - useful alias pstatus and pdiff (should probably go in evolve) |
474 # - useful alias pstatus and pdiff (should probably go in evolve) |
649 # - "troubles" method on changectx |
475 # - "troubles" method on changectx |
650 # - function to travel throught the obsolescence graph |
476 # - function to travel throught the obsolescence graph |
651 # - function to find useful changeset to stabilize |
477 # - function to find useful changeset to stabilize |
652 |
478 |
653 ### Marker Create |
479 createmarkers = obsolete.createmarkers |
654 # NOW IN CORE f85816af6294 |
|
655 try: |
|
656 createmarkers = obsolete.createmarkers |
|
657 except AttributeError: |
|
658 def createmarkers(repo, relations, metadata=None, flag=0): |
|
659 """Add obsolete markers between changeset in a repo |
|
660 |
|
661 <relations> must be an iterable of (<old>, (<new>, ...)) tuple. |
|
662 `old` and `news` are changectx. |
|
663 |
|
664 Current user and date are used except if specified otherwise in the |
|
665 metadata attribute. |
|
666 |
|
667 /!\ assume the repo have been locked by the user /!\ |
|
668 """ |
|
669 # prepare metadata |
|
670 if metadata is None: |
|
671 metadata = {} |
|
672 if 'date' not in metadata: |
|
673 metadata['date'] = '%i %i' % util.makedate() |
|
674 if 'user' not in metadata: |
|
675 metadata['user'] = repo.ui.username() |
|
676 # check future marker |
|
677 tr = repo.transaction('add-obsolescence-marker') |
|
678 try: |
|
679 for prec, sucs in relations: |
|
680 if not prec.mutable(): |
|
681 raise util.Abort("cannot obsolete immutable changeset: %s" % prec) |
|
682 nprec = prec.node() |
|
683 nsucs = tuple(s.node() for s in sucs) |
|
684 if nprec in nsucs: |
|
685 raise util.Abort("changeset %s cannot obsolete himself" % prec) |
|
686 repo.obsstore.create(tr, nprec, nsucs, flag, metadata) |
|
687 clearobscaches(repo) |
|
688 tr.close() |
|
689 finally: |
|
690 tr.release() |
|
691 |
480 |
692 |
481 |
693 ### Useful alias |
482 ### Useful alias |
694 |
483 |
695 @eh.uisetup |
484 @eh.uisetup |
1006 |
785 |
1007 ##################################################################### |
786 ##################################################################### |
1008 ### Core Other extension compat ### |
787 ### Core Other extension compat ### |
1009 ##################################################################### |
788 ##################################################################### |
1010 |
789 |
1011 # This section make official history rewritter create obsolete marker |
|
1012 |
|
1013 |
|
1014 ### commit --amend |
|
1015 # make commit --amend create obsolete marker |
|
1016 # |
|
1017 # The precursor is still strip from the repository. |
|
1018 |
|
1019 # IN CORE 63e45aee46d4 |
|
1020 |
|
1021 if getattr(cmdutil, 'obsolete', None) is None: |
|
1022 @eh.wrapfunction(cmdutil, 'amend') |
|
1023 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): |
|
1024 oldnode = old.node() |
|
1025 new = orig(ui, repo, commitfunc, old, *args, **kwargs) |
|
1026 if new != oldnode: |
|
1027 lock = repo.lock() |
|
1028 try: |
|
1029 tr = repo.transaction('post-amend-obst') |
|
1030 try: |
|
1031 meta = { |
|
1032 'date': '%i %i' % util.makedate(), |
|
1033 'user': ui.username(), |
|
1034 } |
|
1035 repo.obsstore.create(tr, oldnode, [new], 0, meta) |
|
1036 tr.close() |
|
1037 clearobscaches(repo) |
|
1038 finally: |
|
1039 tr.release() |
|
1040 finally: |
|
1041 lock.release() |
|
1042 return new |
|
1043 |
|
1044 ### rebase |
|
1045 # |
|
1046 # - ignore obsolete changeset |
|
1047 # - create obsolete marker *instead of* striping |
|
1048 |
|
1049 def buildstate(orig, repo, dest, rebaseset, *ags, **kws): |
|
1050 """wrapper for rebase 's buildstate that exclude obsolete changeset""" |
|
1051 |
|
1052 rebaseset = repo.revs('%ld - extinct()', rebaseset) |
|
1053 if not rebaseset: |
|
1054 repo.ui.warn(_('whole rebase set is extinct and ignored.\n')) |
|
1055 return {} |
|
1056 root = min(rebaseset) |
|
1057 if (not getattr(repo, '_rebasekeep', False) |
|
1058 and not repo[root].mutable()): |
|
1059 raise util.Abort(_("can't rebase immutable changeset %s") % repo[root], |
|
1060 hint=_('see hg help phases for details')) |
|
1061 return orig(repo, dest, rebaseset, *ags, **kws) |
|
1062 |
|
1063 def defineparents(orig, repo, rev, target, state, *args, **kwargs): |
|
1064 rebasestate = getattr(repo, '_rebasestate', None) |
|
1065 if rebasestate is not None: |
|
1066 repo._rebasestate = dict(state) |
|
1067 repo._rebasetarget = target |
|
1068 return orig(repo, rev, target, state, *args, **kwargs) |
|
1069 |
|
1070 def concludenode(orig, repo, rev, p1, *args, **kwargs): |
|
1071 """wrapper for rebase 's concludenode that set obsolete relation""" |
|
1072 newrev = orig(repo, rev, p1, *args, **kwargs) |
|
1073 rebasestate = getattr(repo, '_rebasestate', None) |
|
1074 if rebasestate is not None: |
|
1075 if newrev is not None: |
|
1076 nrev = repo[newrev].rev() |
|
1077 else: |
|
1078 nrev = p1 |
|
1079 repo._rebasestate[rev] = nrev |
|
1080 return newrev |
|
1081 |
|
1082 def cmdrebase(orig, ui, repo, *args, **kwargs): |
|
1083 |
|
1084 reallykeep = kwargs.get('keep', False) |
|
1085 kwargs = dict(kwargs) |
|
1086 kwargs['keep'] = True |
|
1087 repo._rebasekeep = reallykeep |
|
1088 |
|
1089 # We want to mark rebased revision as obsolete and set their |
|
1090 # replacements if any. Doing it in concludenode() prevents |
|
1091 # aborting the rebase, and is not called with all relevant |
|
1092 # revisions in --collapse case. Instead, we try to track the |
|
1093 # rebase state structure by sampling/updating it in |
|
1094 # defineparents() and concludenode(). The obsolete markers are |
|
1095 # added from this state after a successful call. |
|
1096 repo._rebasestate = {} |
|
1097 repo._rebasetarget = None |
|
1098 try: |
|
1099 l = repo.lock() |
|
1100 try: |
|
1101 res = orig(ui, repo, *args, **kwargs) |
|
1102 if not reallykeep: |
|
1103 # Filter nullmerge or unrebased entries |
|
1104 repo._rebasestate = dict(p for p in repo._rebasestate.iteritems() |
|
1105 if p[1] >= 0) |
|
1106 if not res and not kwargs.get('abort') and repo._rebasestate: |
|
1107 # Rebased revisions are assumed to be descendants of |
|
1108 # targetrev. If a source revision is mapped to targetrev |
|
1109 # or to another rebased revision, it must have been |
|
1110 # removed. |
|
1111 markers = [] |
|
1112 if kwargs.get('collapse'): |
|
1113 # collapse assume revision disapear because they are all |
|
1114 # in the created revision |
|
1115 newrevs = set(repo._rebasestate.values()) |
|
1116 newrevs.remove(repo._rebasetarget) |
|
1117 if newrevs: |
|
1118 # we create new revision. |
|
1119 # A single one by --collapse design |
|
1120 assert len(newrevs) == 1 |
|
1121 new = tuple(repo[n] for n in newrevs) |
|
1122 else: |
|
1123 # every body died. no new changeset created |
|
1124 new = (repo[repo._rebasetarget],) |
|
1125 for rev, newrev in sorted(repo._rebasestate.items()): |
|
1126 markers.append((repo[rev], new)) |
|
1127 else: |
|
1128 # no collapse assume revision disapear because they are |
|
1129 # contained in parent |
|
1130 for rev, newrev in sorted(repo._rebasestate.items()): |
|
1131 markers.append((repo[rev], (repo[newrev],))) |
|
1132 createmarkers(repo, markers) |
|
1133 return res |
|
1134 finally: |
|
1135 l.release() |
|
1136 finally: |
|
1137 delattr(repo, '_rebasestate') |
|
1138 delattr(repo, '_rebasetarget') |
|
1139 |
790 |
1140 @eh.extsetup |
791 @eh.extsetup |
1141 def _rebasewrapping(ui): |
792 def _rebasewrapping(ui): |
1142 # warning about more obsolete |
793 # warning about more obsolete |
1143 try: |
794 try: |
1144 rebase = extensions.find('rebase') |
795 rebase = extensions.find('rebase') |
1145 if rebase: |
796 if rebase: |
1146 incore = getattr(rebase, 'obsolete', None) is not None |
|
1147 if not incore: |
|
1148 extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase) |
|
1149 extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) |
797 extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) |
1150 if not incore: |
|
1151 extensions.wrapfunction(rebase, 'buildstate', buildstate) |
|
1152 extensions.wrapfunction(rebase, 'defineparents', defineparents) |
|
1153 extensions.wrapfunction(rebase, 'concludenode', concludenode) |
|
1154 except KeyError: |
798 except KeyError: |
1155 pass # rebase not found |
799 pass # rebase not found |
1156 |
800 |
1157 |
801 |
1158 ##################################################################### |
802 ##################################################################### |