##// END OF EJS Templates
git binary patches: don't print the header for identical files
Alexis S. L. Carvalho -
r4106:797dbdd4 default
parent child Browse files
Show More
@@ -1,683 +1,684 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), "base85 cmdutil mdiff util")
12 12 demandload(globals(), "cStringIO email.Parser errno os popen2 re shutil sha")
13 13 demandload(globals(), "sys tempfile zlib")
14 14
15 15 # helper functions
16 16
17 17 def copyfile(src, dst, basedir=None):
18 18 if not basedir:
19 19 basedir = os.getcwd()
20 20
21 21 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
22 22 if os.path.exists(absdst):
23 23 raise util.Abort(_("cannot create %s: destination already exists") %
24 24 dst)
25 25
26 26 targetdir = os.path.dirname(absdst)
27 27 if not os.path.isdir(targetdir):
28 28 os.makedirs(targetdir)
29 29
30 30 util.copyfile(abssrc, absdst)
31 31
32 32 # public functions
33 33
34 34 def extract(ui, fileobj):
35 35 '''extract patch from data read from fileobj.
36 36
37 37 patch can be normal patch or contained in email message.
38 38
39 39 return tuple (filename, message, user, date). any item in returned
40 40 tuple can be None. if filename is None, fileobj did not contain
41 41 patch. caller must unlink filename when done.'''
42 42
43 43 # attempt to detect the start of a patch
44 44 # (this heuristic is borrowed from quilt)
45 45 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
46 46 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
47 47 '(---|\*\*\*)[ \t])', re.MULTILINE)
48 48
49 49 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
50 50 tmpfp = os.fdopen(fd, 'w')
51 51 try:
52 52 hgpatch = False
53 53
54 54 msg = email.Parser.Parser().parse(fileobj)
55 55
56 56 message = msg['Subject']
57 57 user = msg['From']
58 58 # should try to parse msg['Date']
59 59 date = None
60 60
61 61 if message:
62 62 message = message.replace('\n\t', ' ')
63 63 ui.debug('Subject: %s\n' % message)
64 64 if user:
65 65 ui.debug('From: %s\n' % user)
66 66 diffs_seen = 0
67 67 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
68 68
69 69 for part in msg.walk():
70 70 content_type = part.get_content_type()
71 71 ui.debug('Content-Type: %s\n' % content_type)
72 72 if content_type not in ok_types:
73 73 continue
74 74 payload = part.get_payload(decode=True)
75 75 m = diffre.search(payload)
76 76 if m:
77 77 ui.debug(_('found patch at byte %d\n') % m.start(0))
78 78 diffs_seen += 1
79 79 cfp = cStringIO.StringIO()
80 80 if message:
81 81 cfp.write(message)
82 82 cfp.write('\n')
83 83 for line in payload[:m.start(0)].splitlines():
84 84 if line.startswith('# HG changeset patch'):
85 85 ui.debug(_('patch generated by hg export\n'))
86 86 hgpatch = True
87 87 # drop earlier commit message content
88 88 cfp.seek(0)
89 89 cfp.truncate()
90 90 elif hgpatch:
91 91 if line.startswith('# User '):
92 92 user = line[7:]
93 93 ui.debug('From: %s\n' % user)
94 94 elif line.startswith("# Date "):
95 95 date = line[7:]
96 96 if not line.startswith('# '):
97 97 cfp.write(line)
98 98 cfp.write('\n')
99 99 message = cfp.getvalue()
100 100 if tmpfp:
101 101 tmpfp.write(payload)
102 102 if not payload.endswith('\n'):
103 103 tmpfp.write('\n')
104 104 elif not diffs_seen and message and content_type == 'text/plain':
105 105 message += '\n' + payload
106 106 except:
107 107 tmpfp.close()
108 108 os.unlink(tmpname)
109 109 raise
110 110
111 111 tmpfp.close()
112 112 if not diffs_seen:
113 113 os.unlink(tmpname)
114 114 return None, message, user, date
115 115 return tmpname, message, user, date
116 116
117 117 GP_PATCH = 1 << 0 # we have to run patch
118 118 GP_FILTER = 1 << 1 # there's some copy/rename operation
119 119 GP_BINARY = 1 << 2 # there's a binary patch
120 120
121 121 def readgitpatch(patchname):
122 122 """extract git-style metadata about patches from <patchname>"""
123 123 class gitpatch:
124 124 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
125 125 def __init__(self, path):
126 126 self.path = path
127 127 self.oldpath = None
128 128 self.mode = None
129 129 self.op = 'MODIFY'
130 130 self.copymod = False
131 131 self.lineno = 0
132 132 self.binary = False
133 133
134 134 # Filter patch for git information
135 135 gitre = re.compile('diff --git a/(.*) b/(.*)')
136 136 pf = file(patchname)
137 137 gp = None
138 138 gitpatches = []
139 139 # Can have a git patch with only metadata, causing patch to complain
140 140 dopatch = 0
141 141
142 142 lineno = 0
143 143 for line in pf:
144 144 lineno += 1
145 145 if line.startswith('diff --git'):
146 146 m = gitre.match(line)
147 147 if m:
148 148 if gp:
149 149 gitpatches.append(gp)
150 150 src, dst = m.group(1, 2)
151 151 gp = gitpatch(dst)
152 152 gp.lineno = lineno
153 153 elif gp:
154 154 if line.startswith('--- '):
155 155 if gp.op in ('COPY', 'RENAME'):
156 156 gp.copymod = True
157 157 dopatch |= GP_FILTER
158 158 gitpatches.append(gp)
159 159 gp = None
160 160 dopatch |= GP_PATCH
161 161 continue
162 162 if line.startswith('rename from '):
163 163 gp.op = 'RENAME'
164 164 gp.oldpath = line[12:].rstrip()
165 165 elif line.startswith('rename to '):
166 166 gp.path = line[10:].rstrip()
167 167 elif line.startswith('copy from '):
168 168 gp.op = 'COPY'
169 169 gp.oldpath = line[10:].rstrip()
170 170 elif line.startswith('copy to '):
171 171 gp.path = line[8:].rstrip()
172 172 elif line.startswith('deleted file'):
173 173 gp.op = 'DELETE'
174 174 elif line.startswith('new file mode '):
175 175 gp.op = 'ADD'
176 176 gp.mode = int(line.rstrip()[-3:], 8)
177 177 elif line.startswith('new mode '):
178 178 gp.mode = int(line.rstrip()[-3:], 8)
179 179 elif line.startswith('GIT binary patch'):
180 180 dopatch |= GP_BINARY
181 181 gp.binary = True
182 182 if gp:
183 183 gitpatches.append(gp)
184 184
185 185 if not gitpatches:
186 186 dopatch = GP_PATCH
187 187
188 188 return (dopatch, gitpatches)
189 189
190 190 def dogitpatch(patchname, gitpatches, cwd=None):
191 191 """Preprocess git patch so that vanilla patch can handle it"""
192 192 def extractbin(fp):
193 193 i = [0] # yuck
194 194 def readline():
195 195 i[0] += 1
196 196 return fp.readline().rstrip()
197 197 line = readline()
198 198 while line and not line.startswith('literal '):
199 199 line = readline()
200 200 if not line:
201 201 return None, i[0]
202 202 size = int(line[8:])
203 203 dec = []
204 204 line = readline()
205 205 while line:
206 206 l = line[0]
207 207 if l <= 'Z' and l >= 'A':
208 208 l = ord(l) - ord('A') + 1
209 209 else:
210 210 l = ord(l) - ord('a') + 27
211 211 dec.append(base85.b85decode(line[1:])[:l])
212 212 line = readline()
213 213 text = zlib.decompress(''.join(dec))
214 214 if len(text) != size:
215 215 raise util.Abort(_('binary patch is %d bytes, not %d') %
216 216 (len(text), size))
217 217 return text, i[0]
218 218
219 219 pf = file(patchname)
220 220 pfline = 1
221 221
222 222 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
223 223 tmpfp = os.fdopen(fd, 'w')
224 224
225 225 try:
226 226 for i in xrange(len(gitpatches)):
227 227 p = gitpatches[i]
228 228 if not p.copymod and not p.binary:
229 229 continue
230 230
231 231 # rewrite patch hunk
232 232 while pfline < p.lineno:
233 233 tmpfp.write(pf.readline())
234 234 pfline += 1
235 235
236 236 if p.binary:
237 237 text, delta = extractbin(pf)
238 238 if not text:
239 239 raise util.Abort(_('binary patch extraction failed'))
240 240 pfline += delta
241 241 if not cwd:
242 242 cwd = os.getcwd()
243 243 absdst = os.path.join(cwd, p.path)
244 244 basedir = os.path.dirname(absdst)
245 245 if not os.path.isdir(basedir):
246 246 os.makedirs(basedir)
247 247 out = file(absdst, 'wb')
248 248 out.write(text)
249 249 out.close()
250 250 elif p.copymod:
251 251 copyfile(p.oldpath, p.path, basedir=cwd)
252 252 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
253 253 line = pf.readline()
254 254 pfline += 1
255 255 while not line.startswith('--- a/'):
256 256 tmpfp.write(line)
257 257 line = pf.readline()
258 258 pfline += 1
259 259 tmpfp.write('--- a/%s\n' % p.path)
260 260
261 261 line = pf.readline()
262 262 while line:
263 263 tmpfp.write(line)
264 264 line = pf.readline()
265 265 except:
266 266 tmpfp.close()
267 267 os.unlink(patchname)
268 268 raise
269 269
270 270 tmpfp.close()
271 271 return patchname
272 272
273 273 def patch(patchname, ui, strip=1, cwd=None, files={}):
274 274 """apply the patch <patchname> to the working directory.
275 275 a list of patched files is returned"""
276 276
277 277 # helper function
278 278 def __patch(patchname):
279 279 """patch and updates the files and fuzz variables"""
280 280 fuzz = False
281 281
282 282 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
283 283 'patch')
284 284 args = []
285 285 if cwd:
286 286 args.append('-d %s' % util.shellquote(cwd))
287 287 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
288 288 util.shellquote(patchname)))
289 289
290 290 for line in fp:
291 291 line = line.rstrip()
292 292 ui.note(line + '\n')
293 293 if line.startswith('patching file '):
294 294 pf = util.parse_patch_output(line)
295 295 printed_file = False
296 296 files.setdefault(pf, (None, None))
297 297 elif line.find('with fuzz') >= 0:
298 298 fuzz = True
299 299 if not printed_file:
300 300 ui.warn(pf + '\n')
301 301 printed_file = True
302 302 ui.warn(line + '\n')
303 303 elif line.find('saving rejects to file') >= 0:
304 304 ui.warn(line + '\n')
305 305 elif line.find('FAILED') >= 0:
306 306 if not printed_file:
307 307 ui.warn(pf + '\n')
308 308 printed_file = True
309 309 ui.warn(line + '\n')
310 310 code = fp.close()
311 311 if code:
312 312 raise util.Abort(_("patch command failed: %s") %
313 313 util.explain_exit(code)[0])
314 314 return fuzz
315 315
316 316 (dopatch, gitpatches) = readgitpatch(patchname)
317 317 for gp in gitpatches:
318 318 files[gp.path] = (gp.op, gp)
319 319
320 320 fuzz = False
321 321 if dopatch:
322 322 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
323 323 if filterpatch:
324 324 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
325 325 try:
326 326 if dopatch & GP_PATCH:
327 327 fuzz = __patch(patchname)
328 328 finally:
329 329 if filterpatch:
330 330 os.unlink(patchname)
331 331
332 332 return fuzz
333 333
334 334 def diffopts(ui, opts={}, untrusted=False):
335 335 def get(key, name=None):
336 336 return (opts.get(key) or
337 337 ui.configbool('diff', name or key, None, untrusted=untrusted))
338 338 return mdiff.diffopts(
339 339 text=opts.get('text'),
340 340 git=get('git'),
341 341 nodates=get('nodates'),
342 342 showfunc=get('show_function', 'showfunc'),
343 343 ignorews=get('ignore_all_space', 'ignorews'),
344 344 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
345 345 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
346 346
347 347 def updatedir(ui, repo, patches, wlock=None):
348 348 '''Update dirstate after patch application according to metadata'''
349 349 if not patches:
350 350 return
351 351 copies = []
352 352 removes = {}
353 353 cfiles = patches.keys()
354 354 cwd = repo.getcwd()
355 355 if cwd:
356 356 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
357 357 for f in patches:
358 358 ctype, gp = patches[f]
359 359 if ctype == 'RENAME':
360 360 copies.append((gp.oldpath, gp.path, gp.copymod))
361 361 removes[gp.oldpath] = 1
362 362 elif ctype == 'COPY':
363 363 copies.append((gp.oldpath, gp.path, gp.copymod))
364 364 elif ctype == 'DELETE':
365 365 removes[gp.path] = 1
366 366 for src, dst, after in copies:
367 367 if not after:
368 368 copyfile(src, dst, repo.root)
369 369 repo.copy(src, dst, wlock=wlock)
370 370 removes = removes.keys()
371 371 if removes:
372 372 removes.sort()
373 373 repo.remove(removes, True, wlock=wlock)
374 374 for f in patches:
375 375 ctype, gp = patches[f]
376 376 if gp and gp.mode:
377 377 x = gp.mode & 0100 != 0
378 378 dst = os.path.join(repo.root, gp.path)
379 379 # patch won't create empty files
380 380 if ctype == 'ADD' and not os.path.exists(dst):
381 381 repo.wwrite(gp.path, '')
382 382 util.set_exec(dst, x)
383 383 cmdutil.addremove(repo, cfiles, wlock=wlock)
384 384 files = patches.keys()
385 385 files.extend([r for r in removes if r not in files])
386 386 files.sort()
387 387
388 388 return files
389 389
390 390 def b85diff(fp, to, tn):
391 391 '''print base85-encoded binary diff'''
392 392 def gitindex(text):
393 393 if not text:
394 394 return '0' * 40
395 395 l = len(text)
396 396 s = sha.new('blob %d\0' % l)
397 397 s.update(text)
398 398 return s.hexdigest()
399 399
400 400 def fmtline(line):
401 401 l = len(line)
402 402 if l <= 26:
403 403 l = chr(ord('A') + l - 1)
404 404 else:
405 405 l = chr(l - 26 + ord('a') - 1)
406 406 return '%c%s\n' % (l, base85.b85encode(line, True))
407 407
408 408 def chunk(text, csize=52):
409 409 l = len(text)
410 410 i = 0
411 411 while i < l:
412 412 yield text[i:i+csize]
413 413 i += csize
414 414
415 415 tohash = gitindex(to)
416 416 tnhash = gitindex(tn)
417 417 if tohash == tnhash:
418 return
418 return ""
419
419 420 # TODO: deltas
420 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
421 (tohash, tnhash, len(tn)))
422
423 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
424 fp.write(tn)
425 fp.write('\n')
421 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
422 (tohash, tnhash, len(tn))]
423 for l in chunk(zlib.compress(tn)):
424 ret.append(fmtline(l))
425 ret.append('\n')
426 return ''.join(ret)
426 427
427 428 def diff(repo, node1=None, node2=None, files=None, match=util.always,
428 429 fp=None, changes=None, opts=None):
429 430 '''print diff of changes to files between two nodes, or node and
430 431 working directory.
431 432
432 433 if node1 is None, use first dirstate parent instead.
433 434 if node2 is None, compare node1 with working directory.'''
434 435
435 436 if opts is None:
436 437 opts = mdiff.defaultopts
437 438 if fp is None:
438 439 fp = repo.ui
439 440
440 441 if not node1:
441 442 node1 = repo.dirstate.parents()[0]
442 443
443 444 clcache = {}
444 445 def getchangelog(n):
445 446 if n not in clcache:
446 447 clcache[n] = repo.changelog.read(n)
447 448 return clcache[n]
448 449 mcache = {}
449 450 def getmanifest(n):
450 451 if n not in mcache:
451 452 mcache[n] = repo.manifest.read(n)
452 453 return mcache[n]
453 454 fcache = {}
454 455 def getfile(f):
455 456 if f not in fcache:
456 457 fcache[f] = repo.file(f)
457 458 return fcache[f]
458 459
459 460 # reading the data for node1 early allows it to play nicely
460 461 # with repo.status and the revlog cache.
461 462 change = getchangelog(node1)
462 463 mmap = getmanifest(change[0])
463 464 date1 = util.datestr(change[2])
464 465
465 466 if not changes:
466 467 changes = repo.status(node1, node2, files, match=match)[:5]
467 468 modified, added, removed, deleted, unknown = changes
468 469 if files:
469 470 def filterfiles(filters):
470 471 l = [x for x in filters if x in files]
471 472
472 473 for t in files:
473 474 if not t.endswith("/"):
474 475 t += "/"
475 476 l += [x for x in filters if x.startswith(t)]
476 477 return l
477 478
478 479 modified, added, removed = map(filterfiles, (modified, added, removed))
479 480
480 481 if not modified and not added and not removed:
481 482 return
482 483
483 484 # returns False if there was no rename between n1 and n2
484 485 # returns None if the file was created between n1 and n2
485 486 # returns the (file, node) present in n1 that was renamed to f in n2
486 487 def renamedbetween(f, n1, n2):
487 488 r1, r2 = map(repo.changelog.rev, (n1, n2))
488 489 orig = f
489 490 src = None
490 491 while r2 > r1:
491 492 cl = getchangelog(n2)
492 493 if f in cl[3]:
493 494 m = getmanifest(cl[0])
494 495 try:
495 496 src = getfile(f).renamed(m[f])
496 497 except KeyError:
497 498 return None
498 499 if src:
499 500 f = src[0]
500 501 n2 = repo.changelog.parents(n2)[0]
501 502 r2 = repo.changelog.rev(n2)
502 503 cl = getchangelog(n1)
503 504 m = getmanifest(cl[0])
504 505 if f not in m:
505 506 return None
506 507 if f == orig:
507 508 return False
508 509 return f, m[f]
509 510
510 511 if node2:
511 512 change = getchangelog(node2)
512 513 mmap2 = getmanifest(change[0])
513 514 _date2 = util.datestr(change[2])
514 515 def date2(f):
515 516 return _date2
516 517 def read(f):
517 518 return getfile(f).read(mmap2[f])
518 519 def renamed(f):
519 520 return renamedbetween(f, node1, node2)
520 521 else:
521 522 tz = util.makedate()[1]
522 523 _date2 = util.datestr()
523 524 def date2(f):
524 525 try:
525 526 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
526 527 except OSError, err:
527 528 if err.errno != errno.ENOENT: raise
528 529 return _date2
529 530 def read(f):
530 531 return repo.wread(f)
531 532 def renamed(f):
532 533 src = repo.dirstate.copied(f)
533 534 parent = repo.dirstate.parents()[0]
534 535 if src:
535 536 f = src
536 537 of = renamedbetween(f, node1, parent)
537 538 if of or of is None:
538 539 return of
539 540 elif src:
540 541 cl = getchangelog(parent)[0]
541 542 return (src, getmanifest(cl)[src])
542 543 else:
543 544 return None
544 545
545 546 if repo.ui.quiet:
546 547 r = None
547 548 else:
548 549 hexfunc = repo.ui.debugflag and hex or short
549 550 r = [hexfunc(node) for node in [node1, node2] if node]
550 551
551 552 if opts.git:
552 553 copied = {}
553 554 for f in added:
554 555 src = renamed(f)
555 556 if src:
556 557 copied[f] = src
557 558 srcs = [x[1][0] for x in copied.items()]
558 559
559 560 all = modified + added + removed
560 561 all.sort()
561 562 gone = {}
562 563 for f in all:
563 564 to = None
564 565 tn = None
565 566 dodiff = True
566 567 header = []
567 568 if f in mmap:
568 569 to = getfile(f).read(mmap[f])
569 570 if f not in removed:
570 571 tn = read(f)
571 572 if opts.git:
572 573 def gitmode(x):
573 574 return x and '100755' or '100644'
574 575 def addmodehdr(header, omode, nmode):
575 576 if omode != nmode:
576 577 header.append('old mode %s\n' % omode)
577 578 header.append('new mode %s\n' % nmode)
578 579
579 580 a, b = f, f
580 581 if f in added:
581 582 if node2:
582 583 mode = gitmode(mmap2.execf(f))
583 584 else:
584 585 mode = gitmode(util.is_exec(repo.wjoin(f), None))
585 586 if f in copied:
586 587 a, arev = copied[f]
587 588 omode = gitmode(mmap.execf(a))
588 589 addmodehdr(header, omode, mode)
589 590 if a in removed and a not in gone:
590 591 op = 'rename'
591 592 gone[a] = 1
592 593 else:
593 594 op = 'copy'
594 595 header.append('%s from %s\n' % (op, a))
595 596 header.append('%s to %s\n' % (op, f))
596 597 to = getfile(a).read(arev)
597 598 else:
598 599 header.append('new file mode %s\n' % mode)
599 600 if util.binary(tn):
600 601 dodiff = 'binary'
601 602 elif f in removed:
602 603 if f in srcs:
603 604 dodiff = False
604 605 else:
605 606 mode = gitmode(mmap.execf(f))
606 607 header.append('deleted file mode %s\n' % mode)
607 608 else:
608 609 omode = gitmode(mmap.execf(f))
609 610 if node2:
610 611 nmode = gitmode(mmap2.execf(f))
611 612 else:
612 613 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
613 614 addmodehdr(header, omode, nmode)
614 615 if util.binary(to) or util.binary(tn):
615 616 dodiff = 'binary'
616 617 r = None
617 618 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
618 if dodiff == 'binary':
619 fp.write(''.join(header))
620 b85diff(fp, to, tn)
621 elif dodiff:
622 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
619 if dodiff:
620 if dodiff == 'binary':
621 text = b85diff(fp, to, tn)
622 else:
623 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
623 624 if text or len(header) > 1:
624 625 fp.write(''.join(header))
625 626 fp.write(text)
626 627
627 628 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
628 629 opts=None):
629 630 '''export changesets as hg patches.'''
630 631
631 632 total = len(revs)
632 633 revwidth = max([len(str(rev)) for rev in revs])
633 634
634 635 def single(node, seqno, fp):
635 636 parents = [p for p in repo.changelog.parents(node) if p != nullid]
636 637 if switch_parent:
637 638 parents.reverse()
638 639 prev = (parents and parents[0]) or nullid
639 640 change = repo.changelog.read(node)
640 641
641 642 if not fp:
642 643 fp = cmdutil.make_file(repo, template, node, total=total,
643 644 seqno=seqno, revwidth=revwidth)
644 645 if fp not in (sys.stdout, repo.ui):
645 646 repo.ui.note("%s\n" % fp.name)
646 647
647 648 fp.write("# HG changeset patch\n")
648 649 fp.write("# User %s\n" % change[1])
649 650 fp.write("# Date %d %d\n" % change[2])
650 651 fp.write("# Node ID %s\n" % hex(node))
651 652 fp.write("# Parent %s\n" % hex(prev))
652 653 if len(parents) > 1:
653 654 fp.write("# Parent %s\n" % hex(parents[1]))
654 655 fp.write(change[4].rstrip())
655 656 fp.write("\n\n")
656 657
657 658 diff(repo, prev, node, fp=fp, opts=opts)
658 659 if fp not in (sys.stdout, repo.ui):
659 660 fp.close()
660 661
661 662 for seqno, rev in enumerate(revs):
662 663 single(repo.lookup(rev), seqno+1, fp)
663 664
664 665 def diffstat(patchlines):
665 666 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
666 667 try:
667 668 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
668 669 try:
669 670 for line in patchlines: print >> p.tochild, line
670 671 p.tochild.close()
671 672 if p.wait(): return
672 673 fp = os.fdopen(fd, 'r')
673 674 stat = []
674 675 for line in fp: stat.append(line.lstrip())
675 676 last = stat.pop()
676 677 stat.insert(0, last)
677 678 stat = ''.join(stat)
678 679 if stat.startswith('0 files'): raise ValueError
679 680 return stat
680 681 except: raise
681 682 finally:
682 683 try: os.unlink(name)
683 684 except: pass
@@ -1,24 +1,23 b''
1 1 % diff -r 0 -r 1
2 2 diff -r 48b371597640 -r acea2ab458c8 binfile.bin
3 3 Binary file binfile.bin has changed
4 4 % diff -r 0 -r 2
5 5 % diff --git -r 0 -r 1
6 6 diff --git a/binfile.bin b/binfile.bin
7 7 index 37ba3d1c6f17137d9c5f5776fa040caf5fe73ff9..58dc31a9e2f40f74ff3b45903f7d620b8e5b7356
8 8 GIT binary patch
9 9 literal 594
10 10 zc$@)J0<HatP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00009a7bBm000XU
11 11 z000XU0RWnu7ytkO2XskIMF-Uh9TW;VpMjwv0005-Nkl<ZD9@FWPs=e;7{<>W$NUkd
12 12 zX$nnYLt$-$V!?uy+1V%`z&Eh=ah|duER<4|QWhju3gb^nF*8iYobxWG-qqXl=2~5M
13 13 z*IoDB)sG^CfNuoBmqLTVU^<;@nwHP!1wrWd`{(mHo6VNXWtyh{alzqmsH*yYzpvLT
14 14 zLdY<T=ks|woh-`&01!ej#(xbV1f|pI*=%;d-%F*E*X#ZH`4I%6SS+$EJDE&ct=8po
15 15 ziN#{?_j|kD%Cd|oiqds`xm@;oJ-^?NG3Gdqrs?5u*zI;{nogxsx~^|Fn^Y?Gdc6<;
16 16 zfMJ+iF1J`LMx&A2?dEwNW8ClebzPTbIh{@$hS6*`kH@1d%Lo7fA#}N1)oN7`gm$~V
17 17 z+wDx#)OFqMcE{s!JN0-xhG8ItAjVkJwEcb`3WWlJfU2r?;Pd%dmR+q@mSri5q9_W-
18 18 zaR2~ECX?B2w+zELozC0s*6Z~|QG^f{3I#<`?)Q7U-JZ|q5W;9Q8i_=pBuSzunx=U;
19 19 z9C)5jBoYw9^?EHyQl(M}1OlQcCX>lXB*ODN003Z&P17_@)3Pi=i0wb04<W?v-u}7K
20 20 zXmmQA+wDgE!qR9o8jr`%=ab_&uh(l?R=r;Tjiqon91I2-hIu?57~@*4h7h9uORK#=
21 21 gQItJW-{SoTm)8|5##k|m00000NkvXXu0mjf3JwksH2?qr
22 22
23 23 % diff --git -r 0 -r 2
24 diff --git a/binfile.bin b/binfile.bin
General Comments 0
You need to be logged in to leave comments. Login now