Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/util.py @ 2860:b3d1145ed06c
Teach import to understand git diff extensions.
Vanilla patch chokes on git patches that include files that are copied
or renamed, then modified. So this code detects that case and rewrites
the patch if necessary.
author | Brendan Cully <brendan@kublai.com> |
---|---|
date | Fri, 11 Aug 2006 15:50:07 -0700 |
parents | 1e8b8107a2c9 |
children | 0f08f2c042ec |
comparison
equal
deleted
inserted
replaced
2854:7706fa503677 | 2860:b3d1145ed06c |
---|---|
91 p_name = os.path.join(p, name) | 91 p_name = os.path.join(p, name) |
92 if os.path.exists(p_name): | 92 if os.path.exists(p_name): |
93 return p_name | 93 return p_name |
94 return default | 94 return default |
95 | 95 |
96 def readgitpatch(patchname): | |
97 """extract git-style metadata about patches from <patchname>""" | |
98 class gitpatch: | |
99 "op is one of ADD, DELETE, RENAME, MODIFY or COPY" | |
100 def __init__(self, path): | |
101 self.path = path | |
102 self.oldpath = None | |
103 self.mode = None | |
104 self.op = 'MODIFY' | |
105 self.copymod = False | |
106 self.lineno = 0 | |
107 | |
108 # Filter patch for git information | |
109 gitre = re.compile('diff --git a/(.*) b/(.*)') | |
110 pf = file(patchname) | |
111 gp = None | |
112 gitpatches = [] | |
113 # Can have a git patch with only metadata, causing patch to complain | |
114 dopatch = False | |
115 | |
116 lineno = 0 | |
117 for line in pf: | |
118 lineno += 1 | |
119 if line.startswith('diff --git'): | |
120 m = gitre.match(line) | |
121 if m: | |
122 if gp: | |
123 gitpatches.append(gp) | |
124 src, dst = m.group(1,2) | |
125 gp = gitpatch(dst) | |
126 gp.lineno = lineno | |
127 elif gp: | |
128 if line.startswith('--- '): | |
129 if gp.op in ('COPY', 'RENAME'): | |
130 gp.copymod = True | |
131 dopatch = 'filter' | |
132 gitpatches.append(gp) | |
133 gp = None | |
134 if not dopatch: | |
135 dopatch = True | |
136 continue | |
137 if line.startswith('rename from '): | |
138 gp.op = 'RENAME' | |
139 gp.oldpath = line[12:].rstrip() | |
140 elif line.startswith('rename to '): | |
141 gp.path = line[10:].rstrip() | |
142 elif line.startswith('copy from '): | |
143 gp.op = 'COPY' | |
144 gp.oldpath = line[10:].rstrip() | |
145 elif line.startswith('copy to '): | |
146 gp.path = line[8:].rstrip() | |
147 elif line.startswith('deleted file'): | |
148 gp.op = 'DELETE' | |
149 elif line.startswith('new file mode '): | |
150 gp.op = 'ADD' | |
151 gp.mode = int(line.rstrip()[-3:], 8) | |
152 elif line.startswith('new mode '): | |
153 gp.mode = int(line.rstrip()[-3:], 8) | |
154 if gp: | |
155 gitpatches.append(gp) | |
156 | |
157 if not gitpatches: | |
158 dopatch = True | |
159 | |
160 return (dopatch, gitpatches) | |
161 | |
162 def dogitpatch(patchname, gitpatches): | |
163 """Preprocess git patch so that vanilla patch can handle it""" | |
164 pf = file(patchname) | |
165 pfline = 1 | |
166 | |
167 fd, patchname = tempfile.mkstemp(prefix='hg-patch-') | |
168 tmpfp = os.fdopen(fd, 'w') | |
169 | |
170 try: | |
171 for i in range(len(gitpatches)): | |
172 p = gitpatches[i] | |
173 if not p.copymod: | |
174 continue | |
175 | |
176 if os.path.exists(p.path): | |
177 raise Abort(_("cannot create %s: destination already exists") % | |
178 p.path) | |
179 | |
180 (src, dst) = [os.path.join(os.getcwd(), n) | |
181 for n in (p.oldpath, p.path)] | |
182 | |
183 print "copying %s to %s" % (src, dst) | |
184 targetdir = os.path.dirname(dst) | |
185 if not os.path.isdir(targetdir): | |
186 os.makedirs(targetdir) | |
187 try: | |
188 shutil.copyfile(src, dst) | |
189 shutil.copymode(src, dst) | |
190 except shutil.Error, inst: | |
191 raise Abort(str(inst)) | |
192 | |
193 # rewrite patch hunk | |
194 while pfline < p.lineno: | |
195 tmpfp.write(pf.readline()) | |
196 pfline += 1 | |
197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path)) | |
198 line = pf.readline() | |
199 pfline += 1 | |
200 while not line.startswith('--- a/'): | |
201 tmpfp.write(line) | |
202 line = pf.readline() | |
203 pfline += 1 | |
204 tmpfp.write('--- a/%s\n' % p.path) | |
205 | |
206 line = pf.readline() | |
207 while line: | |
208 tmpfp.write(line) | |
209 line = pf.readline() | |
210 except: | |
211 tmpfp.close() | |
212 os.unlink(patchname) | |
213 raise | |
214 | |
215 tmpfp.close() | |
216 return patchname | |
217 | |
96 def patch(strip, patchname, ui, cwd=None): | 218 def patch(strip, patchname, ui, cwd=None): |
97 """apply the patch <patchname> to the working directory. | 219 """apply the patch <patchname> to the working directory. |
98 a list of patched files is returned""" | 220 a list of patched files is returned""" |
99 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') | 221 |
100 args = [] | 222 (dopatch, gitpatches) = readgitpatch(patchname) |
101 if cwd: | 223 |
102 args.append('-d %s' % shellquote(cwd)) | |
103 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip, | |
104 shellquote(patchname))) | |
105 files = {} | 224 files = {} |
106 for line in fp: | 225 if dopatch: |
107 line = line.rstrip() | 226 if dopatch == 'filter': |
108 ui.status("%s\n" % line) | 227 patchname = dogitpatch(patchname, gitpatches) |
109 if line.startswith('patching file '): | 228 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') |
110 pf = parse_patch_output(line) | 229 args = [] |
111 files.setdefault(pf, 1) | 230 if cwd: |
112 code = fp.close() | 231 args.append('-d %s' % shellquote(cwd)) |
113 if code: | 232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip, |
114 raise Abort(_("patch command failed: %s") % explain_exit(code)[0]) | 233 shellquote(patchname))) |
115 return files.keys() | 234 |
235 if dopatch == 'filter': | |
236 False and os.unlink(patchname) | |
237 | |
238 for line in fp: | |
239 line = line.rstrip() | |
240 ui.status("%s\n" % line) | |
241 if line.startswith('patching file '): | |
242 pf = parse_patch_output(line) | |
243 files.setdefault(pf, (None, None)) | |
244 code = fp.close() | |
245 if code: | |
246 raise Abort(_("patch command failed: %s") % explain_exit(code)[0]) | |
247 | |
248 for gp in gitpatches: | |
249 files[gp.path] = (gp.op, gp) | |
250 | |
251 return files | |
116 | 252 |
117 def binary(s): | 253 def binary(s): |
118 """return true if a string is binary data using diff's heuristic""" | 254 """return true if a string is binary data using diff's heuristic""" |
119 if s and '\0' in s[:4096]: | 255 if s and '\0' in s[:4096]: |
120 return True | 256 return True |