[PATCH 3 of 3] histedit: remove bootstrap dependencies on parentctx

Mateusz Kwapich mitrandir at fb.com
Thu Feb 5 15:59:16 CST 2015


# HG changeset patch
# User Mateusz Kwapich <mitrandir at fb.com>
# Date 1423170607 28800
#      Thu Feb 05 13:10:07 2015 -0800
# Node ID d313a7f719220b81759c09019d0766a9a8a01332
# Parent  9ea70194adf7d075f31044c082f70f7c436a392a
histedit: remove bootstrap dependencies on parentctx

To make histedit more robust to commits disappearing in between actions, let's
make bootstrapcontinue() not depend on the old parentctx existing.

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -719,29 +719,29 @@
     if os.path.exists(repo.sjoin('undo')):
         os.unlink(repo.sjoin('undo'))
 
-def gatherchildren(repo, ctx):
-    # is there any new commit between the expected parent and "."
-    #
-    # note: does not take non linear new change in account (but previous
-    #       implementation didn't used them anyway (issue3655)
-    newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
-    if ctx.node() != node.nullid:
-        if not newchildren:
-            # `ctx` should match but no result. This means that
-            # currentnode is not a descendant from ctx.
-            msg = _('%s is not an ancestor of working directory')
-            hint = _('use "histedit --abort" to clear broken state')
-            raise util.Abort(msg % ctx, hint=hint)
-        newchildren.pop(0)  # remove ctx
-    return newchildren
+def bootstrapcontinue(ui, state, opts):
+    repo, oldparentctxnode = state.repo, state.parentctxnode
+    action, rulenode = state.rules.pop(0)
+    rulenode = node.bin(rulenode)
+    rulectx = None
+    if rulenode in repo:
+        rulectx = repo[rulenode]
 
-def bootstrapcontinue(ui, state, opts):
-    repo, parentctxnode = state.repo, state.parentctxnode
-    parentctx = repo[parentctxnode]
-    action, currentnode = state.rules.pop(0)
-    ctx = repo[currentnode]
+    # Record new commits between the original parent and the wctx, if any.
+    newchildren = [c.node() for c in repo.set('(%n::. - %n)',
+        oldparentctxnode, oldparentctxnode)]
 
-    newchildren = gatherchildren(repo, parentctx)
+    reusectx = rulectx
+    if not reusectx and action in ('r', 'roll'):
+        # roll is just going to remove the commit anyway, so just use
+        # anything
+        reusectx = repo['.']
+    if not reusectx:
+        raise util.Abort(_("unable to commit pending changes - "
+            "previous commit %s is not available to reuse the commit "
+            "message") % node.short(rulenode))
+
+    parentctxnode = repo['.'].node()
 
     # Commit dirty working directory if necessary
     new = None
@@ -749,26 +749,26 @@
     if s.modified or s.added or s.removed or s.deleted:
         # prepare the message for the commit to comes
         if action in ('f', 'fold', 'r', 'roll'):
-            message = 'fold-temp-revision %s' % currentnode[:12]
+            message = 'fold-temp-revision %s' % node.short(rulenode)
         else:
-            message = ctx.description()
+            message = reusectx.description()
         editopt = action in ('e', 'edit', 'm', 'mess')
         canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
         editform = 'histedit.%s' % canonaction.get(action, action)
         editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
-        commit = commitfuncfor(repo, ctx)
-        new = commit(text=message, user=ctx.user(), date=ctx.date(),
-                     extra=ctx.extra(), editor=editor)
+        commit = commitfuncfor(repo, reusectx)
+        new = commit(text=message, user=reusectx.user(), date=reusectx.date(),
+                     extra=reusectx.extra(), editor=editor)
         if new is not None:
             newchildren.append(new)
 
     replacements = []
     # track replacements
-    if ctx.node() not in newchildren:
+    if rulenode not in newchildren:
         # note: new children may be empty when the changeset is dropped.
         # this happen e.g during conflicting pick where we revert content
         # to parent.
-        replacements.append((ctx.node(), tuple(newchildren)))
+        replacements.append((rulenode, tuple(newchildren)))
 
     if action in ('f', 'fold', 'r', 'roll'):
         if newchildren:
@@ -781,20 +781,26 @@
             if action in ('r', 'roll'):
                 foldopts = foldopts.copy()
                 foldopts['rollup'] = True
