histedit: replaces patching logic by merges
authorPierre-Yves David <pierre-yves.david@ens-lyon.org>
Fri, 21 Sep 2012 19:27:22 +0200
changeset 17647 d34ba4991188
parent 17646 d44731a3adb8
child 17648 07f1ac17b722
histedit: replaces patching logic by merges The old and fragile patching logic is replaced by smart merges (as rebase and graft do). This should prevents some conflicts and smoother human resolution. For this purpose the "foldchanges" function is renamed to "applychanges" and handle a single revision only.
hgext/histedit.py
tests/test-histedit-fold-non-commute.t
tests/test-histedit-fold.t
tests/test-histedit-non-commute-abort.t
tests/test-histedit-non-commute.t
--- a/hgext/histedit.py	Fri Sep 21 19:13:25 2012 +0200
+++ b/hgext/histedit.py	Fri Sep 21 19:27:22 2012 +0200
@@ -142,7 +142,6 @@
     import cPickle as pickle
 except ImportError:
     import pickle
-import tempfile
 import os
 
 from mercurial import bookmarks
@@ -154,10 +153,10 @@
 from mercurial import hg
 from mercurial import lock as lockmod
 from mercurial import node
-from mercurial import patch
 from mercurial import repair
 from mercurial import scmutil
 from mercurial import util
+from mercurial import merge as mergemod
 from mercurial.i18n import _
 
 cmdtable = {}
@@ -177,25 +176,27 @@
 #
 """)
 
-def foldchanges(ui, repo, node1, node2, opts):
-    """Produce a new changeset that represents the diff from node1 to node2."""
-    try:
-        fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
-        fp = os.fdopen(fd, 'w')
-        diffopts = patch.diffopts(ui, opts)
-        diffopts.git = True
-        diffopts.ignorews = False
-        diffopts.ignorewsamount = False
-        diffopts.ignoreblanklines = False
-        gen = patch.diff(repo, node1, node2, opts=diffopts)
-        for chunk in gen:
-            fp.write(chunk)
-        fp.close()
-        files = set()
-        patch.patch(ui, repo, patchfile, files=files, eolmode=None)
-    finally:
-        os.unlink(patchfile)
-    return files
+def applychanges(ui, repo, ctx, opts):
+    """Merge changeset from ctx (only) in the current working directory"""
+    wcpar = repo.dirstate.parents()[0]
+    if ctx.p1().node() == wcpar:
+        # edition ar "in place" we do not need to make any merge,
+        # just applies changes on parent for edition
+        cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
+        stats = None
+    else:
+        try:
+            # ui.forcemerge is an internal variable, do not document
+            repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+            stats = mergemod.update(repo, ctx.node(), True, True, False,
+                                    ctx.p1().node())
+        finally:
+            repo.ui.setconfig('ui', 'forcemerge', '')
+        repo.setparents(wcpar, node.nullid)
+        repo.dirstate.write()
+        # fix up dirstate for copies and renames
+    cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
+    return stats
 
 def collapse(repo, first, last, commitopts):
     """collapse the set of revisions from first to last as new one.
@@ -273,27 +274,24 @@
         ui.debug('node %s unchanged\n' % ha)
         return oldctx, [], [], []
     hg.update(repo, ctx.node())
-    try:
-        files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
-        if not files:
-            ui.warn(_('%s: empty changeset')
-                         % node.hex(ha))
-            return ctx, [], [], []
-    except Exception:
+    stats = applychanges(ui, repo, oldctx, opts)
+    if stats and stats[3] > 0:
         raise util.Abort(_('Fix up the change and run '
                            'hg histedit --continue'))
+    # drop the second merge parent
     n = repo.commit(text=oldctx.description(), user=oldctx.user(),
                     date=oldctx.date(), extra=oldctx.extra())
+    if n is None:
+        ui.warn(_('%s: empty changeset\n')
+                     % node.hex(ha))
+        return ctx, [], [], []
     return repo[n], [n], [oldctx.node()], []
 
 
 def edit(ui, repo, ctx, ha, opts):
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
-    try:
-        foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
-    except Exception:
-        pass
+    applychanges(ui, repo, oldctx, opts)
     raise util.Abort(_('Make changes as needed, you may commit or record as '
                        'needed now.\nWhen you are finished, run hg'
                        ' histedit --continue to resume.'))
@@ -301,17 +299,16 @@
 def fold(ui, repo, ctx, ha, opts):
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
-    try:
-        files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
-        if not files:
-            ui.warn(_('%s: empty changeset')
-                         % node.hex(ha))
-            return ctx, [], [], []
-    except Exception:
+    stats = applychanges(ui, repo, oldctx, opts)
+    if stats and stats[3] > 0:
         raise util.Abort(_('Fix up the change and run '
                            'hg histedit --continue'))
     n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
                     date=oldctx.date(), extra=oldctx.extra())
+    if n is None:
+        ui.warn(_('%s: empty changeset')
+                     % node.hex(ha))
+        return ctx, [], [], []
     return finishfold(ui, repo, ctx, oldctx, n, opts, [])
 
 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
@@ -346,9 +343,8 @@
 def message(ui, repo, ctx, ha, opts):
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
-    try:
-        foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
-    except Exception:
+    stats = applychanges(ui, repo, oldctx, opts)
+    if stats and stats[3] > 0:
         raise util.Abort(_('Fix up the change and run '
                            'hg histedit --continue'))
     message = oldctx.description() + '\n'
