Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/patch.py @ 3367:7f486971d263
Add git-1.4 binary patch support
author | Brendan Cully <brendan@kublai.com> |
---|---|
date | Thu, 12 Oct 2006 09:17:16 -0700 |
parents | 319358e6bd96 |
children | fd43ff3b4442 |
comparison
equal
deleted
inserted
replaced
3366:dca067d751a9 | 3367:7f486971d263 |
---|---|
6 # of the GNU General Public License, incorporated herein by reference. | 6 # of the GNU General Public License, incorporated herein by reference. |
7 | 7 |
8 from demandload import demandload | 8 from demandload import demandload |
9 from i18n import gettext as _ | 9 from i18n import gettext as _ |
10 from node import * | 10 from node import * |
11 demandload(globals(), "cmdutil mdiff util") | 11 demandload(globals(), "base85 cmdutil mdiff util") |
12 demandload(globals(), '''cStringIO email.Parser errno os re shutil sys tempfile | 12 demandload(globals(), "cStringIO email.Parser errno os re shutil sha sys") |
13 popen2''') | 13 demandload(globals(), "tempfile zlib") |
14 | 14 |
15 # helper functions | 15 # helper functions |
16 | 16 |
17 def copyfile(src, dst, basedir=None): | 17 def copyfile(src, dst, basedir=None): |
18 if not basedir: | 18 if not basedir: |
126 self.oldpath = None | 126 self.oldpath = None |
127 self.mode = None | 127 self.mode = None |
128 self.op = 'MODIFY' | 128 self.op = 'MODIFY' |
129 self.copymod = False | 129 self.copymod = False |
130 self.lineno = 0 | 130 self.lineno = 0 |
131 self.binary = False | |
131 | 132 |
132 # Filter patch for git information | 133 # Filter patch for git information |
133 gitre = re.compile('diff --git a/(.*) b/(.*)') | 134 gitre = re.compile('diff --git a/(.*) b/(.*)') |
134 pf = file(patchname) | 135 pf = file(patchname) |
135 gp = None | 136 gp = None |
173 elif line.startswith('new file mode '): | 174 elif line.startswith('new file mode '): |
174 gp.op = 'ADD' | 175 gp.op = 'ADD' |
175 gp.mode = int(line.rstrip()[-3:], 8) | 176 gp.mode = int(line.rstrip()[-3:], 8) |
176 elif line.startswith('new mode '): | 177 elif line.startswith('new mode '): |
177 gp.mode = int(line.rstrip()[-3:], 8) | 178 gp.mode = int(line.rstrip()[-3:], 8) |
179 elif line.startswith('GIT binary patch'): | |
180 if not dopatch: | |
181 dopatch = 'binary' | |
182 gp.binary = True | |
178 if gp: | 183 if gp: |
179 gitpatches.append(gp) | 184 gitpatches.append(gp) |
180 | 185 |
181 if not gitpatches: | 186 if not gitpatches: |
182 dopatch = True | 187 dopatch = True |
183 | 188 |
184 return (dopatch, gitpatches) | 189 return (dopatch, gitpatches) |
185 | 190 |
186 def dogitpatch(patchname, gitpatches, cwd=None): | 191 def dogitpatch(patchname, gitpatches, cwd=None): |
187 """Preprocess git patch so that vanilla patch can handle it""" | 192 """Preprocess git patch so that vanilla patch can handle it""" |
193 def extractbin(fp): | |
194 line = fp.readline() | |
195 while line and not line.startswith('literal '): | |
196 line = fp.readline() | |
197 if not line: | |
198 return | |
199 size = int(line[8:].rstrip()) | |
200 dec = [] | |
201 line = fp.readline() | |
202 while line: | |
203 line = line[1:-1] | |
204 dec.append(base85.b85decode(line)) | |
205 line = fp.readline() | |
206 text = zlib.decompress(''.join(dec)) | |
207 if len(text) != size: | |
208 raise util.Abort(_('binary patch is %d bytes, not %d') % | |
209 (len(text), size)) | |
210 return text | |
211 | |
188 pf = file(patchname) | 212 pf = file(patchname) |
189 pfline = 1 | 213 pfline = 1 |
190 | 214 |
191 fd, patchname = tempfile.mkstemp(prefix='hg-patch-') | 215 fd, patchname = tempfile.mkstemp(prefix='hg-patch-') |
192 tmpfp = os.fdopen(fd, 'w') | 216 tmpfp = os.fdopen(fd, 'w') |
193 | 217 |
194 try: | 218 try: |
195 for i in range(len(gitpatches)): | 219 for i in range(len(gitpatches)): |
196 p = gitpatches[i] | 220 p = gitpatches[i] |
197 if not p.copymod: | 221 if not p.copymod and not p.binary: |
198 continue | 222 continue |
199 | |
200 copyfile(p.oldpath, p.path, basedir=cwd) | |
201 | 223 |
202 # rewrite patch hunk | 224 # rewrite patch hunk |
203 while pfline < p.lineno: | 225 while pfline < p.lineno: |
204 tmpfp.write(pf.readline()) | 226 tmpfp.write(pf.readline()) |
205 pfline += 1 | 227 pfline += 1 |
206 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path)) | 228 |
207 line = pf.readline() | 229 if p.binary: |
208 pfline += 1 | 230 text = extractbin(pf) |
209 while not line.startswith('--- a/'): | 231 if not text: |
210 tmpfp.write(line) | 232 raise util.Abort(_('binary patch extraction failed')) |
233 if not cwd: | |
234 cwd = os.getcwd() | |
235 absdst = os.path.join(cwd, p.path) | |
236 basedir = os.path.dirname(absdst) | |
237 if not os.path.isdir(basedir): | |
238 os.makedirs(basedir) | |
239 out = file(absdst, 'wb') | |
240 out.write(text) | |
241 out.close() | |
242 elif p.copymod: | |
243 copyfile(p.oldpath, p.path, basedir=cwd) | |
244 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path)) | |
211 line = pf.readline() | 245 line = pf.readline() |
212 pfline += 1 | 246 pfline += 1 |
213 tmpfp.write('--- a/%s\n' % p.path) | 247 while not line.startswith('--- a/'): |
248 tmpfp.write(line) | |
249 line = pf.readline() | |
250 pfline += 1 | |
251 tmpfp.write('--- a/%s\n' % p.path) | |
214 | 252 |
215 line = pf.readline() | 253 line = pf.readline() |
216 while line: | 254 while line: |
217 tmpfp.write(line) | 255 tmpfp.write(line) |
218 line = pf.readline() | 256 line = pf.readline() |
268 util.explain_exit(code)[0]) | 306 util.explain_exit(code)[0]) |
269 return files, fuzz | 307 return files, fuzz |
270 | 308 |
271 (dopatch, gitpatches) = readgitpatch(patchname) | 309 (dopatch, gitpatches) = readgitpatch(patchname) |
272 | 310 |
311 files, fuzz = {}, False | |
273 if dopatch: | 312 if dopatch: |
274 if dopatch == 'filter': | 313 if dopatch in ('filter', 'binary'): |
275 patchname = dogitpatch(patchname, gitpatches, cwd=cwd) | 314 patchname = dogitpatch(patchname, gitpatches, cwd=cwd) |
276 try: | 315 try: |
277 files, fuzz = __patch(patchname) | 316 if dopatch != 'binary': |
317 files, fuzz = __patch(patchname) | |
278 finally: | 318 finally: |
279 if dopatch == 'filter': | 319 if dopatch == 'filter': |
280 os.unlink(patchname) | 320 os.unlink(patchname) |
281 else: | |
282 files, fuzz = {}, False | |
283 | 321 |
284 for gp in gitpatches: | 322 for gp in gitpatches: |
285 files[gp.path] = (gp.op, gp) | 323 files[gp.path] = (gp.op, gp) |
286 | 324 |
287 return (files, fuzz) | 325 return (files, fuzz) |
338 files.extend([r for r in removes if r not in files]) | 376 files.extend([r for r in removes if r not in files]) |
339 files.sort() | 377 files.sort() |
340 | 378 |
341 return files | 379 return files |
342 | 380 |
381 def b85diff(fp, to, tn): | |
382 '''print base85-encoded binary diff''' | |
383 def gitindex(text): | |
384 if not text: | |
385 return '0' * 40 | |
386 l = len(text) | |
387 s = sha.new('blob %d\0' % l) | |
388 s.update(text) | |
389 return s.hexdigest() | |
390 | |
391 def fmtline(line): | |
392 l = len(line) | |
393 if l <= 26: | |
394 l = chr(ord('A') + l - 1) | |
395 else: | |
396 l = chr(l - 26 + ord('a') - 1) | |
397 return '%c%s\n' % (l, base85.b85encode(line, True)) | |
398 | |
399 def chunk(text, csize=52): | |
400 l = len(text) | |
401 i = 0 | |
402 while i < l: | |
403 yield text[i:i+csize] | |
404 i += csize | |
405 | |
406 # TODO: deltas | |
407 l = len(tn) | |
408 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' % | |
409 (gitindex(to), gitindex(tn), len(tn))) | |
410 | |
411 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))]) | |
412 fp.write(tn) | |
413 fp.write('\n') | |
414 | |
343 def diff(repo, node1=None, node2=None, files=None, match=util.always, | 415 def diff(repo, node1=None, node2=None, files=None, match=util.always, |
344 fp=None, changes=None, opts=None): | 416 fp=None, changes=None, opts=None): |
345 '''print diff of changes to files between two nodes, or node and | 417 '''print diff of changes to files between two nodes, or node and |
346 working directory. | 418 working directory. |
347 | 419 |
494 header.append('%s from %s\n' % (op, a)) | 566 header.append('%s from %s\n' % (op, a)) |
495 header.append('%s to %s\n' % (op, f)) | 567 header.append('%s to %s\n' % (op, f)) |
496 to = getfile(a).read(arev) | 568 to = getfile(a).read(arev) |
497 else: | 569 else: |
498 header.append('new file mode %s\n' % mode) | 570 header.append('new file mode %s\n' % mode) |
571 if util.binary(tn): | |
572 dodiff = 'binary' | |
499 elif f in removed: | 573 elif f in removed: |
500 if f in srcs: | 574 if f in srcs: |
501 dodiff = False | 575 dodiff = False |
502 else: | 576 else: |
503 mode = gitmode(mmap.execf(f)) | 577 mode = gitmode(mmap.execf(f)) |
507 if node2: | 581 if node2: |
508 nmode = gitmode(mmap2.execf(f)) | 582 nmode = gitmode(mmap2.execf(f)) |
509 else: | 583 else: |
510 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f))) | 584 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f))) |
511 addmodehdr(header, omode, nmode) | 585 addmodehdr(header, omode, nmode) |
586 if util.binary(to) or util.binary(tn): | |
587 dodiff = 'binary' | |
512 r = None | 588 r = None |
513 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) | 589 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) |
514 if dodiff: | 590 if dodiff == 'binary': |
591 fp.write(''.join(header)) | |
592 b85diff(fp, to, tn) | |
593 elif dodiff: | |
515 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts) | 594 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts) |
516 if text or len(header) > 1: | 595 if text or len(header) > 1: |
517 fp.write(''.join(header)) | 596 fp.write(''.join(header)) |
518 fp.write(text) | 597 fp.write(text) |
519 | 598 |