Commits

Augie Fackler committed aea051d

histedit: preserve bookmark locations during history edits

Comments (0)

Files changed (2)

 import tempfile
 import os
 
+try:
+    from mercurial import bookmarks
+    bookmarks.write
+except ImportError:
+    bookmarks = None
 from mercurial import cmdutil
 from mercurial import error
 from mercurial import hg
         if len(parent) != 0:
             raise util.Abort('no arguments allowed with --continue')
         (parentctxnode, created, replaced,
-         tmpnodes, existing, rules, keep, tip, ) = readstate(repo)
+         tmpnodes, existing, rules, keep, tip, replacemap ) = readstate(repo)
         currentparent, wantnull = repo.dirstate.parents()
         parentctx = repo[parentctxnode]
         # discover any nodes the user has added in the interim
         if len(parent) != 0:
             raise util.Abort('no arguments allowed with --abort')
         (parentctxnode, created, replaced, tmpnodes,
-         existing, rules, keep, tip, ) = readstate(repo)
+         existing, rules, keep, tip, replacemap) = readstate(repo)
         ui.debug('restore wc to old tip %s\n' % node.hex(tip))
         hg.clean(repo, tip)
         ui.debug('should strip created nodes %s\n' %
         parentctx = repo[parent].parents()[0]
         keep = opts.get('keep', False)
         replaced = []
+        replacemap = {}
         tmpnodes = []
         created = []
 
 
     while rules:
         writestate(repo, parentctx.node(), created, replaced, tmpnodes, existing,
-                   rules, keep, tip)
+                   rules, keep, tip, replacemap)
         action, ha = rules.pop(0)
         (parentctx, created_,
          replaced_, tmpnodes_, ) = actiontable[action](ui, repo,
                                                        parentctx, ha,
                                                        opts)
+
+        hexshort = lambda x: node.hex(x)[:12]
+
+        if replaced_:
+            clen, rlen = len(created_), len(replaced_)
+            if clen == rlen == 1:
+                ui.debug('histedit: exact replacement of %s with %s\n' % (
+                    hexshort(replaced_[0]), hexshort(created_[0])))
+
+                replacemap[replaced_[0]] = created_[0]
+            elif clen > rlen:
+                assert rlen == 1, ('unexpected replacement of '
+                                   '%d changes with %d changes' % (rlen, clen))
+                # made more changesets than we're replacing
+                # TODO synthesize patch names for created patches
+                replacemap[replaced_[0]] = created_[-1]
+                ui.debug('histedit: created many, assuming %s replaced by %s' % (
+                    hexshort(replaced_[0]), hexshort(created_[-1])))
+            elif rlen > clen:
+                if not created_:
+                    # This must be a drop. Try and put our metadata on
+                    # the parent change.
+                    assert rlen == 1
+                    r = replaced_[0]
+                    ui.debug('histedit: %s seems replaced with nothing, '
+                            'finding a parent\n' % (hexshort(r)))
+                    pctx = repo[r].parents()[0]
+                    if pctx.node() in replacemap:
+                        ui.debug('histedit: parent is already replaced\n')
+                        replacemap[r] = replacemap[pctx.node()]
+                    else:
+                        replacemap[r] = pctx.node()
+                    ui.debug('histedit: %s best replaced by %s\n' % (
+                        hexshort(r), hexshort(replacemap[r])))
+                else:
+                    assert len(created_) == 1
+                    for r in replaced_:
+                        ui.debug('histedit: %s replaced by %s\n' % (
+                            hexshort(r), hexshort(created_[0])))
+                        replacemap[r] = created_[0]
+            else:
+                assert False, (
+                    'Unhandled case in replacement mapping! '
+                    'replacing %d changes with %d changes' % (rlen, clen))
         created.extend(created_)
         replaced.extend(replaced_)
         tmpnodes.extend(tmpnodes_)
     hg.update(repo, parentctx.node())
 
     if not keep:
