Patchwork [1,of,4] record: move filterpatch from record to patch

login
register
mail settings
Submitter Laurent Charignon
Date March 11, 2015, 2:24 a.m.
Message ID <5860dbae11e639d9fcc6.1426040668@dev919.prn2.facebook.com>
Download mbox | patch
Permalink /patch/7990/
State Accepted
Headers show

Comments

Laurent Charignon - March 11, 2015, 2:24 a.m.
# HG changeset patch
# User Laurent Charignon <lcharignon@fb.com>
# Date 1426023727 25200
#      Tue Mar 10 14:42:07 2015 -0700
# Node ID 5860dbae11e639d9fcc622f68336e658fcce1330
# Parent  857eaa5f7d34d08b5af9a499e3888d6114459931
record: move filterpatch from record to patch

Part of a serie of patches to move record from hgext to core

Patch

diff --git a/hgext/record.py b/hgext/record.py
--- a/hgext/record.py
+++ b/hgext/record.py
@@ -10,164 +10,12 @@ 
 from mercurial.i18n import _
 from mercurial import cmdutil, commands, extensions, hg, patch
 from mercurial import util
-import copy, cStringIO, errno, os, shutil, tempfile
+import cStringIO, errno, os, shutil, tempfile
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 testedwith = 'internal'
 