-            parentctx, repl = finishfold(ui, repo, parentctx, ctx.node(),
-                                         ctx.description(), new, foldopts,
-                                         newchildren)
+            foldintoctx = None
+            if oldparentctxnode in repo:
+                foldintoctx = repo[oldparentctxnode]
+            else:
+                foldintoctx = repo['.'].parents()[0]
+            parentctx, repl = finishfold(ui, repo, foldintoctx, rulenode,
+                                         reusectx.description(), new,
+                                         foldopts, newchildren)
+            parentctxnode = parentctx.node()
             replacements.extend(repl)
         else:
             # newchildren is empty if the fold did not result in any commit
             # this happen when all folded change are discarded during the
             # merge.
-            replacements.append((ctx.node(), (parentctx.node(),)))
+            replacements.append((rulenode, (parentctxnode,)))
     elif newchildren:
         # otherwise update "parentctx" before proceeding to further operation
-        parentctx = repo[newchildren[-1]]
+        parentctxnode = repo[newchildren[-1]].node()
 
-    state.parentctxnode = parentctx.node()
+    state.parentctxnode = parentctxnode
     state.replacements.extend(replacements)
 
     return state
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -112,35 +112,6 @@
   > EOF
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-Run on a revision not descendants of the initial parent
---------------------------------------------------------------------
-
-Test the message shown for inconsistent histedit state, which may be
-created (and forgotten) by Mercurial earlier than 2.7. This emulates
-Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
-temporarily.
-
-  $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
-  > edit 08d98a8350f3 4 five
-  > EOF
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  reverting alpha
-  Make changes as needed, you may commit or record as needed now.
-  When you are finished, run hg histedit --continue to resume.
-  [1]
-
-  $ mv .hg/histedit-state .hg/histedit-state.back
-  $ hg update --quiet --clean 2
-  $ mv .hg/histedit-state.back .hg/histedit-state
-
-  $ hg histedit --continue
-  abort: c8e68270e35a is not an ancestor of working directory
-  (use "histedit --abort" to clear broken state)
-  [255]
-
-  $ hg histedit --abort
-  $ hg update --quiet --clean
-
 Test that missing revisions are detected
 ---------------------------------------
 
diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
--- a/tests/test-histedit-edit.t
+++ b/tests/test-histedit-edit.t
@@ -343,3 +343,5 @@
   $ HGEDITOR=true hg histedit --continue
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
+
+
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -457,3 +457,50 @@
   abort: cannot edit history that contains merges
   [255]
   $ cd ..
+
+Run continue on amended parent
+------------------------------
+Test if histedit can succesfully continue after amending initial parent
+in the middle of histedit.
+
+  $ cat > $TESTTMP/histedit-edit-editor.py <<EOF
+  > #!/usr/bin/env python
+  > import sys
+  > filename = sys.argv[1]
+  > with open(filename) as f:
+  >     line1 = f.readline()
+  >     line2 = f.readline()
+  >     line3 = f.readline()
+  > with open(filename, "w") as f:
+  >     f.write(line1)
+  >     f.write(line2.replace('pick', 'edit'))
+  >     f.write(line3)
+  > EOF
+  $ chmod +x $TESTTMP/histedit-edit-editor.py
+
+  $ hg init histedittest
+  $ cd histedittest
+  $ echo "abc" > file1
+  $ echo "123" > file2
+  $ hg add file1 file2
+  $ hg ci -m "initial"
+  $ echo "def" >> file1
+  $ echo "foo" >> file2
+  $ hg ci -m "commit 1"
+  $ echo "456" >> file2
+  $ hg ci -m "commit 2"
+  $ HGEDITOR="$TESTTMP/histedit-edit-editor.py" hg histedit tip~2
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  reverting file1
+  reverting file2
+  Make changes as needed, you may commit or record as needed now.
+  When you are finished, run hg histedit --continue to resume.
+  [1]
+  $ hg commit --amend -m "initial" file1
+  $ hg histedit --continue --traceback --config extensions.histedit=/data/users/mitrandir/hg/hgext/histedit.py
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log
+  6:911fe08e3eea (draft) commit 2
+  5:77b81cdf6a31 (draft) commit 1
+  4:d133778d3f09 (draft) initial


More information about the Mercurial-devel mailing list