+        if replacemap:
+            ui.note('histedit: Should update metadata for the following '
+                    'changes:\n')
+            for old, new in replacemap.iteritems():
+                if old in tmpnodes or old in created:
+                    # can't have any metadata we'd want to update
+                    continue
+                while new in replacemap:
+                    new = replacemap[new]
+                ui.note('histedit:  %s to %s\n' % (hexshort(old), hexshort(new)))
+                octx = repo[old]
+                if bookmarks is not None:
+                    marks = octx.bookmarks()
+                    if marks:
+                        ui.note('histedit:     moving bookmarks %s\n' %
+                                ', '.join(marks))
+                        for mark in marks:
+                            repo._bookmarks[mark] = new
+                        bookmarks.write(repo)
+                # TODO update mq state
+
         ui.debug('should strip replaced nodes %s\n' %
                  ', '.join([node.hex(n)[:12] for n in replaced]))
         for n in sorted(replaced, key=lambda x: repo[x].rev()):
 
 
 def writestate(repo, parentctxnode, created, replaced,
-               tmpnodes, existing, rules, keep, oldtip):
+               tmpnodes, existing, rules, keep, oldtip, replacemap):
     fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
     pickle.dump((parentctxnode, created, replaced,
-                 tmpnodes, existing, rules, keep, oldtip,),
+                 tmpnodes, existing, rules, keep, oldtip, replacemap),
                 fp)
     fp.close()
 
 def readstate(repo):
-    """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules, keep, oldtip, ).
+    """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
+                           keep, oldtip, replacemap ).
     """
     fp = open(os.path.join(repo.path, 'histedit-state'))
     return pickle.load(fp)

tests/test-histedit-bookmark-motion.t

+  $ . "$TESTDIR/histedit-helpers.sh"
+
+  $  cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+
+  $ echo "histedit=$(echo $(dirname $TESTDIR))/hg_histedit.py" >> $HGRCPATH
+  $ hg init r
+  $ cd r
+See if bookmarks are in core. If not, then we don't support bookmark
+motion on this version of hg.
+  $ hg bookmarks || exit 80
+  no bookmarks set
+  $ for x in a b c d e f ; do
+  >     echo $x > $x
+  >     hg add $x
+  >     hg ci -m $x
+  > done
+
+  $ hg book -r 1 will-move-backwards
+  $ hg book -r 2 two
+  $ hg book -r 2 also-two
+  $ hg book -r 3 three
+  $ hg book -r 4 four
+  $ hg book -r tip five
+  $ hg log --graph
+  @  changeset:   5:652413bf663e
+  |  bookmark:    five
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   4:e860deea161a
+  |  bookmark:    four
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     e
+  |
+  o  changeset:   3:055a42cdd887
+  |  bookmark:    three
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   2:177f92b77385
+  |  bookmark:    also-two
+  |  bookmark:    two
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   1:d2ae7f538514
+  |  bookmark:    will-move-backwards
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   0:cb9a9f314b8b
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+  
+  $ HGEDITOR=cat hg histedit 1
+  pick d2ae7f538514 1 b
+  pick 177f92b77385 2 c
+  pick 055a42cdd887 3 d
+  pick e860deea161a 4 e
+  pick 652413bf663e 5 f
+  
+  # Edit history between d2ae7f538514 and 652413bf663e
+  #
+  # Commands:
+  #  p, pick = use commit
+  #  e, edit = use commit, but stop for amending
+  #  f, fold = use commit, but fold into previous commit (combines N and N-1)
+  #  d, drop = remove commit from history
+  #  m, mess = edit message without changing commit content
+  #
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat >> commands.txt <<EOF
+  > pick 177f92b77385 2 c
+  > drop d2ae7f538514 1 b
+  > pick 055a42cdd887 3 d
+  > fold e860deea161a 4 e
+  > pick 652413bf663e 5 f
+  > EOF
+  $ hg histedit 1 --commands commands.txt --verbose | grep histedit
+  histedit: Should update metadata for the following changes:
+  histedit:  055a42cdd887 to ae467701c500
+  histedit:     moving bookmarks three
+  histedit:  652413bf663e to 0efacef7cb48
+  histedit:     moving bookmarks five
+  histedit:  d2ae7f538514 to cb9a9f314b8b
+  histedit:     moving bookmarks will-move-backwards
+  histedit:  e860deea161a to ae467701c500
+  histedit:     moving bookmarks four
+  histedit:  177f92b77385 to d36c0562f908
+  histedit:     moving bookmarks also-two, two
+  saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-backup.hg
+  saved backup bundle to $TESTTMP/r/.hg/strip-backup/34a9919932c1-backup.hg
+  $ hg log --graph
+  @  changeset:   3:0efacef7cb48
+  |  bookmark:    five
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   2:ae467701c500
+  |  bookmark:    four
+  |  bookmark:    three
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   1:d36c0562f908
+  |  bookmark:    also-two
+  |  bookmark:    two
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   0:cb9a9f314b8b
+     bookmark:    will-move-backwards
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+