record: move filterpatch from record to patch
Part of a series of patches to move record from hgext to core
--- a/hgext/record.py Tue Mar 10 17:34:42 2015 -0700
+++ b/hgext/record.py Tue Mar 10 14:42:07 2015 -0700
@@ -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)
--- a/mercurial/patch.py Tue Mar 10 17:34:42 2015 -0700
+++ b/mercurial/patch.py Tue Mar 10 14:42:07 2015 -0700
@@ -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.
@@ -908,6 +908,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):
self.number = num