diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -16,6 +16,8 @@ import socket from .i18n import _ from .node import ( + hex, + nullid, wdirid, wdirrev, ) @@ -24,6 +26,7 @@ from . import ( encoding, error, match as matchmod, + obsolete, pathutil, phases, pycompat, @@ -561,6 +564,68 @@ def origpath(ui, repo, filepath): return fullorigpath + ".orig" +def cleanupnodes(repo, mapping, operation): + """do common cleanups when old nodes are replaced by new nodes + + That includes writing obsmarkers or stripping nodes, and moving bookmarks. + (we might also want to move working directory parent in the future) + + mapping is {oldnode: [newnode]} or a iterable of nodes if they do not have + replacements. operation is a string, like "rebase". + """ + if not util.safehasattr(mapping, 'items'): + mapping = {n: () for n in mapping} + + with repo.transaction('cleanup') as tr: + # Move bookmarks + bmarks = repo._bookmarks + bmarkchanged = False + for oldnode, newnodes in mapping.items(): + oldbmarks = repo.nodebookmarks(oldnode) + if not oldbmarks: + continue + bmarkchanged = True + if len(newnodes) > 1: + heads = list(repo.set('heads(%ln)', newnodes)) + if len(heads) != 1: + raise error.ProgrammingError( + 'cannot figure out bookmark movement') + newnode = heads[0].node() + elif len(newnodes) == 0: + # move bookmark backwards + roots = list(repo.set('max((::%n) - %ln)', oldnode, + list(mapping))) + if roots: + newnode = roots[0].node() + else: + newnode = nullid + else: + newnode = newnodes[0] + repo.ui.debug('moving bookmarks %r from %s to %s\n' % + (oldbmarks, hex(oldnode), hex(newnode))) + for name in oldbmarks: + bmarks[name] = newnode + if bmarkchanged: + bmarks.recordchange(tr) + + # Obsolete or strip nodes + if obsolete.isenabled(repo, obsolete.createmarkersopt): + # If a node is already obsoleted, and we want to obsolete it + # without a successor, skip that obssolete request since it's + # unnecessary. That's the "if s or not isobs(n)" check below. + # Also sort the node in topology order, that might be useful for + # some obsstore logic. + # NOTE: the filtering and sorting might belong to createmarkers. + isobs = repo.obsstore.successors.__contains__ + sortfunc = lambda ns: repo.changelog.rev(ns[0]) + rels = [(repo[n], (repo[m] for m in s)) + for n, s in sorted(mapping.items(), key=sortfunc) + if s or not isobs(n)] + obsolete.createmarkers(repo, rels, operation=operation) + else: + from . import repair # avoid import cycle + repair.delayedstrip(repo.ui, repo, list(mapping), operation) + def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None): if opts is None: opts = {} diff --git a/tests/test-strip.t b/tests/test-strip.t --- a/tests/test-strip.t +++ b/tests/test-strip.t @@ -955,6 +955,7 @@ Use delayedstrip to strip inside a trans > \|/ > A Z > EOS + $ cp -R . ../scmutilcleanup $ hg up -C I 2 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -990,3 +991,104 @@ Use delayedstrip to strip inside a trans |/ o 0:426bada5c675 A +Test high-level scmutil.cleanupnodes API + + $ cd $TESTTMP/scmutilcleanup + $ hg debugdrawdag <<'EOS' + > D2 F2 G2 # D2, F2, G2 are replacements for D, F, G + > | | | + > C H G + > EOS + $ for i in B C D F G I Z; do + > hg bookmark -i -r $i b-$i + > done + $ cp -R . ../scmutilcleanup.obsstore + + $ cat > $TESTTMP/scmutilcleanup.py < from mercurial import scmutil + > def reposetup(ui, repo): + > def nodes(expr): + > return [repo.changelog.node(r) for r in repo.revs(expr)] + > def node(expr): + > return nodes(expr)[0] + > with repo.wlock(): + > with repo.lock(): + > with repo.transaction('delayedstrip'): + > mapping = {node('F'): [node('F2')], + > node('D'): [node('D2')], + > node('G'): [node('G2')]} + > scmutil.cleanupnodes(repo, mapping, 'replace') + > scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace') + > EOF + $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py + warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60 + saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob) + + $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)' + o 8:1473d4b996d1 G2 b-G + | + | o 7:d94e89b773b6 F2 b-F + | | + | o 5:7fe5bac4c918 H + |/| + | o 3:7fb047a69f22 E + | | + | | o 6:7c78f703e465 D2 b-D + | | | + | | o 4:26805aba1e60 C + | | | + | | o 2:112478962961 B + | |/ + o | 1:1fc8102cda62 G + / + o 0:426bada5c675 A b-B b-C b-I + + $ hg bookmark + b-B 0:426bada5c675 + b-C 0:426bada5c675 + b-D 6:7c78f703e465 + b-F 7:d94e89b773b6 + b-G 8:1473d4b996d1 + b-I 0:426bada5c675 + b-Z -1:000000000000 + +Test the above using obsstore "by the way". Not directly related to strip, but +we have reusable code here + + $ cd $TESTTMP/scmutilcleanup.obsstore + $ cat >> .hg/hgrc < [experimental] + > evolution=all + > evolution.track-operation=1 + > EOF + + $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py + + $ rm .hg/localtags + $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)' + o 12:1473d4b996d1 G2 b-G + | + | o 11:d94e89b773b6 F2 b-F + | | + | o 8:7fe5bac4c918 H + |/| + | o 4:7fb047a69f22 E + | | + | | o 10:7c78f703e465 D2 b-D + | | | + | | x 6:26805aba1e60 C + | | | + | | x 3:112478962961 B + | |/ + x | 1:1fc8102cda62 G + / + o 0:426bada5c675 A b-B b-C b-I + + $ hg debugobsolete + 1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'} + 64a8289d249234b9886244d379f15e6b650b28e3 d94e89b773b67e72642a931159ada8d1a9246998 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'} + f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'} + 48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'} + 112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'} + 08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'} + 26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}