diff hgext/histedit.py @ 17647:d34ba4991188

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.
author Pierre-Yves David <pierre-yves.david@ens-lyon.org>
date Fri, 21 Sep 2012 19:27:22 +0200
parents 4721fc933943
children 4f2390e3f4b0
line wrap: on
line diff
--- 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'