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 |
|
|
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