##// END OF EJS Templates
Try to find diffstat in PATH before calling it...
Alexis S. L. Carvalho -
r4316:6e4334be default
parent child Browse files
Show More
@@ -1,684 +1,686
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(repo.root, 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 418 return ""
419 419
420 420 # TODO: deltas
421 421 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
422 422 (tohash, tnhash, len(tn))]
423 423 for l in chunk(zlib.compress(tn)):
424 424 ret.append(fmtline(l))
425 425 ret.append('\n')
426 426 return ''.join(ret)
427 427
428 428 def diff(repo, node1=None, node2=None, files=None, match=util.always,
429 429 fp=None, changes=None, opts=None):
430 430 '''print diff of changes to files between two nodes, or node and
431 431 working directory.
432 432
433 433 if node1 is None, use first dirstate parent instead.
434 434 if node2 is None, compare node1 with working directory.'''
435 435
436 436 if opts is None:
437 437 opts = mdiff.defaultopts
438 438 if fp is None:
439 439 fp = repo.ui
440 440
441 441 if not node1:
442 442 node1 = repo.dirstate.parents()[0]
443 443
444 444 clcache = {}
445 445 def getchangelog(n):
446 446 if n not in clcache:
447 447 clcache[n] = repo.changelog.read(n)
448 448 return clcache[n]
449 449 mcache = {}
450 450 def getmanifest(n):
451 451 if n not in mcache:
452 452 mcache[n] = repo.manifest.read(n)
453 453 return mcache[n]
454 454 fcache = {}
455 455 def getfile(f):
456 456 if f not in fcache:
457 457 fcache[f] = repo.file(f)
458 458 return fcache[f]
459 459
460 460 # reading the data for node1 early allows it to play nicely
461 461 # with repo.status and the revlog cache.
462 462 change = getchangelog(node1)
463 463 mmap = getmanifest(change[0])
464 464 date1 = util.datestr(change[2])
465 465
466 466 if not changes:
467 467 changes = repo.status(node1, node2, files, match=match)[:5]
468 468 modified, added, removed, deleted, unknown = changes
469 469 if files:
470 470 def filterfiles(filters):
471 471 l = [x for x in filters if x in files]
472 472
473 473 for t in files:
474 474 if not t.endswith("/"):
475 475 t += "/"
476 476 l += [x for x in filters if x.startswith(t)]
477 477 return l
478 478
479 479 modified, added, removed = map(filterfiles, (modified, added, removed))
480 480
481 481 if not modified and not added and not removed:
482 482 return
483 483
484 484 # returns False if there was no rename between n1 and n2
485 485 # returns None if the file was created between n1 and n2
486 486 # returns the (file, node) present in n1 that was renamed to f in n2
487 487 def renamedbetween(f, n1, n2):
488 488 r1, r2 = map(repo.changelog.rev, (n1, n2))
489 489 orig = f
490 490 src = None
491 491 while r2 > r1:
492 492 cl = getchangelog(n2)
493 493 if f in cl[3]:
494 494 m = getmanifest(cl[0])
495 495 try:
496 496 src = getfile(f).renamed(m[f])
497 497 except KeyError:
498 498 return None
499 499 if src:
500 500 f = src[0]
501 501 n2 = repo.changelog.parents(n2)[0]
502 502 r2 = repo.changelog.rev(n2)
503 503 cl = getchangelog(n1)
504 504 m = getmanifest(cl[0])
505 505 if f not in m:
506 506 return None
507 507 if f == orig:
508 508 return False
509 509 return f, m[f]
510 510
511 511 if node2:
512 512 change = getchangelog(node2)
513 513 mmap2 = getmanifest(change[0])
514 514 _date2 = util.datestr(change[2])
515 515 def date2(f):
516 516 return _date2
517 517 def read(f):
518 518 return getfile(f).read(mmap2[f])
519 519 def renamed(f):
520 520 return renamedbetween(f, node1, node2)
521 521 else:
522 522 tz = util.makedate()[1]
523 523 _date2 = util.datestr()
524 524 def date2(f):
525 525 try:
526 526 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
527 527 except OSError, err:
528 528 if err.errno != errno.ENOENT: raise
529 529 return _date2
530 530 def read(f):
531 531 return repo.wread(f)
532 532 def renamed(f):
533 533 src = repo.dirstate.copied(f)
534 534 parent = repo.dirstate.parents()[0]
535 535 if src:
536 536 f = src
537 537 of = renamedbetween(f, node1, parent)
538 538 if of or of is None:
539 539 return of
540 540 elif src:
541 541 cl = getchangelog(parent)[0]
542 542 return (src, getmanifest(cl)[src])
543 543 else:
544 544 return None
545 545
546 546 if repo.ui.quiet:
547 547 r = None
548 548 else:
549 549 hexfunc = repo.ui.debugflag and hex or short
550 550 r = [hexfunc(node) for node in [node1, node2] if node]
551 551
552 552 if opts.git:
553 553 copied = {}
554 554 for f in added:
555 555 src = renamed(f)
556 556 if src:
557 557 copied[f] = src
558 558 srcs = [x[1][0] for x in copied.items()]
559 559
560 560 all = modified + added + removed
561 561 all.sort()
562 562 gone = {}
563 563 for f in all:
564 564 to = None
565 565 tn = None
566 566 dodiff = True
567 567 header = []
568 568 if f in mmap:
569 569 to = getfile(f).read(mmap[f])
570 570 if f not in removed:
571 571 tn = read(f)
572 572 if opts.git:
573 573 def gitmode(x):
574 574 return x and '100755' or '100644'
575 575 def addmodehdr(header, omode, nmode):
576 576 if omode != nmode:
577 577 header.append('old mode %s\n' % omode)
578 578 header.append('new mode %s\n' % nmode)
579 579
580 580 a, b = f, f
581 581 if f in added:
582 582 if node2:
583 583 mode = gitmode(mmap2.execf(f))
584 584 else:
585 585 mode = gitmode(util.is_exec(repo.wjoin(f), None))
586 586 if f in copied:
587 587 a, arev = copied[f]
588 588 omode = gitmode(mmap.execf(a))
589 589 addmodehdr(header, omode, mode)
590 590 if a in removed and a not in gone:
591 591 op = 'rename'
592 592 gone[a] = 1
593 593 else:
594 594 op = 'copy'
595 595 header.append('%s from %s\n' % (op, a))
596 596 header.append('%s to %s\n' % (op, f))
597 597 to = getfile(a).read(arev)
598 598 else:
599 599 header.append('new file mode %s\n' % mode)
600 600 if util.binary(tn):
601 601 dodiff = 'binary'
602 602 elif f in removed:
603 603 if f in srcs:
604 604 dodiff = False
605 605 else:
606 606 mode = gitmode(mmap.execf(f))
607 607 header.append('deleted file mode %s\n' % mode)
608 608 else:
609 609 omode = gitmode(mmap.execf(f))
610 610 if node2:
611 611 nmode = gitmode(mmap2.execf(f))
612 612 else:
613 613 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
614 614 addmodehdr(header, omode, nmode)
615 615 if util.binary(to) or util.binary(tn):
616 616 dodiff = 'binary'
617 617 r = None
618 618 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
619 619 if dodiff:
620 620 if dodiff == 'binary':
621 621 text = b85diff(fp, to, tn)
622 622 else:
623 623 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
624 624 if text or len(header) > 1:
625 625 fp.write(''.join(header))
626 626 fp.write(text)
627 627
628 628 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
629 629 opts=None):
630 630 '''export changesets as hg patches.'''
631 631
632 632 total = len(revs)
633 633 revwidth = max([len(str(rev)) for rev in revs])
634 634
635 635 def single(node, seqno, fp):
636 636 parents = [p for p in repo.changelog.parents(node) if p != nullid]
637 637 if switch_parent:
638 638 parents.reverse()
639 639 prev = (parents and parents[0]) or nullid
640 640 change = repo.changelog.read(node)
641 641
642 642 if not fp:
643 643 fp = cmdutil.make_file(repo, template, node, total=total,
644 644 seqno=seqno, revwidth=revwidth)
645 645 if fp not in (sys.stdout, repo.ui):
646 646 repo.ui.note("%s\n" % fp.name)
647 647
648 648 fp.write("# HG changeset patch\n")
649 649 fp.write("# User %s\n" % change[1])
650 650 fp.write("# Date %d %d\n" % change[2])
651 651 fp.write("# Node ID %s\n" % hex(node))
652 652 fp.write("# Parent %s\n" % hex(prev))
653 653 if len(parents) > 1:
654 654 fp.write("# Parent %s\n" % hex(parents[1]))
655 655 fp.write(change[4].rstrip())
656 656 fp.write("\n\n")
657 657
658 658 diff(repo, prev, node, fp=fp, opts=opts)
659 659 if fp not in (sys.stdout, repo.ui):
660 660 fp.close()
661 661
662 662 for seqno, rev in enumerate(revs):
663 663 single(repo.lookup(rev), seqno+1, fp)
664 664
665 665 def diffstat(patchlines):
666 if not util.find_in_path('diffstat', os.environ.get('PATH', '')):
667 return
666 668 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
667 669 try:
668 670 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
669 671 try:
670 672 for line in patchlines: print >> p.tochild, line
671 673 p.tochild.close()
672 674 if p.wait(): return
673 675 fp = os.fdopen(fd, 'r')
674 676 stat = []
675 677 for line in fp: stat.append(line.lstrip())
676 678 last = stat.pop()
677 679 stat.insert(0, last)
678 680 stat = ''.join(stat)
679 681 if stat.startswith('0 files'): raise ValueError
680 682 return stat
681 683 except: raise
682 684 finally:
683 685 try: os.unlink(name)
684 686 except: pass
General Comments 0
You need to be logged in to leave comments. Login now