##// END OF EJS Templates
record: move filterpatch from record to patch...
Laurent Charignon -
r24269:9a745ced default
parent child Browse files
Show More
@@ -10,164 +10,12
10 10 from mercurial.i18n import _
11 11 from mercurial import cmdutil, commands, extensions, hg, patch
12 12 from mercurial import util
13 import copy, cStringIO, errno, os, shutil, tempfile
13 import cStringIO, errno, os, shutil, tempfile
14 14
15 15 cmdtable = {}
16 16 command = cmdutil.command(cmdtable)
17 17 testedwith = 'internal'
18 18
19 def filterpatch(ui, headers):
20 """Interactively filter patch chunks into applied-only chunks"""
21
22 def prompt(skipfile, skipall, query, chunk):
23 """prompt query, and process base inputs
24
25 - y/n for the rest of file
26 - y/n for the rest
27 - ? (help)
28 - q (quit)
29
30 Return True/False and possibly updated skipfile and skipall.
31 """
32 newpatches = None
33 if skipall is not None:
34 return skipall, skipfile, skipall, newpatches
35 if skipfile is not None:
36 return skipfile, skipfile, skipall, newpatches
37 while True:
38 resps = _('[Ynesfdaq?]'
39 '$$ &Yes, record this change'
40 '$$ &No, skip this change'
41 '$$ &Edit this change manually'
42 '$$ &Skip remaining changes to this file'
43 '$$ Record remaining changes to this &file'
44 '$$ &Done, skip remaining changes and files'
45 '$$ Record &all changes to all remaining files'
46 '$$ &Quit, recording no changes'
47 '$$ &? (display help)')
48 r = ui.promptchoice("%s %s" % (query, resps))
49 ui.write("\n")
50 if r == 8: # ?
51 for c, t in ui.extractchoices(resps)[1]:
52 ui.write('%s - %s\n' % (c, t.lower()))
53 continue
54 elif r == 0: # yes
55 ret = True
56 elif r == 1: # no
57 ret = False
58 elif r == 2: # Edit patch
59 if chunk is None:
60 ui.write(_('cannot edit patch for whole file'))
61 ui.write("\n")
62 continue
63 if chunk.header.binary():
64 ui.write(_('cannot edit patch for binary file'))
65 ui.write("\n")
66 continue
67 # Patch comment based on the Git one (based on comment at end of
68 # http://mercurial.selenic.com/wiki/RecordExtension)
69 phelp = '---' + _("""
70 To remove '-' lines, make them ' ' lines (context).
71 To remove '+' lines, delete them.
72 Lines starting with # will be removed from the patch.
73
74 If the patch applies cleanly, the edited hunk will immediately be
75 added to the record list. If it does not apply cleanly, a rejects
76 file will be generated: you can use that when you try again. If
77 all lines of the hunk are removed, then the edit is aborted and
78 the hunk is left unchanged.
79 """)
80 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
81 suffix=".diff", text=True)
82 ncpatchfp = None
83 try:
84 # Write the initial patch
85 f = os.fdopen(patchfd, "w")
86 chunk.header.write(f)
87 chunk.write(f)
88 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
89 f.close()
90 # Start the editor and wait for it to complete
91 editor = ui.geteditor()
92 ui.system("%s \"%s\"" % (editor, patchfn),
93 environ={'HGUSER': ui.username()},
94 onerr=util.Abort, errprefix=_("edit failed"))
95 # Remove comment lines
96 patchfp = open(patchfn)
97 ncpatchfp = cStringIO.StringIO()
98 for line in patchfp:
99 if not line.startswith('#'):
100 ncpatchfp.write(line)
101 patchfp.close()
102 ncpatchfp.seek(0)
103 newpatches = patch.parsepatch(ncpatchfp)
104 finally:
105 os.unlink(patchfn)
106 del ncpatchfp
107 # Signal that the chunk shouldn't be applied as-is, but
108 # provide the new patch to be used instead.
109 ret = False
110 elif r == 3: # Skip
111 ret = skipfile = False
112 elif r == 4: # file (Record remaining)
113 ret = skipfile = True
114 elif r == 5: # done, skip remaining
115 ret = skipall = False
116 elif r == 6: # all
117 ret = skipall = True
118 elif r == 7: # quit
119 raise util.Abort(_('user quit'))
120 return ret, skipfile, skipall, newpatches
121
122 seen = set()
123 applied = {} # 'filename' -> [] of chunks
124 skipfile, skipall = None, None
125 pos, total = 1, sum(len(h.hunks) for h in headers)
126 for h in headers:
127 pos += len(h.hunks)
128 skipfile = None
129 fixoffset = 0
130 hdr = ''.join(h.header)
131 if hdr in seen:
132 continue
133 seen.add(hdr)
134 if skipall is None:
135 h.pretty(ui)
136 msg = (_('examine changes to %s?') %
137 _(' and ').join("'%s'" % f for f in h.files()))
138 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
139 if not r:
140 continue
141 applied[h.filename()] = [h]
142 if h.allhunks():
143 applied[h.filename()] += h.hunks
144 continue
145 for i, chunk in enumerate(h.hunks):
146 if skipfile is None and skipall is None:
147 chunk.pretty(ui)
148 if total == 1:
149 msg = _("record this change to '%s'?") % chunk.filename()
150 else:
151 idx = pos - len(h.hunks) + i
152 msg = _("record change %d/%d to '%s'?") % (idx, total,
153 chunk.filename())
154 r, skipfile, skipall, newpatches = prompt(skipfile,
155 skipall, msg, chunk)
156 if r:
157 if fixoffset:
158 chunk = copy.copy(chunk)
159 chunk.toline += fixoffset
160 applied[chunk.filename()].append(chunk)
161 elif newpatches is not None:
162 for newpatch in newpatches:
163 for newhunk in newpatch.hunks:
164 if fixoffset:
165 newhunk.toline += fixoffset
166 applied[newhunk.filename()].append(newhunk)
167 else:
168 fixoffset += chunk.removed - chunk.added
169 return sum([h for h in applied.itervalues()
170 if h[0].special() or len(h) > 1], [])
171 19
172 20 @command("record",
173 21 # same options as commit + white space diff options
@@ -290,7 +138,7 def dorecord(ui, repo, commitfunc, cmdsu
290 138
291 139 # 1. filter patch, so we have intending-to apply subset of it
292 140 try:
293 chunks = filterpatch(ui, patch.parsepatch(fp))
141 chunks = patch.filterpatch(ui, patch.parsepatch(fp))
294 142 except patch.PatchError, err:
295 143 raise util.Abort(_('error parsing patch: %s') % err)
296 144
@@ -6,7 +6,7
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 import cStringIO, email, os, errno, re, posixpath
9 import cStringIO, email, os, errno, re, posixpath, copy
10 10 import tempfile, zlib, shutil
11 11 # On python2.4 you have to import these by name or they fail to
12 12 # load. This was not a problem on Python 2.7.
@@ -908,6 +908,158 class recordhunk(object):
908 908 def __repr__(self):
909 909 return '<hunk %r@%d>' % (self.filename(), self.fromline)
910 910
911 def filterpatch(ui, headers):
912 """Interactively filter patch chunks into applied-only chunks"""
913
914 def prompt(skipfile, skipall, query, chunk):
915 """prompt query, and process base inputs
916
917 - y/n for the rest of file
918 - y/n for the rest
919 - ? (help)
920 - q (quit)
921
922 Return True/False and possibly updated skipfile and skipall.
923 """
924 newpatches = None
925 if skipall is not None:
926 return skipall, skipfile, skipall, newpatches
927 if skipfile is not None:
928 return skipfile, skipfile, skipall, newpatches
929 while True:
930 resps = _('[Ynesfdaq?]'
931 '$$ &Yes, record this change'
932 '$$ &No, skip this change'
933 '$$ &Edit this change manually'
934 '$$ &Skip remaining changes to this file'
935 '$$ Record remaining changes to this &file'
936 '$$ &Done, skip remaining changes and files'
937 '$$ Record &all changes to all remaining files'
938 '$$ &Quit, recording no changes'
939 '$$ &? (display help)')
940 r = ui.promptchoice("%s %s" % (query, resps))
941 ui.write("\n")
942 if r == 8: # ?
943 for c, t in ui.extractchoices(resps)[1]:
944 ui.write('%s - %s\n' % (c, t.lower()))
945 continue
946 elif r == 0: # yes
947 ret = True
948 elif r == 1: # no
949 ret = False
950 elif r == 2: # Edit patch
951 if chunk is None:
952 ui.write(_('cannot edit patch for whole file'))
953 ui.write("\n")
954 continue
955 if chunk.header.binary():
956 ui.write(_('cannot edit patch for binary file'))
957 ui.write("\n")
958 continue
959 # Patch comment based on the Git one (based on comment at end of
960 # http://mercurial.selenic.com/wiki/RecordExtension)
961 phelp = '---' + _("""
962 To remove '-' lines, make them ' ' lines (context).
963 To remove '+' lines, delete them.
964 Lines starting with # will be removed from the patch.
965
966 If the patch applies cleanly, the edited hunk will immediately be
967 added to the record list. If it does not apply cleanly, a rejects
968 file will be generated: you can use that when you try again. If
969 all lines of the hunk are removed, then the edit is aborted and
970 the hunk is left unchanged.
971 """)
972 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
973 suffix=".diff", text=True)
974 ncpatchfp = None
975 try:
976 # Write the initial patch
977 f = os.fdopen(patchfd, "w")
978 chunk.header.write(f)
979 chunk.write(f)
980 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
981 f.close()
982 # Start the editor and wait for it to complete
983 editor = ui.geteditor()
984 ui.system("%s \"%s\"" % (editor, patchfn),
985 environ={'HGUSER': ui.username()},
986 onerr=util.Abort, errprefix=_("edit failed"))
987 # Remove comment lines
988 patchfp = open(patchfn)
989 ncpatchfp = cStringIO.StringIO()
990 for line in patchfp:
991 if not line.startswith('#'):
992 ncpatchfp.write(line)
993 patchfp.close()
994 ncpatchfp.seek(0)
995 newpatches = parsepatch(ncpatchfp)
996 finally:
997 os.unlink(patchfn)
998 del ncpatchfp
999 # Signal that the chunk shouldn't be applied as-is, but
1000 # provide the new patch to be used instead.
1001 ret = False
1002 elif r == 3: # Skip
1003 ret = skipfile = False
1004 elif r == 4: # file (Record remaining)
1005 ret = skipfile = True
1006 elif r == 5: # done, skip remaining
1007 ret = skipall = False
1008 elif r == 6: # all
1009 ret = skipall = True
1010 elif r == 7: # quit
1011 raise util.Abort(_('user quit'))
1012 return ret, skipfile, skipall, newpatches
1013
1014 seen = set()
1015 applied = {} # 'filename' -> [] of chunks
1016 skipfile, skipall = None, None
1017 pos, total = 1, sum(len(h.hunks) for h in headers)
1018 for h in headers:
1019 pos += len(h.hunks)
1020 skipfile = None
1021 fixoffset = 0
1022 hdr = ''.join(h.header)
1023 if hdr in seen:
1024 continue
1025 seen.add(hdr)
1026 if skipall is None:
1027 h.pretty(ui)
1028 msg = (_('examine changes to %s?') %
1029 _(' and ').join("'%s'" % f for f in h.files()))
1030 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1031 if not r:
1032 continue
1033 applied[h.filename()] = [h]
1034 if h.allhunks():
1035 applied[h.filename()] += h.hunks
1036 continue
1037 for i, chunk in enumerate(h.hunks):
1038 if skipfile is None and skipall is None:
1039 chunk.pretty(ui)
1040 if total == 1:
1041 msg = _("record this change to '%s'?") % chunk.filename()
1042 else:
1043 idx = pos - len(h.hunks) + i
1044 msg = _("record change %d/%d to '%s'?") % (idx, total,
1045 chunk.filename())
1046 r, skipfile, skipall, newpatches = prompt(skipfile,
1047 skipall, msg, chunk)
1048 if r:
1049 if fixoffset:
1050 chunk = copy.copy(chunk)
1051 chunk.toline += fixoffset
1052 applied[chunk.filename()].append(chunk)
1053 elif newpatches is not None:
1054 for newpatch in newpatches:
1055 for newhunk in newpatch.hunks:
1056 if fixoffset:
1057 newhunk.toline += fixoffset
1058 applied[newhunk.filename()].append(newhunk)
1059 else:
1060 fixoffset += chunk.removed - chunk.added
1061 return sum([h for h in applied.itervalues()
1062 if h[0].special() or len(h) > 1], [])
911 1063 class hunk(object):
912 1064 def __init__(self, desc, num, lr, context):
913 1065 self.number = num
General Comments 0
You need to be logged in to leave comments. Login now