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