202 if not gitpatches: |
210 if not gitpatches: |
203 dopatch = GP_PATCH |
211 dopatch = GP_PATCH |
204 |
212 |
205 return (dopatch, gitpatches) |
213 return (dopatch, gitpatches) |
206 |
214 |
207 def dogitpatch(patchname, gitpatches, cwd=None): |
215 def patch(patchname, ui, strip=1, cwd=None, files={}): |
208 """Preprocess git patch so that vanilla patch can handle it""" |
216 """apply the patch <patchname> to the working directory. |
209 def extractbin(fp): |
217 a list of patched files is returned""" |
210 i = [0] # yuck |
218 fp = file(patchname) |
211 def readline(): |
219 fuzz = False |
212 i[0] += 1 |
220 if cwd: |
213 return fp.readline().rstrip() |
221 curdir = os.getcwd() |
214 line = readline() |
222 os.chdir(cwd) |
|
223 try: |
|
224 ret = applydiff(ui, fp, files, strip=strip) |
|
225 except PatchError: |
|
226 raise util.Abort(_("patch failed to apply")) |
|
227 if cwd: |
|
228 os.chdir(curdir) |
|
229 if ret < 0: |
|
230 raise util.Abort(_("patch failed to apply")) |
|
231 if ret > 0: |
|
232 fuzz = True |
|
233 return fuzz |
|
234 |
|
235 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 |
|
236 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') |
|
237 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') |
|
238 |
|
239 class patchfile: |
|
240 def __init__(self, ui, fname): |
|
241 self.fname = fname |
|
242 self.ui = ui |
|
243 try: |
|
244 fp = file(fname, 'r') |
|
245 self.lines = fp.readlines() |
|
246 self.exists = True |
|
247 except IOError: |
|
248 dirname = os.path.dirname(fname) |
|
249 if dirname and not os.path.isdir(dirname): |
|
250 dirs = dirname.split(os.path.sep) |
|
251 d = "" |
|
252 for x in dirs: |
|
253 d = os.path.join(d, x) |
|
254 if not os.path.isdir(d): |
|
255 os.mkdir(d) |
|
256 self.lines = [] |
|
257 self.exists = False |
|
258 |
|
259 self.hash = {} |
|
260 self.dirty = 0 |
|
261 self.offset = 0 |
|
262 self.rej = [] |
|
263 self.fileprinted = False |
|
264 self.printfile(False) |
|
265 self.hunks = 0 |
|
266 |
|
267 def printfile(self, warn): |
|
268 if self.fileprinted: |
|
269 return |
|
270 if warn or self.ui.verbose: |
|
271 self.fileprinted = True |
|
272 s = _("patching file %s\n" % self.fname) |
|
273 if warn: |
|
274 self.ui.warn(s) |
|
275 else: |
|
276 self.ui.note(s) |
|
277 |
|
278 |
|
279 def findlines(self, l, linenum): |
|
280 # looks through the hash and finds candidate lines. The |
|
281 # result is a list of line numbers sorted based on distance |
|
282 # from linenum |
|
283 def sorter(a, b): |
|
284 vala = abs(a - linenum) |
|
285 valb = abs(b - linenum) |
|
286 return cmp(vala, valb) |
|
287 |
|
288 try: |
|
289 cand = self.hash[l] |
|
290 except: |
|
291 return [] |
|
292 |
|
293 if len(cand) > 1: |
|
294 # resort our list of potentials forward then back. |
|
295 cand.sort(cmp=sorter) |
|
296 return cand |
|
297 |
|
298 def hashlines(self): |
|
299 self.hash = {} |
|
300 for x in xrange(len(self.lines)): |
|
301 s = self.lines[x] |
|
302 self.hash.setdefault(s, []).append(x) |
|
303 |
|
304 def write_rej(self): |
|
305 # our rejects are a little different from patch(1). This always |
|
306 # creates rejects in the same form as the original patch. A file |
|
307 # header is inserted so that you can run the reject through patch again |
|
308 # without having to type the filename. |
|
309 |
|
310 if not self.rej: |
|
311 return |
|
312 if self.hunks != 1: |
|
313 hunkstr = "s" |
|
314 else: |
|
315 hunkstr = "" |
|
316 |
|
317 fname = self.fname + ".rej" |
|
318 self.ui.warn( |
|
319 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n" % |
|
320 (len(self.rej), self.hunks, hunkstr, fname))) |
|
321 try: os.unlink(fname) |
|
322 except: |
|
323 pass |
|
324 fp = file(fname, 'w') |
|
325 base = os.path.basename(self.fname) |
|
326 fp.write("--- %s\n+++ %s\n" % (base, base)) |
|
327 for x in self.rej: |
|
328 for l in x.hunk: |
|
329 fp.write(l) |
|
330 if l[-1] != '\n': |
|
331 fp.write("\n\ No newline at end of file\n") |
|
332 |
|
333 def write(self, dest=None): |
|
334 if self.dirty: |
|
335 if not dest: |
|
336 dest = self.fname |
|
337 st = None |
|
338 try: |
|
339 st = os.lstat(dest) |
|
340 if st.st_nlink > 1: |
|
341 os.unlink(dest) |
|
342 except: pass |
|
343 fp = file(dest, 'w') |
|
344 if st: |
|
345 os.chmod(dest, st.st_mode) |
|
346 fp.writelines(self.lines) |
|
347 fp.close() |
|
348 |
|
349 def close(self): |
|
350 self.write() |
|
351 self.write_rej() |
|
352 |
|
353 def apply(self, h, reverse): |
|
354 if not h.complete(): |
|
355 raise PatchError("bad hunk #%d %s (%d %d %d %d)" % |
|
356 (h.number, h.desc, len(h.a), h.lena, len(h.b), |
|
357 h.lenb)) |
|
358 |
|
359 self.hunks += 1 |
|
360 if reverse: |
|
361 h.reverse() |
|
362 |
|
363 if self.exists and h.createfile(): |
|
364 self.ui.warn(_("file %s already exists\n" % self.fname)) |
|
365 self.rej.append(h) |
|
366 return -1 |
|
367 |
|
368 if isinstance(h, binhunk): |
|
369 if h.rmfile(): |
|
370 os.unlink(self.fname) |
|
371 else: |
|
372 self.lines[:] = h.new() |
|
373 self.offset += len(h.new()) |
|
374 self.dirty = 1 |
|
375 return 0 |
|
376 |
|
377 # fast case first, no offsets, no fuzz |
|
378 old = h.old() |
|
379 # patch starts counting at 1 unless we are adding the file |
|
380 if h.starta == 0: |
|
381 start = 0 |
|
382 else: |
|
383 start = h.starta + self.offset - 1 |
|
384 orig_start = start |
|
385 if diffhelpers.testhunk(old, self.lines, start) == 0: |
|
386 if h.rmfile(): |
|
387 os.unlink(self.fname) |
|
388 else: |
|
389 self.lines[start : start + h.lena] = h.new() |
|
390 self.offset += h.lenb - h.lena |
|
391 self.dirty = 1 |
|
392 return 0 |
|
393 |
|
394 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it |
|
395 self.hashlines() |
|
396 if h.hunk[-1][0] != ' ': |
|
397 # if the hunk tried to put something at the bottom of the file |
|
398 # override the start line and use eof here |
|
399 search_start = len(self.lines) |
|
400 else: |
|
401 search_start = orig_start |
|
402 |
|
403 for fuzzlen in xrange(3): |
|
404 for toponly in [ True, False ]: |
|
405 old = h.old(fuzzlen, toponly) |
|
406 |
|
407 cand = self.findlines(old[0][1:], search_start) |
|
408 for l in cand: |
|
409 if diffhelpers.testhunk(old, self.lines, l) == 0: |
|
410 newlines = h.new(fuzzlen, toponly) |
|
411 self.lines[l : l + len(old)] = newlines |
|
412 self.offset += len(newlines) - len(old) |
|
413 self.dirty = 1 |
|
414 if fuzzlen: |
|
415 fuzzstr = "with fuzz %d " % fuzzlen |
|
416 f = self.ui.warn |
|
417 self.printfile(True) |
|
418 else: |
|
419 fuzzstr = "" |
|
420 f = self.ui.note |
|
421 offset = l - orig_start - fuzzlen |
|
422 if offset == 1: |
|
423 linestr = "line" |
|
424 else: |
|
425 linestr = "lines" |
|
426 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n" % |
|
427 (h.number, l+1, fuzzstr, offset, linestr))) |
|
428 return fuzzlen |
|
429 self.printfile(True) |
|
430 self.ui.warn(_("Hunk #%d FAILED at %d\n" % (h.number, orig_start))) |
|
431 self.rej.append(h) |
|
432 return -1 |
|
433 |
|
434 class hunk: |
|
435 def __init__(self, desc, num, lr, context): |
|
436 self.number = num |
|
437 self.desc = desc |
|
438 self.hunk = [ desc ] |
|
439 self.a = [] |
|
440 self.b = [] |
|
441 if context: |
|
442 self.read_context_hunk(lr) |
|
443 else: |
|
444 self.read_unified_hunk(lr) |
|
445 |
|
446 def read_unified_hunk(self, lr): |
|
447 m = unidesc.match(self.desc) |
|
448 if not m: |
|
449 raise PatchError("bad hunk #%d" % self.number) |
|
450 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups() |
|
451 if self.lena == None: |
|
452 self.lena = 1 |
|
453 else: |
|
454 self.lena = int(self.lena) |
|
455 if self.lenb == None: |
|
456 self.lenb = 1 |
|
457 else: |
|
458 self.lenb = int(self.lenb) |
|
459 self.starta = int(self.starta) |
|
460 self.startb = int(self.startb) |
|
461 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b) |
|
462 # if we hit eof before finishing out the hunk, the last line will |
|
463 # be zero length. Lets try to fix it up. |
|
464 while len(self.hunk[-1]) == 0: |
|
465 del self.hunk[-1] |
|
466 del self.a[-1] |
|
467 del self.b[-1] |
|
468 self.lena -= 1 |
|
469 self.lenb -= 1 |
|
470 |
|
471 def read_context_hunk(self, lr): |
|
472 self.desc = lr.readline() |
|
473 m = contextdesc.match(self.desc) |
|
474 if not m: |
|
475 raise PatchError("bad hunk #%d" % self.number) |
|
476 foo, self.starta, foo2, aend, foo3 = m.groups() |
|
477 self.starta = int(self.starta) |
|
478 if aend == None: |
|
479 aend = self.starta |
|
480 self.lena = int(aend) - self.starta |
|
481 if self.starta: |
|
482 self.lena += 1 |
|
483 for x in xrange(self.lena): |
|
484 l = lr.readline() |
|
485 if l.startswith('---'): |
|
486 lr.push(l) |
|
487 break |
|
488 s = l[2:] |
|
489 if l.startswith('- ') or l.startswith('! '): |
|
490 u = '-' + s |
|
491 elif l.startswith(' '): |
|
492 u = ' ' + s |
|
493 else: |
|
494 raise PatchError("bad hunk #%d old text line %d" % (self.number, x)) |
|
495 self.a.append(u) |
|
496 self.hunk.append(u) |
|
497 |
|
498 l = lr.readline() |
|
499 if l.startswith('\ '): |
|
500 s = self.a[-1][:-1] |
|
501 self.a[-1] = s |
|
502 self.hunk[-1] = s |
|
503 l = lr.readline() |
|
504 m = contextdesc.match(l) |
|
505 if not m: |
|
506 raise PatchError("bad hunk #%d" % self.number) |
|
507 foo, self.startb, foo2, bend, foo3 = m.groups() |
|
508 self.startb = int(self.startb) |
|
509 if bend == None: |
|
510 bend = self.startb |
|
511 self.lenb = int(bend) - self.startb |
|
512 if self.startb: |
|
513 self.lenb += 1 |
|
514 hunki = 1 |
|
515 for x in xrange(self.lenb): |
|
516 l = lr.readline() |
|
517 if l.startswith('\ '): |
|
518 s = self.b[-1][:-1] |
|
519 self.b[-1] = s |
|
520 self.hunk[hunki-1] = s |
|
521 continue |
|
522 if not l: |
|
523 lr.push(l) |
|
524 break |
|
525 s = l[2:] |
|
526 if l.startswith('+ ') or l.startswith('! '): |
|
527 u = '+' + s |
|
528 elif l.startswith(' '): |
|
529 u = ' ' + s |
|
530 elif len(self.b) == 0: |
|
531 # this can happen when the hunk does not add any lines |
|
532 lr.push(l) |
|
533 break |
|
534 else: |
|
535 raise PatchError("bad hunk #%d old text line %d" % (self.number, x)) |
|
536 self.b.append(s) |
|
537 while True: |
|
538 if hunki >= len(self.hunk): |
|
539 h = "" |
|
540 else: |
|
541 h = self.hunk[hunki] |
|
542 hunki += 1 |
|
543 if h == u: |
|
544 break |
|
545 elif h.startswith('-'): |
|
546 continue |
|
547 else: |
|
548 self.hunk.insert(hunki-1, u) |
|
549 break |
|
550 |
|
551 if not self.a: |
|
552 # this happens when lines were only added to the hunk |
|
553 for x in self.hunk: |
|
554 if x.startswith('-') or x.startswith(' '): |
|
555 self.a.append(x) |
|
556 if not self.b: |
|
557 # this happens when lines were only deleted from the hunk |
|
558 for x in self.hunk: |
|
559 if x.startswith('+') or x.startswith(' '): |
|
560 self.b.append(x[1:]) |
|
561 # @@ -start,len +start,len @@ |
|
562 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena, |
|
563 self.startb, self.lenb) |
|
564 self.hunk[0] = self.desc |
|
565 |
|
566 def reverse(self): |
|
567 origlena = self.lena |
|
568 origstarta = self.starta |
|
569 self.lena = self.lenb |
|
570 self.starta = self.startb |
|
571 self.lenb = origlena |
|
572 self.startb = origstarta |
|
573 self.a = [] |
|
574 self.b = [] |
|
575 # self.hunk[0] is the @@ description |
|
576 for x in xrange(1, len(self.hunk)): |
|
577 o = self.hunk[x] |
|
578 if o.startswith('-'): |
|
579 n = '+' + o[1:] |
|
580 self.b.append(o[1:]) |
|
581 elif o.startswith('+'): |
|
582 n = '-' + o[1:] |
|
583 self.a.append(n) |
|
584 else: |
|
585 n = o |
|
586 self.b.append(o[1:]) |
|
587 self.a.append(o) |
|
588 self.hunk[x] = o |
|
589 |
|
590 def fix_newline(self): |
|
591 diffhelpers.fix_newline(self.hunk, self.a, self.b) |
|
592 |
|
593 def complete(self): |
|
594 return len(self.a) == self.lena and len(self.b) == self.lenb |
|
595 |
|
596 def createfile(self): |
|
597 return self.starta == 0 and self.lena == 0 |
|
598 |
|
599 def rmfile(self): |
|
600 return self.startb == 0 and self.lenb == 0 |
|
601 |
|
602 def fuzzit(self, l, fuzz, toponly): |
|
603 # this removes context lines from the top and bottom of list 'l'. It |
|
604 # checks the hunk to make sure only context lines are removed, and then |
|
605 # returns a new shortened list of lines. |
|
606 fuzz = min(fuzz, len(l)-1) |
|
607 if fuzz: |
|
608 top = 0 |
|
609 bot = 0 |
|
610 hlen = len(self.hunk) |
|
611 for x in xrange(hlen-1): |
|
612 # the hunk starts with the @@ line, so use x+1 |
|
613 if self.hunk[x+1][0] == ' ': |
|
614 top += 1 |
|
615 else: |
|
616 break |
|
617 if not toponly: |
|
618 for x in xrange(hlen-1): |
|
619 if self.hunk[hlen-bot-1][0] == ' ': |
|
620 bot += 1 |
|
621 else: |
|
622 break |
|
623 |
|
624 # top and bot now count context in the hunk |
|
625 # adjust them if either one is short |
|
626 context = max(top, bot, 3) |
|
627 if bot < context: |
|
628 bot = max(0, fuzz - (context - bot)) |
|
629 else: |
|
630 bot = min(fuzz, bot) |
|
631 if top < context: |
|
632 top = max(0, fuzz - (context - top)) |
|
633 else: |
|
634 top = min(fuzz, top) |
|
635 |
|
636 return l[top:len(l)-bot] |
|
637 return l |
|
638 |
|
639 def old(self, fuzz=0, toponly=False): |
|
640 return self.fuzzit(self.a, fuzz, toponly) |
|
641 |
|
642 def newctrl(self): |
|
643 res = [] |
|
644 for x in self.hunk: |
|
645 c = x[0] |
|
646 if c == ' ' or c == '+': |
|
647 res.append(x) |
|
648 return res |
|
649 |
|
650 def new(self, fuzz=0, toponly=False): |
|
651 return self.fuzzit(self.b, fuzz, toponly) |
|
652 |
|
653 class binhunk: |
|
654 'A binary patch file. Only understands literals so far.' |
|
655 def __init__(self, gitpatch): |
|
656 self.gitpatch = gitpatch |
|
657 self.text = None |
|
658 self.hunk = ['GIT binary patch\n'] |
|
659 |
|
660 def createfile(self): |
|
661 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY') |
|
662 |
|
663 def rmfile(self): |
|
664 return self.gitpatch.op == 'DELETE' |
|
665 |
|
666 def complete(self): |
|
667 return self.text is not None |
|
668 |
|
669 def new(self): |
|
670 return [self.text] |
|
671 |
|
672 def extract(self, fp): |
|
673 line = fp.readline() |
|
674 self.hunk.append(line) |
215 while line and not line.startswith('literal '): |
675 while line and not line.startswith('literal '): |
216 line = readline() |
676 line = fp.readline() |
|
677 self.hunk.append(line) |
217 if not line: |
678 if not line: |
218 return None, i[0] |
679 raise PatchError('could not extract binary patch') |
219 size = int(line[8:]) |
680 size = int(line[8:].rstrip()) |
220 dec = [] |
681 dec = [] |
221 line = readline() |
682 line = fp.readline() |
222 while line: |
683 self.hunk.append(line) |
|
684 while len(line) > 1: |
223 l = line[0] |
685 l = line[0] |
224 if l <= 'Z' and l >= 'A': |
686 if l <= 'Z' and l >= 'A': |
225 l = ord(l) - ord('A') + 1 |
687 l = ord(l) - ord('A') + 1 |
226 else: |
688 else: |
227 l = ord(l) - ord('a') + 27 |
689 l = ord(l) - ord('a') + 27 |
228 dec.append(base85.b85decode(line[1:])[:l]) |
690 dec.append(base85.b85decode(line[1:-1])[:l]) |
229 line = readline() |
691 line = fp.readline() |
|
692 self.hunk.append(line) |
230 text = zlib.decompress(''.join(dec)) |
693 text = zlib.decompress(''.join(dec)) |
231 if len(text) != size: |
694 if len(text) != size: |
232 raise util.Abort(_('binary patch is %d bytes, not %d') % |
695 raise PatchError('binary patch is %d bytes, not %d' % |
233 (len(text), size)) |
696 len(text), size) |
234 return text, i[0] |
697 self.text = text |
235 |
698 |
236 pf = file(patchname) |
699 def parsefilename(str): |
237 pfline = 1 |
700 # --- filename \t|space stuff |
238 |
701 s = str[4:] |
239 fd, patchname = tempfile.mkstemp(prefix='hg-patch-') |
702 i = s.find('\t') |
240 tmpfp = os.fdopen(fd, 'w') |
703 if i < 0: |
241 |
704 i = s.find(' ') |
242 try: |
705 if i < 0: |
243 for i in xrange(len(gitpatches)): |
706 return s |
244 p = gitpatches[i] |
707 return s[:i] |
245 if not p.copymod and not p.binary: |
708 |
|
709 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse): |
|
710 def pathstrip(path, count=1): |
|
711 pathlen = len(path) |
|
712 i = 0 |
|
713 if count == 0: |
|
714 return path.rstrip() |
|
715 while count > 0: |
|
716 i = path.find(os.sep, i) |
|
717 if i == -1: |
|
718 raise PatchError("Unable to strip away %d dirs from %s" % |
|
719 (count, path)) |
|
720 i += 1 |
|
721 # consume '//' in the path |
|
722 while i < pathlen - 1 and path[i] == os.sep: |
|
723 i += 1 |
|
724 count -= 1 |
|
725 return path[i:].rstrip() |
|
726 |
|
727 nulla = afile_orig == "/dev/null" |
|
728 nullb = bfile_orig == "/dev/null" |
|
729 afile = pathstrip(afile_orig, strip) |
|
730 gooda = os.path.exists(afile) and not nulla |
|
731 bfile = pathstrip(bfile_orig, strip) |
|
732 if afile == bfile: |
|
733 goodb = gooda |
|
734 else: |
|
735 goodb = os.path.exists(bfile) and not nullb |
|
736 createfunc = hunk.createfile |
|
737 if reverse: |
|
738 createfunc = hunk.rmfile |
|
739 if not goodb and not gooda and not createfunc(): |
|
740 raise PatchError(_("Unable to find %s or %s for patching\n" % |
|
741 (afile, bfile))) |
|
742 if gooda and goodb: |
|
743 fname = bfile |
|
744 if afile in bfile: |
|
745 fname = afile |
|
746 elif gooda: |
|
747 fname = afile |
|
748 elif not nullb: |
|
749 fname = bfile |
|
750 if afile in bfile: |
|
751 fname = afile |
|
752 elif not nulla: |
|
753 fname = afile |
|
754 return fname |
|
755 |
|
756 class linereader: |
|
757 # simple class to allow pushing lines back into the input stream |
|
758 def __init__(self, fp): |
|
759 self.fp = fp |
|
760 self.buf = [] |
|
761 |
|
762 def push(self, line): |
|
763 self.buf.append(line) |
|
764 |
|
765 def readline(self): |
|
766 if self.buf: |
|
767 l = self.buf[0] |
|
768 del self.buf[0] |
|
769 return l |
|
770 return self.fp.readline() |
|
771 |
|
772 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False, |
|
773 rejmerge=None, updatedir=None): |
|
774 """reads a patch from fp and tries to apply it. The dict 'changed' is |
|
775 filled in with all of the filenames changed by the patch. Returns 0 |
|
776 for a clean patch, -1 if any rejects were found and 1 if there was |
|
777 any fuzz.""" |
|
778 |
|
779 def scangitpatch(fp, firstline, cwd=None): |
|
780 '''git patches can modify a file, then copy that file to |
|
781 a new file, but expect the source to be the unmodified form. |
|
782 So we scan the patch looking for that case so we can do |
|
783 the copies ahead of time.''' |
|
784 |
|
785 pos = 0 |
|
786 try: |
|
787 pos = fp.tell() |
|
788 except IOError: |
|
789 fp = cStringIO.StringIO(fp.read()) |
|
790 |
|
791 (dopatch, gitpatches) = readgitpatch(fp, firstline) |
|
792 for gp in gitpatches: |
|
793 if gp.copymod: |
|
794 copyfile(gp.oldpath, gp.path, basedir=cwd) |
|
795 |
|
796 fp.seek(pos) |
|
797 |
|
798 return fp, dopatch, gitpatches |
|
799 |
|
800 current_hunk = None |
|
801 current_file = None |
|
802 afile = "" |
|
803 bfile = "" |
|
804 state = None |
|
805 hunknum = 0 |
|
806 rejects = 0 |
|
807 |
|
808 git = False |
|
809 gitre = re.compile('diff --git (a/.*) (b/.*)') |
|
810 |
|
811 # our states |
|
812 BFILE = 1 |
|
813 err = 0 |
|
814 context = None |
|
815 lr = linereader(fp) |
|
816 dopatch = True |
|
817 gitworkdone = False |
|
818 |
|
819 while True: |
|
820 newfile = False |
|
821 x = lr.readline() |
|
822 if not x: |
|
823 break |
|
824 if current_hunk: |
|
825 if x.startswith('\ '): |
|
826 current_hunk.fix_newline() |
|
827 ret = current_file.apply(current_hunk, reverse) |
|
828 if ret > 0: |
|
829 err = 1 |
|
830 current_hunk = None |
|
831 gitworkdone = False |
|
832 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or |
|
833 ((context or context == None) and x.startswith('***************')))): |
|
834 try: |
|
835 if context == None and x.startswith('***************'): |
|
836 context = True |
|
837 current_hunk = hunk(x, hunknum + 1, lr, context) |
|
838 except PatchError: |
|
839 current_hunk = None |
246 continue |
840 continue |
247 |
841 hunknum += 1 |
248 # rewrite patch hunk |
842 if not current_file: |
249 while pfline < p.lineno: |
843 if sourcefile: |
250 tmpfp.write(pf.readline()) |
844 current_file = patchfile(ui, sourcefile) |
251 pfline += 1 |
845 else: |
252 |
846 current_file = selectfile(afile, bfile, current_hunk, |
253 if p.binary: |
847 strip, reverse) |
254 text, delta = extractbin(pf) |
848 current_file = patchfile(ui, current_file) |
255 if not text: |
849 changed.setdefault(current_file.fname, (None, None)) |
256 raise util.Abort(_('binary patch extraction failed')) |
850 elif state == BFILE and x.startswith('GIT binary patch'): |
257 pfline += delta |
851 current_hunk = binhunk(changed[bfile[2:]][1]) |
258 if not cwd: |
852 if not current_file: |
259 cwd = os.getcwd() |
853 if sourcefile: |
260 absdst = os.path.join(cwd, p.path) |
854 current_file = patchfile(ui, sourcefile) |
261 basedir = os.path.dirname(absdst) |
855 else: |
262 if not os.path.isdir(basedir): |
856 current_file = selectfile(afile, bfile, current_hunk, |
263 os.makedirs(basedir) |
857 strip, reverse) |
264 out = file(absdst, 'wb') |
858 current_file = patchfile(ui, current_file) |
265 out.write(text) |
859 hunknum += 1 |
266 out.close() |
860 current_hunk.extract(fp) |
267 elif p.copymod: |
861 elif x.startswith('diff --git'): |
268 copyfile(p.oldpath, p.path, basedir=cwd) |
862 # check for git diff, scanning the whole patch file if needed |
269 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path)) |
863 m = gitre.match(x) |
270 line = pf.readline() |
864 if m: |
271 pfline += 1 |
865 afile, bfile = m.group(1, 2) |
272 while not line.startswith('--- a/'): |
866 if not git: |
273 tmpfp.write(line) |
867 git = True |
274 line = pf.readline() |
868 fp, dopatch, gitpatches = scangitpatch(fp, x) |
275 pfline += 1 |
869 for gp in gitpatches: |
276 tmpfp.write('--- a/%s\n' % p.path) |
870 changed[gp.path] = (gp.op, gp) |
277 |
871 # else error? |
278 line = pf.readline() |
872 # copy/rename + modify should modify target, not source |
279 while line: |
873 if changed.get(bfile[2:], (None, None))[0] in ('COPY', |
280 tmpfp.write(line) |
874 'RENAME'): |
281 line = pf.readline() |
875 afile = bfile |
282 except: |
876 gitworkdone = True |
283 tmpfp.close() |
877 newfile = True |
284 os.unlink(patchname) |
878 elif x.startswith('---'): |
285 raise |
879 # check for a unified diff |
286 |
880 l2 = lr.readline() |
287 tmpfp.close() |
881 if not l2.startswith('+++'): |
288 return patchname |
882 lr.push(l2) |
289 |
883 continue |
290 def patch(patchname, ui, strip=1, cwd=None, files={}): |
884 newfile = True |
291 """apply the patch <patchname> to the working directory. |
885 context = False |
292 a list of patched files is returned""" |
886 afile = parsefilename(x) |
293 |
887 bfile = parsefilename(l2) |
294 # helper function |
888 elif x.startswith('***'): |
295 def __patch(patchname): |
889 # check for a context diff |
296 """patch and updates the files and fuzz variables""" |
890 l2 = lr.readline() |
297 fuzz = False |
891 if not l2.startswith('---'): |
298 |
892 lr.push(l2) |
299 args = [] |
893 continue |
300 patcher = ui.config('ui', 'patch') |
894 l3 = lr.readline() |
301 if not patcher: |
895 lr.push(l3) |
302 patcher = util.find_exe('gpatch') or util.find_exe('patch') |
896 if not l3.startswith("***************"): |
303 # Try to be smart only if patch call was not supplied |
897 lr.push(l2) |
304 if util.needbinarypatch(): |
898 continue |
305 args.append('--binary') |
899 newfile = True |
306 |
900 context = True |
307 if not patcher: |
901 afile = parsefilename(x) |
308 raise util.Abort(_('no patch command found in hgrc or PATH')) |
902 bfile = parsefilename(l2) |
309 |
903 |
310 if cwd: |
904 if newfile: |
311 args.append('-d %s' % util.shellquote(cwd)) |
905 if current_file: |
312 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip, |
906 current_file.close() |
313 util.shellquote(patchname))) |
907 if rejmerge: |
314 |
908 rejmerge(current_file) |
315 for line in fp: |
909 rejects += len(current_file.rej) |
316 line = line.rstrip() |
910 state = BFILE |
317 ui.note(line + '\n') |
911 current_file = None |
318 if line.startswith('patching file '): |
912 hunknum = 0 |
319 pf = util.parse_patch_output(line) |
913 if current_hunk: |
320 printed_file = False |
914 if current_hunk.complete(): |
321 files.setdefault(pf, (None, None)) |
915 ret = current_file.apply(current_hunk, reverse) |
322 elif line.find('with fuzz') >= 0: |
916 if ret > 0: |
323 fuzz = True |
917 err = 1 |
324 if not printed_file: |
918 else: |
325 ui.warn(pf + '\n') |
919 fname = current_file and current_file.fname or None |
326 printed_file = True |
920 raise PatchError("malformed patch %s %s" % (fname, |
327 ui.warn(line + '\n') |
921 current_hunk.desc)) |
328 elif line.find('saving rejects to file') >= 0: |
922 if current_file: |
329 ui.warn(line + '\n') |
923 current_file.close() |
330 elif line.find('FAILED') >= 0: |
924 if rejmerge: |
331 if not printed_file: |
925 rejmerge(current_file) |
332 ui.warn(pf + '\n') |
926 rejects += len(current_file.rej) |
333 printed_file = True |
927 if updatedir and git: |
334 ui.warn(line + '\n') |
928 updatedir(gitpatches) |
335 code = fp.close() |
929 if rejects: |
336 if code: |
930 return -1 |
337 raise util.Abort(_("patch command failed: %s") % |
931 if hunknum == 0 and dopatch and not gitworkdone: |
338 util.explain_exit(code)[0]) |
932 raise PatchError("No valid hunks found") |
339 return fuzz |
933 return err |
340 |
|
341 (dopatch, gitpatches) = readgitpatch(patchname) |
|
342 for gp in gitpatches: |
|
343 files[gp.path] = (gp.op, gp) |
|
344 |
|
345 fuzz = False |
|
346 if dopatch: |
|
347 filterpatch = dopatch & (GP_FILTER | GP_BINARY) |
|
348 if filterpatch: |
|
349 patchname = dogitpatch(patchname, gitpatches, cwd=cwd) |
|
350 try: |
|
351 if dopatch & GP_PATCH: |
|
352 fuzz = __patch(patchname) |
|
353 finally: |
|
354 if filterpatch: |
|
355 os.unlink(patchname) |
|
356 |
|
357 return fuzz |
|
358 |
934 |
359 def diffopts(ui, opts={}, untrusted=False): |
935 def diffopts(ui, opts={}, untrusted=False): |
360 def get(key, name=None): |
936 def get(key, name=None): |
361 return (opts.get(key) or |
937 return (opts.get(key) or |
362 ui.configbool('diff', name or key, None, untrusted=untrusted)) |
938 ui.configbool('diff', name or key, None, untrusted=untrusted)) |