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