patch: offer the user a chance to fix the patch if it fails to apply
authorPierre-Yves David <pierre-yves.david@octobus.net>
Wed, 11 Apr 2018 07:23:34 +0200
changeset 3661 61fdd25542a6
parent 3660 f018656ca3bf
child 3662 0caa27259800
patch: offer the user a chance to fix the patch if it fails to apply The experience is still not great, but better than purely dropping the changes.
hgext3rd/evolve/cmdrewrite.py
tests/test-amend-patch.t
--- a/hgext3rd/evolve/cmdrewrite.py	Sun Mar 18 23:48:06 2018 +0530
+++ b/hgext3rd/evolve/cmdrewrite.py	Wed Apr 11 07:23:34 2018 +0200
@@ -168,24 +168,7 @@
                                          match=matcher,
                                          opts=diffopts):
                 fp.write(chunk)
-
-        fp.seek(0)
-        newpatch = ui.edit(fp.getvalue(), old.user(), action="diff")
-
-        afp = stringio()
-        afp.write(newpatch)
-        if pats:
-            # write rest of the files in the patch
-            restmatcher = scmutil.match(old, [], opts={'exclude': pats})
-            for chunk, label in patch.diffui(repo, p1.node(), old.node(),
-                                             match=restmatcher,
-                                             opts=diffopts):
-                    afp.write(chunk)
-
-        afp.seek(0)
-        # write the patch to repo and get the newnode
-        newnode = _writepatch(ui, repo, old, afp)
-
+        newnode = _editandapply(ui, repo, pats, old, p1, fp, diffopts)
         if newnode == old.node():
             raise error.Abort(_("nothing changed"))
         metadata = {}
@@ -201,6 +184,44 @@
         tr.release()
         lockmod.release(lock, wlock)
 
+def _editandapply(ui, repo, pats, old, p1, fp, diffopts):
+    RETRYCHOICE = _('try to fix the patch (yn)?$$ &Yes $$ &No')
+    newnode = None
+    while newnode is None:
+        fp.seek(0)
+        previous_patch = fp.getvalue()
+        newpatch = ui.edit(fp.getvalue(), old.user(), action="diff")
+
+        afp = stringio()
+        afp.write(newpatch)
+        if pats:
+            # write rest of the files in the patch
+            restmatcher = scmutil.match(old, [], opts={'exclude': pats})
+            for chunk, label in patch.diffui(repo, p1.node(), old.node(),
+                                             match=restmatcher,
+                                             opts=diffopts):
+                    afp.write(chunk)
+
+        user_patch = afp.getvalue()
+        if user_patch == previous_patch:
+            raise error.Abort(_("patch unchanged"))
+        afp.seek(0)
+        # write the patch to repo and get the newnode
+        try:
+            newnode = _writepatch(ui, repo, old, afp)
+        except patch.PatchError as err:
+            ui.write_err(_("failed to apply edited patch: %s\n") % err)
+            defaultchoice = 0 # yes
+            if not ui.interactive:
+                defaultchoice = 1 # no
+            if ui.promptchoice(RETRYCHOICE, default=defaultchoice):
+                raise error.Abort(_("Could not apply amended path"))
+            else:
+                # consider a third choice where we restore the original patch
+                fp = stringio()
+                fp.write(user_patch)
+    return newnode
+
 def _writepatch(ui, repo, old, fp):
     """utility function to use filestore and patchrepo to apply a patch to the
     repository with metadata being extracted from the patch"""
@@ -219,14 +240,10 @@
     fp.seek(0)
     try:
         files = set()
-        try:
-            patch.patchrepo(ui, repo, pold, store, fp, 1, '',
-                            files=files, eolmode=None)
-        except patch.PatchError as err:
-            raise error.Abort(str(err))
-
-        finally:
-            del fp
+        # beware: next line may raise a PatchError to be handled by the caller
+        # of this function
+        patch.patchrepo(ui, repo, pold, store, fp, 1, '',
+                        files=files, eolmode=None)
 
         memctx = context.memctx(repo, parents, message, files=files,
                                 filectxfn=store,
--- a/tests/test-amend-patch.t	Sun Mar 18 23:48:06 2018 +0530
+++ b/tests/test-amend-patch.t	Wed Apr 11 07:23:34 2018 +0200
@@ -139,8 +139,10 @@
   > EOF
 
   $ HGEDITOR="sh ./editor.sh" hg amend --patch
-  abort: bad hunk #1 @@ -0,0 +1,1 @@
+  failed to apply edited patch: bad hunk #1 @@ -0,0 +1,1 @@
    (1 0 1 1)
+  try to fix the patch (yn)? y
+  abort: patch unchanged
   [255]
 
 Having deletions which dont exists
@@ -166,8 +168,10 @@
   > EOF
 
   $ HGEDITOR="sh ./editor.sh" hg amend --patch
-  abort: bad hunk #1 @@ -0,0 +1,1 @@
+  failed to apply edited patch: bad hunk #1 @@ -0,0 +1,1 @@
    (1 0 1 1)
+  try to fix the patch (yn)? y
+  abort: patch unchanged
   [255]
 
 Changing the file mode using amend --patch
@@ -325,7 +329,9 @@
   $ HGEDITOR="sh ./editor.sh" hg amend --patch
   patching file changedfile
   Hunk #1 FAILED at 0
-  abort: patch failed to apply
+  failed to apply edited patch: patch failed to apply
+  try to fix the patch (yn)? y
+  abort: patch unchanged
   [255]
 
 Add more addition to the patch
@@ -733,7 +739,7 @@
   @@ -1,2 +0,0 @@
   -foobar
   -babar
-  abort: nothing changed
+  abort: patch unchanged
   [255]
 
   $ HGEDITOR=cat hg amend --patch doesnotexists