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