-def filterpatch(ui, headers):
-    """Interactively filter patch chunks into applied-only chunks"""
-
-    def prompt(skipfile, skipall, query, chunk):
-        """prompt query, and process base inputs
-
-        - y/n for the rest of file
-        - y/n for the rest
-        - ? (help)
-        - q (quit)
-
-        Return True/False and possibly updated skipfile and skipall.
-        """
-        newpatches = None
-        if skipall is not None:
-            return skipall, skipfile, skipall, newpatches
-        if skipfile is not None:
-            return skipfile, skipfile, skipall, newpatches
-        while True:
-            resps = _('[Ynesfdaq?]'
-                      '$$ &Yes, record this change'
-                      '$$ &No, skip this change'
-                      '$$ &Edit this change manually'
-                      '$$ &Skip remaining changes to this file'
-                      '$$ Record remaining changes to this &file'
-                      '$$ &Done, skip remaining changes and files'
-                      '$$ Record &all changes to all remaining files'
-                      '$$ &Quit, recording no changes'
-                      '$$ &? (display help)')
-            r = ui.promptchoice("%s %s" % (query, resps))
-            ui.write("\n")
-            if r == 8: # ?
-                for c, t in ui.extractchoices(resps)[1]:
-                    ui.write('%s - %s\n' % (c, t.lower()))
-                continue
-            elif r == 0: # yes
-                ret = True
-            elif r == 1: # no
-                ret = False
-            elif r == 2: # Edit patch
-                if chunk is None:
-                    ui.write(_('cannot edit patch for whole file'))
-                    ui.write("\n")
-                    continue
-                if chunk.header.binary():
-                    ui.write(_('cannot edit patch for binary file'))
-                    ui.write("\n")
-                    continue
-                # Patch comment based on the Git one (based on comment at end of
-                # http://mercurial.selenic.com/wiki/RecordExtension)
-                phelp = '---' + _("""
-To remove '-' lines, make them ' ' lines (context).
-To remove '+' lines, delete them.
-Lines starting with # will be removed from the patch.
-
-If the patch applies cleanly, the edited hunk will immediately be
-added to the record list. If it does not apply cleanly, a rejects
-file will be generated: you can use that when you try again. If
-all lines of the hunk are removed, then the edit is aborted and
-the hunk is left unchanged.
-""")
-                (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
-                        suffix=".diff", text=True)
-                ncpatchfp = None
-                try:
-                    # Write the initial patch
-                    f = os.fdopen(patchfd, "w")
-                    chunk.header.write(f)
-                    chunk.write(f)
-                    f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
-                    f.close()
-                    # Start the editor and wait for it to complete
-                    editor = ui.geteditor()
-                    ui.system("%s \"%s\"" % (editor, patchfn),
-                              environ={'HGUSER': ui.username()},
-                              onerr=util.Abort, errprefix=_("edit failed"))
-                    # Remove comment lines
-                    patchfp = open(patchfn)
-                    ncpatchfp = cStringIO.StringIO()
-                    for line in patchfp:
-                        if not line.startswith('#'):
-                            ncpatchfp.write(line)
-                    patchfp.close()
-                    ncpatchfp.seek(0)
-                    newpatches = patch.parsepatch(ncpatchfp)
-                finally:
-                    os.unlink(patchfn)
-                    del ncpatchfp
-                # Signal that the chunk shouldn't be applied as-is, but
-                # provide the new patch to be used instead.
-                ret = False
-            elif r == 3: # Skip
-                ret = skipfile = False
-            elif r == 4: # file (Record remaining)
-                ret = skipfile = True
-            elif r == 5: # done, skip remaining
-                ret = skipall = False
-            elif r == 6: # all
-                ret = skipall = True
-            elif r == 7: # quit
-                raise util.Abort(_('user quit'))
-            return ret, skipfile, skipall, newpatches
-
-    seen = set()
-    applied = {}        # 'filename' -> [] of chunks
-    skipfile, skipall = None, None
-    pos, total = 1, sum(len(h.hunks) for h in headers)
-    for h in headers:
-        pos += len(h.hunks)
-        skipfile = None
-        fixoffset = 0
-        hdr = ''.join(h.header)
-        if hdr in seen:
-            continue
-        seen.add(hdr)
-        if skipall is None:
-            h.pretty(ui)
-        msg = (_('examine changes to %s?') %
-               _(' and ').join("'%s'" % f for f in h.files()))
-        r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
-        if not r:
-            continue
-        applied[h.filename()] = [h]
-        if h.allhunks():
-            applied[h.filename()] += h.hunks
-            continue
-        for i, chunk in enumerate(h.hunks):
-            if skipfile is None and skipall is None:
-                chunk.pretty(ui)
-            if total == 1:
-                msg = _("record this change to '%s'?") % chunk.filename()
-            else:
-                idx = pos - len(h.hunks) + i
-                msg = _("record change %d/%d to '%s'?") % (idx, total,
-                                                           chunk.filename())
-            r, skipfile, skipall, newpatches = prompt(skipfile,
-                    skipall, msg, chunk)
-            if r:
-                if fixoffset:
-                    chunk = copy.copy(chunk)
-                    chunk.toline += fixoffset
-                applied[chunk.filename()].append(chunk)
-            elif newpatches is not None:
-                for newpatch in newpatches:
-                    for newhunk in newpatch.hunks:
-                        if fixoffset:
-                            newhunk.toline += fixoffset
-                        applied[newhunk.filename()].append(newhunk)
-            else:
-                fixoffset += chunk.removed - chunk.added
-    return sum([h for h in applied.itervalues()
-               if h[0].special() or len(h) > 1], [])
 
 @command("record",
          # same options as commit + white space diff options
@@ -290,7 +138,7 @@ 
 
         # 1. filter patch, so we have intending-to apply subset of it
         try:
-            chunks = filterpatch(ui, patch.parsepatch(fp))
+            chunks = patch.filterpatch(ui, patch.parsepatch(fp))
         except patch.PatchError, err:
             raise util.Abort(_('error parsing patch: %s') % err)
 
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -6,7 +6,7 @@ 
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import cStringIO, email, os, errno, re, posixpath
+import cStringIO, email, os, errno, re, posixpath, copy
 import tempfile, zlib, shutil
 # On python2.4 you have to import these by name or they fail to
 # load. This was not a problem on Python 2.7.
@@ -909,6 +909,158 @@ 
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
+def filterpatch(ui, headers):
+    """Interactively filter patch chunks into applied-only chunks"""
+
+    def prompt(skipfile, skipall, query, chunk):
+        """prompt query, and process base inputs
+
+        - y/n for the rest of file
+        - y/n for the rest
+        - ? (help)
+        - q (quit)
+
+        Return True/False and possibly updated skipfile and skipall.
+        """
+        newpatches = None
+        if skipall is not None:
+            return skipall, skipfile, skipall, newpatches
+        if skipfile is not None:
+            return skipfile, skipfile, skipall, newpatches
+        while True:
+            resps = _('[Ynesfdaq?]'
+                      '$$ &Yes, record this change'
+                      '$$ &No, skip this change'
+                      '$$ &Edit this change manually'
+                      '$$ &Skip remaining changes to this file'
+                      '$$ Record remaining changes to this &file'
+                      '$$ &Done, skip remaining changes and files'
+                      '$$ Record &all changes to all remaining files'
+                      '$$ &Quit, recording no changes'
+                      '$$ &? (display help)')
+            r = ui.promptchoice("%s %s" % (query, resps))
+            ui.write("\n")
+            if r == 8: # ?
+                for c, t in ui.extractchoices(resps)[1]:
+                    ui.write('%s - %s\n' % (c, t.lower()))
+                continue
+            elif r == 0: # yes
+                ret = True
+            elif r == 1: # no
+                ret = False
+            elif r == 2: # Edit patch
+                if chunk is None:
+                    ui.write(_('cannot edit patch for whole file'))
+                    ui.write("\n")
+                    continue
+                if chunk.header.binary():
+                    ui.write(_('cannot edit patch for binary file'))
+                    ui.write("\n")
+                    continue
+                # Patch comment based on the Git one (based on comment at end of
+                # http://mercurial.selenic.com/wiki/RecordExtension)
+                phelp = '---' + _("""
+To remove '-' lines, make them ' ' lines (context).
+To remove '+' lines, delete them.
+Lines starting with # will be removed from the patch.
+
+If the patch applies cleanly, the edited hunk will immediately be
+added to the record list. If it does not apply cleanly, a rejects
+file will be generated: you can use that when you try again. If
+all lines of the hunk are removed, then the edit is aborted and
+the hunk is left unchanged.
+""")
+                (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
+                        suffix=".diff", text=True)
+                ncpatchfp = None
+                try:
+                    # Write the initial patch
+                    f = os.fdopen(patchfd, "w")
+                    chunk.header.write(f)
+                    chunk.write(f)
+                    f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
+                    f.close()
+                    # Start the editor and wait for it to complete
+                    editor = ui.geteditor()
+                    ui.system("%s \"%s\"" % (editor, patchfn),
+                              environ={'HGUSER': ui.username()},
+                              onerr=util.Abort, errprefix=_("edit failed"))
+                    # Remove comment lines
+                    patchfp = open(patchfn)
+                    ncpatchfp = cStringIO.StringIO()
+                    for line in patchfp:
+                        if not line.startswith('#'):
+                            ncpatchfp.write(line)
+                    patchfp.close()
+                    ncpatchfp.seek(0)
+                    newpatches = parsepatch(ncpatchfp)
+                finally:
+                    os.unlink(patchfn)
+                    del ncpatchfp
+                # Signal that the chunk shouldn't be applied as-is, but
+                # provide the new patch to be used instead.
+                ret = False
+            elif r == 3: # Skip
+                ret = skipfile = False
+            elif r == 4: # file (Record remaining)
+                ret = skipfile = True
+            elif r == 5: # done, skip remaining
+                ret = skipall = False
+            elif r == 6: # all
+                ret = skipall = True
+            elif r == 7: # quit
+                raise util.Abort(_('user quit'))
+            return ret, skipfile, skipall, newpatches
+
+    seen = set()
+    applied = {}        # 'filename' -> [] of chunks
+    skipfile, skipall = None, None
+    pos, total = 1, sum(len(h.hunks) for h in headers)
+    for h in headers:
+        pos += len(h.hunks)
+        skipfile = None
+        fixoffset = 0
+        hdr = ''.join(h.header)
+        if hdr in seen:
+            continue
+        seen.add(hdr)
+        if skipall is None:
+            h.pretty(ui)
+        msg = (_('examine changes to %s?') %
+               _(' and ').join("'%s'" % f for f in h.files()))
+        r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
+        if not r:
+            continue
+        applied[h.filename()] = [h]
+        if h.allhunks():
+            applied[h.filename()] += h.hunks
+            continue
+        for i, chunk in enumerate(h.hunks):
+            if skipfile is None and skipall is None:
+                chunk.pretty(ui)
+            if total == 1:
+                msg = _("record this change to '%s'?") % chunk.filename()
+            else:
+                idx = pos - len(h.hunks) + i
+                msg = _("record change %d/%d to '%s'?") % (idx, total,
+                                                           chunk.filename())
+            r, skipfile, skipall, newpatches = prompt(skipfile,
+                    skipall, msg, chunk)
+            if r:
+                if fixoffset:
+                    chunk = copy.copy(chunk)
+                    chunk.toline += fixoffset
+                applied[chunk.filename()].append(chunk)
+            elif newpatches is not None:
+                for newpatch in newpatches:
+                    for newhunk in newpatch.hunks:
+                        if fixoffset:
+                            newhunk.toline += fixoffset
+                        applied[newhunk.filename()].append(newhunk)
+            else:
+                fixoffset += chunk.removed - chunk.added
+    return sum([h for h in applied.itervalues()
+               if h[0].special() or len(h) > 1], [])
 
 class hunk(object):
     def __init__(self, desc, num, lr, context):