##// END OF EJS Templates
use short hashes with diff -v
Alexis S. L. Carvalho -
r3387:2065789f default
parent child Browse files
Show More
@@ -0,0 +1,26 b''
1 #!/bin/sh
2
3 hg init a
4 cd a
5 echo bar > foo
6 hg add foo
7 hg ci -m 'add foo' -d '1000000 0'
8
9 echo foobar > foo
10 hg ci -m 'change foo' -d '1000001 0'
11
12 echo 'quiet:'
13 hg --quiet diff -r 0 -r 1
14 echo
15
16 echo 'normal:'
17 hg diff -r 0 -r 1
18 echo
19
20 echo 'verbose:'
21 hg --verbose diff -r 0 -r 1
22 echo
23
24 echo 'debug:'
25 hg --debug diff -r 0 -r 1
26 echo
@@ -0,0 +1,31 b''
1 quiet:
2 --- a/foo Mon Jan 12 13:46:40 1970 +0000
3 +++ b/foo Mon Jan 12 13:46:41 1970 +0000
4 @@ -1,1 +1,1 @@ bar
5 -bar
6 +foobar
7
8 normal:
9 diff -r 74de3f1392e2 -r b8b5f023a6ad foo
10 --- a/foo Mon Jan 12 13:46:40 1970 +0000
11 +++ b/foo Mon Jan 12 13:46:41 1970 +0000
12 @@ -1,1 +1,1 @@ bar
13 -bar
14 +foobar
15
16 verbose:
17 diff -r 74de3f1392e2 -r b8b5f023a6ad foo
18 --- a/foo Mon Jan 12 13:46:40 1970 +0000
19 +++ b/foo Mon Jan 12 13:46:41 1970 +0000
20 @@ -1,1 +1,1 @@ bar
21 -bar
22 +foobar
23
24 debug:
25 diff -r 74de3f1392e2d67856fb155963441f2610494e1a -r b8b5f023a6ad77fc378bd95cf3fa00cd1414d107 foo
26 --- a/foo Mon Jan 12 13:46:40 1970 +0000
27 +++ b/foo Mon Jan 12 13:46:41 1970 +0000
28 @@ -1,1 +1,1 @@ bar
29 -bar
30 +foobar
31
@@ -1,659 +1,659 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 try:
30 30 shutil.copyfile(abssrc, absdst)
31 31 shutil.copymode(abssrc, absdst)
32 32 except shutil.Error, inst:
33 33 raise util.Abort(str(inst))
34 34
35 35 # public functions
36 36
37 37 def extract(ui, fileobj):
38 38 '''extract patch from data read from fileobj.
39 39
40 40 patch can be normal patch or contained in email message.
41 41
42 42 return tuple (filename, message, user, date). any item in returned
43 43 tuple can be None. if filename is None, fileobj did not contain
44 44 patch. caller must unlink filename when done.'''
45 45
46 46 # attempt to detect the start of a patch
47 47 # (this heuristic is borrowed from quilt)
48 48 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
49 49 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
50 50 '(---|\*\*\*)[ \t])', re.MULTILINE)
51 51
52 52 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
53 53 tmpfp = os.fdopen(fd, 'w')
54 54 try:
55 55 hgpatch = False
56 56
57 57 msg = email.Parser.Parser().parse(fileobj)
58 58
59 59 message = msg['Subject']
60 60 user = msg['From']
61 61 # should try to parse msg['Date']
62 62 date = None
63 63
64 64 if message:
65 65 message = message.replace('\n\t', ' ')
66 66 ui.debug('Subject: %s\n' % message)
67 67 if user:
68 68 ui.debug('From: %s\n' % user)
69 69 diffs_seen = 0
70 70 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
71 71
72 72 for part in msg.walk():
73 73 content_type = part.get_content_type()
74 74 ui.debug('Content-Type: %s\n' % content_type)
75 75 if content_type not in ok_types:
76 76 continue
77 77 payload = part.get_payload(decode=True)
78 78 m = diffre.search(payload)
79 79 if m:
80 80 ui.debug(_('found patch at byte %d\n') % m.start(0))
81 81 diffs_seen += 1
82 82 cfp = cStringIO.StringIO()
83 83 if message:
84 84 cfp.write(message)
85 85 cfp.write('\n')
86 86 for line in payload[:m.start(0)].splitlines():
87 87 if line.startswith('# HG changeset patch'):
88 88 ui.debug(_('patch generated by hg export\n'))
89 89 hgpatch = True
90 90 # drop earlier commit message content
91 91 cfp.seek(0)
92 92 cfp.truncate()
93 93 elif hgpatch:
94 94 if line.startswith('# User '):
95 95 user = line[7:]
96 96 ui.debug('From: %s\n' % user)
97 97 elif line.startswith("# Date "):
98 98 date = line[7:]
99 99 if not line.startswith('# '):
100 100 cfp.write(line)
101 101 cfp.write('\n')
102 102 message = cfp.getvalue()
103 103 if tmpfp:
104 104 tmpfp.write(payload)
105 105 if not payload.endswith('\n'):
106 106 tmpfp.write('\n')
107 107 elif not diffs_seen and message and content_type == 'text/plain':
108 108 message += '\n' + payload
109 109 except:
110 110 tmpfp.close()
111 111 os.unlink(tmpname)
112 112 raise
113 113
114 114 tmpfp.close()
115 115 if not diffs_seen:
116 116 os.unlink(tmpname)
117 117 return None, message, user, date
118 118 return tmpname, message, user, date
119 119
120 120 def readgitpatch(patchname):
121 121 """extract git-style metadata about patches from <patchname>"""
122 122 class gitpatch:
123 123 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
124 124 def __init__(self, path):
125 125 self.path = path
126 126 self.oldpath = None
127 127 self.mode = None
128 128 self.op = 'MODIFY'
129 129 self.copymod = False
130 130 self.lineno = 0
131 131 self.binary = False
132 132
133 133 # Filter patch for git information
134 134 gitre = re.compile('diff --git a/(.*) b/(.*)')
135 135 pf = file(patchname)
136 136 gp = None
137 137 gitpatches = []
138 138 # Can have a git patch with only metadata, causing patch to complain
139 139 dopatch = False
140 140
141 141 lineno = 0
142 142 for line in pf:
143 143 lineno += 1
144 144 if line.startswith('diff --git'):
145 145 m = gitre.match(line)
146 146 if m:
147 147 if gp:
148 148 gitpatches.append(gp)
149 149 src, dst = m.group(1,2)
150 150 gp = gitpatch(dst)
151 151 gp.lineno = lineno
152 152 elif gp:
153 153 if line.startswith('--- '):
154 154 if gp.op in ('COPY', 'RENAME'):
155 155 gp.copymod = True
156 156 dopatch = 'filter'
157 157 gitpatches.append(gp)
158 158 gp = None
159 159 if not dopatch:
160 160 dopatch = True
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 if not dopatch:
181 181 dopatch = 'binary'
182 182 gp.binary = True
183 183 if gp:
184 184 gitpatches.append(gp)
185 185
186 186 if not gitpatches:
187 187 dopatch = True
188 188
189 189 return (dopatch, gitpatches)
190 190
191 191 def dogitpatch(patchname, gitpatches, cwd=None):
192 192 """Preprocess git patch so that vanilla patch can handle it"""
193 193 def extractbin(fp):
194 194 line = fp.readline().rstrip()
195 195 while line and not line.startswith('literal '):
196 196 line = fp.readline().rstrip()
197 197 if not line:
198 198 return
199 199 size = int(line[8:])
200 200 dec = []
201 201 line = fp.readline().rstrip()
202 202 while line:
203 203 l = line[0]
204 204 if l <= 'Z' and l >= 'A':
205 205 l = ord(l) - ord('A') + 1
206 206 else:
207 207 l = ord(l) - ord('a') + 27
208 208 dec.append(base85.b85decode(line[1:])[:l])
209 209 line = fp.readline().rstrip()
210 210 text = zlib.decompress(''.join(dec))
211 211 if len(text) != size:
212 212 raise util.Abort(_('binary patch is %d bytes, not %d') %
213 213 (len(text), size))
214 214 return text
215 215
216 216 pf = file(patchname)
217 217 pfline = 1
218 218
219 219 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
220 220 tmpfp = os.fdopen(fd, 'w')
221 221
222 222 try:
223 223 for i in range(len(gitpatches)):
224 224 p = gitpatches[i]
225 225 if not p.copymod and not p.binary:
226 226 continue
227 227
228 228 # rewrite patch hunk
229 229 while pfline < p.lineno:
230 230 tmpfp.write(pf.readline())
231 231 pfline += 1
232 232
233 233 if p.binary:
234 234 text = extractbin(pf)
235 235 if not text:
236 236 raise util.Abort(_('binary patch extraction failed'))
237 237 if not cwd:
238 238 cwd = os.getcwd()
239 239 absdst = os.path.join(cwd, p.path)
240 240 basedir = os.path.dirname(absdst)
241 241 if not os.path.isdir(basedir):
242 242 os.makedirs(basedir)
243 243 out = file(absdst, 'wb')
244 244 out.write(text)
245 245 out.close()
246 246 elif p.copymod:
247 247 copyfile(p.oldpath, p.path, basedir=cwd)
248 248 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
249 249 line = pf.readline()
250 250 pfline += 1
251 251 while not line.startswith('--- a/'):
252 252 tmpfp.write(line)
253 253 line = pf.readline()
254 254 pfline += 1
255 255 tmpfp.write('--- a/%s\n' % p.path)
256 256
257 257 line = pf.readline()
258 258 while line:
259 259 tmpfp.write(line)
260 260 line = pf.readline()
261 261 except:
262 262 tmpfp.close()
263 263 os.unlink(patchname)
264 264 raise
265 265
266 266 tmpfp.close()
267 267 return patchname
268 268
269 269 def patch(patchname, ui, strip=1, cwd=None):
270 270 """apply the patch <patchname> to the working directory.
271 271 a list of patched files is returned"""
272 272
273 273 # helper function
274 274 def __patch(patchname):
275 275 """patch and updates the files and fuzz variables"""
276 276 files = {}
277 277 fuzz = False
278 278
279 279 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
280 280 'patch')
281 281 args = []
282 282 if cwd:
283 283 args.append('-d %s' % util.shellquote(cwd))
284 284 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
285 285 util.shellquote(patchname)))
286 286
287 287 for line in fp:
288 288 line = line.rstrip()
289 289 ui.note(line + '\n')
290 290 if line.startswith('patching file '):
291 291 pf = util.parse_patch_output(line)
292 292 printed_file = False
293 293 files.setdefault(pf, (None, None))
294 294 elif line.find('with fuzz') >= 0:
295 295 fuzz = True
296 296 if not printed_file:
297 297 ui.warn(pf + '\n')
298 298 printed_file = True
299 299 ui.warn(line + '\n')
300 300 elif line.find('saving rejects to file') >= 0:
301 301 ui.warn(line + '\n')
302 302 elif line.find('FAILED') >= 0:
303 303 if not printed_file:
304 304 ui.warn(pf + '\n')
305 305 printed_file = True
306 306 ui.warn(line + '\n')
307 307 code = fp.close()
308 308 if code:
309 309 raise util.Abort(_("patch command failed: %s") %
310 310 util.explain_exit(code)[0])
311 311 return files, fuzz
312 312
313 313 (dopatch, gitpatches) = readgitpatch(patchname)
314 314
315 315 files, fuzz = {}, False
316 316 if dopatch:
317 317 if dopatch in ('filter', 'binary'):
318 318 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
319 319 try:
320 320 if dopatch != 'binary':
321 321 files, fuzz = __patch(patchname)
322 322 finally:
323 323 if dopatch == 'filter':
324 324 os.unlink(patchname)
325 325
326 326 for gp in gitpatches:
327 327 files[gp.path] = (gp.op, gp)
328 328
329 329 return (files, fuzz)
330 330
331 331 def diffopts(ui, opts={}):
332 332 return mdiff.diffopts(
333 333 text=opts.get('text'),
334 334 git=(opts.get('git') or
335 335 ui.configbool('diff', 'git', None)),
336 336 nodates=(opts.get('nodates') or
337 337 ui.configbool('diff', 'nodates', None)),
338 338 showfunc=(opts.get('show_function') or
339 339 ui.configbool('diff', 'showfunc', None)),
340 340 ignorews=(opts.get('ignore_all_space') or
341 341 ui.configbool('diff', 'ignorews', None)),
342 342 ignorewsamount=(opts.get('ignore_space_change') or
343 343 ui.configbool('diff', 'ignorewsamount', None)),
344 344 ignoreblanklines=(opts.get('ignore_blank_lines') or
345 345 ui.configbool('diff', 'ignoreblanklines', None)))
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.append(gp.oldpath)
362 362 elif ctype == 'COPY':
363 363 copies.append((gp.oldpath, gp.path, gp.copymod))
364 364 elif ctype == 'DELETE':
365 365 removes.append(gp.path)
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 if removes:
371 371 repo.remove(removes, True, wlock=wlock)
372 372 for f in patches:
373 373 ctype, gp = patches[f]
374 374 if gp and gp.mode:
375 375 x = gp.mode & 0100 != 0
376 376 dst = os.path.join(repo.root, gp.path)
377 377 util.set_exec(dst, x)
378 378 cmdutil.addremove(repo, cfiles, wlock=wlock)
379 379 files = patches.keys()
380 380 files.extend([r for r in removes if r not in files])
381 381 files.sort()
382 382
383 383 return files
384 384
385 385 def b85diff(fp, to, tn):
386 386 '''print base85-encoded binary diff'''
387 387 def gitindex(text):
388 388 if not text:
389 389 return '0' * 40
390 390 l = len(text)
391 391 s = sha.new('blob %d\0' % l)
392 392 s.update(text)
393 393 return s.hexdigest()
394 394
395 395 def fmtline(line):
396 396 l = len(line)
397 397 if l <= 26:
398 398 l = chr(ord('A') + l - 1)
399 399 else:
400 400 l = chr(l - 26 + ord('a') - 1)
401 401 return '%c%s\n' % (l, base85.b85encode(line, True))
402 402
403 403 def chunk(text, csize=52):
404 404 l = len(text)
405 405 i = 0
406 406 while i < l:
407 407 yield text[i:i+csize]
408 408 i += csize
409 409
410 410 # TODO: deltas
411 411 l = len(tn)
412 412 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
413 413 (gitindex(to), gitindex(tn), len(tn)))
414 414
415 415 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
416 416 fp.write(tn)
417 417 fp.write('\n')
418 418
419 419 def diff(repo, node1=None, node2=None, files=None, match=util.always,
420 420 fp=None, changes=None, opts=None):
421 421 '''print diff of changes to files between two nodes, or node and
422 422 working directory.
423 423
424 424 if node1 is None, use first dirstate parent instead.
425 425 if node2 is None, compare node1 with working directory.'''
426 426
427 427 if opts is None:
428 428 opts = mdiff.defaultopts
429 429 if fp is None:
430 430 fp = repo.ui
431 431
432 432 if not node1:
433 433 node1 = repo.dirstate.parents()[0]
434 434
435 435 clcache = {}
436 436 def getchangelog(n):
437 437 if n not in clcache:
438 438 clcache[n] = repo.changelog.read(n)
439 439 return clcache[n]
440 440 mcache = {}
441 441 def getmanifest(n):
442 442 if n not in mcache:
443 443 mcache[n] = repo.manifest.read(n)
444 444 return mcache[n]
445 445 fcache = {}
446 446 def getfile(f):
447 447 if f not in fcache:
448 448 fcache[f] = repo.file(f)
449 449 return fcache[f]
450 450
451 451 # reading the data for node1 early allows it to play nicely
452 452 # with repo.status and the revlog cache.
453 453 change = getchangelog(node1)
454 454 mmap = getmanifest(change[0])
455 455 date1 = util.datestr(change[2])
456 456
457 457 if not changes:
458 458 changes = repo.status(node1, node2, files, match=match)[:5]
459 459 modified, added, removed, deleted, unknown = changes
460 460 if files:
461 461 def filterfiles(filters):
462 462 l = [x for x in filters if x in files]
463 463
464 464 for t in files:
465 465 if not t.endswith("/"):
466 466 t += "/"
467 467 l += [x for x in filters if x.startswith(t)]
468 468 return l
469 469
470 470 modified, added, removed = map(filterfiles, (modified, added, removed))
471 471
472 472 if not modified and not added and not removed:
473 473 return
474 474
475 475 def renamedbetween(f, n1, n2):
476 476 r1, r2 = map(repo.changelog.rev, (n1, n2))
477 477 src = None
478 478 while r2 > r1:
479 479 cl = getchangelog(n2)[0]
480 480 m = getmanifest(cl)
481 481 try:
482 482 src = getfile(f).renamed(m[f])
483 483 except KeyError:
484 484 return None
485 485 if src:
486 486 f = src[0]
487 487 n2 = repo.changelog.parents(n2)[0]
488 488 r2 = repo.changelog.rev(n2)
489 489 return src
490 490
491 491 if node2:
492 492 change = getchangelog(node2)
493 493 mmap2 = getmanifest(change[0])
494 494 _date2 = util.datestr(change[2])
495 495 def date2(f):
496 496 return _date2
497 497 def read(f):
498 498 return getfile(f).read(mmap2[f])
499 499 def renamed(f):
500 500 return renamedbetween(f, node1, node2)
501 501 else:
502 502 tz = util.makedate()[1]
503 503 _date2 = util.datestr()
504 504 def date2(f):
505 505 try:
506 506 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
507 507 except OSError, err:
508 508 if err.errno != errno.ENOENT: raise
509 509 return _date2
510 510 def read(f):
511 511 return repo.wread(f)
512 512 def renamed(f):
513 513 src = repo.dirstate.copied(f)
514 514 parent = repo.dirstate.parents()[0]
515 515 if src:
516 516 f = src[0]
517 517 of = renamedbetween(f, node1, parent)
518 518 if of:
519 519 return of
520 520 elif src:
521 521 cl = getchangelog(parent)[0]
522 522 return (src, getmanifest(cl)[src])
523 523 else:
524 524 return None
525 525
526 526 if repo.ui.quiet:
527 527 r = None
528 528 else:
529 hexfunc = repo.ui.verbose and hex or short
529 hexfunc = repo.ui.debugflag and hex or short
530 530 r = [hexfunc(node) for node in [node1, node2] if node]
531 531
532 532 if opts.git:
533 533 copied = {}
534 534 for f in added:
535 535 src = renamed(f)
536 536 if src:
537 537 copied[f] = src
538 538 srcs = [x[1][0] for x in copied.items()]
539 539
540 540 all = modified + added + removed
541 541 all.sort()
542 542 for f in all:
543 543 to = None
544 544 tn = None
545 545 dodiff = True
546 546 header = []
547 547 if f in mmap:
548 548 to = getfile(f).read(mmap[f])
549 549 if f not in removed:
550 550 tn = read(f)
551 551 if opts.git:
552 552 def gitmode(x):
553 553 return x and '100755' or '100644'
554 554 def addmodehdr(header, omode, nmode):
555 555 if omode != nmode:
556 556 header.append('old mode %s\n' % omode)
557 557 header.append('new mode %s\n' % nmode)
558 558
559 559 a, b = f, f
560 560 if f in added:
561 561 if node2:
562 562 mode = gitmode(mmap2.execf(f))
563 563 else:
564 564 mode = gitmode(util.is_exec(repo.wjoin(f), None))
565 565 if f in copied:
566 566 a, arev = copied[f]
567 567 omode = gitmode(mmap.execf(a))
568 568 addmodehdr(header, omode, mode)
569 569 op = a in removed and 'rename' or 'copy'
570 570 header.append('%s from %s\n' % (op, a))
571 571 header.append('%s to %s\n' % (op, f))
572 572 to = getfile(a).read(arev)
573 573 else:
574 574 header.append('new file mode %s\n' % mode)
575 575 if util.binary(tn):
576 576 dodiff = 'binary'
577 577 elif f in removed:
578 578 if f in srcs:
579 579 dodiff = False
580 580 else:
581 581 mode = gitmode(mmap.execf(f))
582 582 header.append('deleted file mode %s\n' % mode)
583 583 else:
584 584 omode = gitmode(mmap.execf(f))
585 585 if node2:
586 586 nmode = gitmode(mmap2.execf(f))
587 587 else:
588 588 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
589 589 addmodehdr(header, omode, nmode)
590 590 if util.binary(to) or util.binary(tn):
591 591 dodiff = 'binary'
592 592 r = None
593 593 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
594 594 if dodiff == 'binary':
595 595 fp.write(''.join(header))
596 596 b85diff(fp, to, tn)
597 597 elif dodiff:
598 598 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
599 599 if text or len(header) > 1:
600 600 fp.write(''.join(header))
601 601 fp.write(text)
602 602
603 603 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
604 604 opts=None):
605 605 '''export changesets as hg patches.'''
606 606
607 607 total = len(revs)
608 608 revwidth = max(map(len, revs))
609 609
610 610 def single(node, seqno, fp):
611 611 parents = [p for p in repo.changelog.parents(node) if p != nullid]
612 612 if switch_parent:
613 613 parents.reverse()
614 614 prev = (parents and parents[0]) or nullid
615 615 change = repo.changelog.read(node)
616 616
617 617 if not fp:
618 618 fp = cmdutil.make_file(repo, template, node, total=total,
619 619 seqno=seqno, revwidth=revwidth)
620 620 if fp not in (sys.stdout, repo.ui):
621 621 repo.ui.note("%s\n" % fp.name)
622 622
623 623 fp.write("# HG changeset patch\n")
624 624 fp.write("# User %s\n" % change[1])
625 625 fp.write("# Date %d %d\n" % change[2])
626 626 fp.write("# Node ID %s\n" % hex(node))
627 627 fp.write("# Parent %s\n" % hex(prev))
628 628 if len(parents) > 1:
629 629 fp.write("# Parent %s\n" % hex(parents[1]))
630 630 fp.write(change[4].rstrip())
631 631 fp.write("\n\n")
632 632
633 633 diff(repo, prev, node, fp=fp, opts=opts)
634 634 if fp not in (sys.stdout, repo.ui):
635 635 fp.close()
636 636
637 637 for seqno, cset in enumerate(revs):
638 638 single(cset, seqno, fp)
639 639
640 640 def diffstat(patchlines):
641 641 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
642 642 try:
643 643 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
644 644 try:
645 645 for line in patchlines: print >> p.tochild, line
646 646 p.tochild.close()
647 647 if p.wait(): return
648 648 fp = os.fdopen(fd, 'r')
649 649 stat = []
650 650 for line in fp: stat.append(line.lstrip())
651 651 last = stat.pop()
652 652 stat.insert(0, last)
653 653 stat = ''.join(stat)
654 654 if stat.startswith('0 files'): raise ValueError
655 655 return stat
656 656 except: raise
657 657 finally:
658 658 try: os.unlink(name)
659 659 except: pass
General Comments 0
You need to be logged in to leave comments. Login now