--- a/tests/test-histedit-fold-non-commute.t	Fri Sep 21 19:13:25 2012 +0200
+++ b/tests/test-histedit-fold-non-commute.t	Fri Sep 21 19:27:22 2012 +0200
@@ -88,13 +88,14 @@
 edit the history
   $ HGEDITOR="cat \"$EDITED\" > " hg histedit 3 2>&1 | fixbundle
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
 fix up
-  $ echo a > e
+  $ echo 'I can haz no commute' > e
+  $ hg resolve --mark e
   $ cat > cat.py <<EOF
   > import sys
   > print open(sys.argv[1]).read()
@@ -121,25 +122,27 @@
   
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
 just continue this time
+  $ hg revert -r 'p1()' e
+  $ hg resolve --mark e
   $ hg histedit --continue 2>&1 | fixbundle
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 log after edit
   $ hg log --graph
-  @  changeset:   5:45bd04206744
+  @  changeset:   5:2696a654c663
   |  tag:         tip
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     f
   |
-  o  changeset:   4:abff6367c13a
+  o  changeset:   4:ec2c1cf833a8
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     d
@@ -167,7 +170,7 @@
 
 contents of e
   $ hg cat e
-  a
+  I can haz no commute
 
 manifest
   $ hg manifest
--- a/tests/test-histedit-fold.t	Fri Sep 21 19:13:25 2012 +0200
+++ b/tests/test-histedit-fold.t	Fri Sep 21 19:27:22 2012 +0200
@@ -157,16 +157,21 @@
 
   $ HGEDITOR='python editor.py' hg histedit 1
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file file
-  Hunk #1 FAILED at 2
-  1 out of 1 hunks FAILED -- saving rejects to file file.rej
+  merging file
+  warning: conflicts during merge.
+  merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
   [255]
-There were conflicts, but we'll continue without resolving. This
+There were conflicts, we keep P1 content. This
 should effectively drop the changes from +6.
   $ hg status
+  M file
   ? editor.py
-  ? file.rej
+  ? file.orig
+  $ hg resolve -l
+  U file
+  $ hg revert -r 'p1()' file
+  $ hg resolve --mark file
   $ hg histedit --continue
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/*-backup.hg (glob)
@@ -217,12 +222,19 @@
   > EOF
   $ HGEDITOR="cat $EDITED >" hg histedit 1
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file file
-  Hunk #1 FAILED at 2
-  1 out of 1 hunks FAILED -- saving rejects to file file.rej
+  merging file
+  warning: conflicts during merge.
+  merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
   [255]
-  $ echo 5 >> file
+  $ cat > file << EOF
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > EOF
+  $ hg resolve --mark file
   $ hg commit -m '+5.2'
   created new head
   $ echo 6 >> file
--- a/tests/test-histedit-non-commute-abort.t	Fri Sep 21 19:13:25 2012 +0200
+++ b/tests/test-histedit-non-commute-abort.t	Fri Sep 21 19:27:22 2012 +0200
@@ -73,23 +73,21 @@
 edit the history
   $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  remote changed e which local deleted
+  use (c)hanged version or leave (d)eleted? c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
-fix up (pre abort)
-  $ echo a > e
-  $ hg add e
-  $ hg histedit --continue 2>&1 | fixbundle
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  file e already exists
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
-  abort: Fix up the change and run hg histedit --continue
 
 abort the edit
   $ hg histedit --abort 2>&1 | fixbundle
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 log after abort
+  $ hg resolve -l
   $ hg log --graph
   @  changeset:   6:bfa474341cc9
   |  tag:         tip
--- a/tests/test-histedit-non-commute.t	Fri Sep 21 19:13:25 2012 +0200
+++ b/tests/test-histedit-non-commute.t	Fri Sep 21 19:27:22 2012 +0200
@@ -89,9 +89,9 @@
 edit the history
   $ HGEDITOR="cat \"$EDITED\" > " hg histedit 3 2>&1 | fixbundle
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
 abort the edit
@@ -147,21 +147,24 @@
 edit the history
   $ HGEDITOR="cat \"$EDITED\" > " hg histedit 3 2>&1 | fixbundle
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
 fix up
   $ echo 'I can haz no commute' > e
+  $ hg resolve --mark e
   $ hg histedit --continue 2>&1 | fixbundle
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
 just continue this time
+  $ hg revert -r 'p1()' e
+  $ hg resolve --mark e
   $ hg histedit --continue 2>&1 | fixbundle
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -227,19 +230,22 @@
 edit the history, this time with a fold action
   $ HGEDITOR="cat \"$EDITED\" > " hg histedit 3 2>&1 | fixbundle
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 
   $ echo 'I can haz no commute' > e
+  $ hg resolve --mark e
   $ HGEDITOR="cat \"$EDITED\" > " hg histedit --continue 2>&1 | fixbundle
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  patching file e
-  Hunk #1 FAILED at 0
-  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
   abort: Fix up the change and run hg histedit --continue
 second edit also fails, but just continue
+  $ hg revert -r 'p1()' e
+  $ hg resolve --mark e
   $ hg histedit --continue 2>&1 | fixbundle
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved