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