comparison mercurial/patch.py @ 7152:f0055cec8446

patch: pass linereader to scangitpatch(), extract from iterhunks() It's good because it unifies the patch file access methods.
author Patrick Mezard <pmezard@gmail.com>
date Sat, 18 Oct 2008 23:45:46 +0200
parents b5bc5293021c
children 353141d74ca8
comparison
equal deleted inserted replaced
7151:b5bc5293021c 7152:f0055cec8446
164 def setmode(self, mode): 164 def setmode(self, mode):
165 islink = mode & 020000 165 islink = mode & 020000
166 isexec = mode & 0100 166 isexec = mode & 0100
167 self.mode = (islink, isexec) 167 self.mode = (islink, isexec)
168 168
169 def readgitpatch(fp, firstline=None): 169 def readgitpatch(lr):
170 """extract git-style metadata about patches from <patchname>""" 170 """extract git-style metadata about patches from <patchname>"""
171
172 def reader(fp, firstline):
173 if firstline is not None:
174 yield firstline
175 for line in fp:
176 yield line
177 171
178 # Filter patch for git information 172 # Filter patch for git information
179 gitre = re.compile('diff --git a/(.*) b/(.*)') 173 gitre = re.compile('diff --git a/(.*) b/(.*)')
180 gp = None 174 gp = None
181 gitpatches = [] 175 gitpatches = []
182 # Can have a git patch with only metadata, causing patch to complain 176 # Can have a git patch with only metadata, causing patch to complain
183 dopatch = 0 177 dopatch = 0
184 178
185 lineno = 0 179 lineno = 0
186 for line in reader(fp, firstline): 180 for line in lr:
187 lineno += 1 181 lineno += 1
188 if line.startswith('diff --git'): 182 if line.startswith('diff --git'):
189 m = gitre.match(line) 183 m = gitre.match(line)
190 if m: 184 if m:
191 if gp: 185 if gp:
459 self.lenb = 1 453 self.lenb = 1
460 else: 454 else:
461 self.lenb = int(self.lenb) 455 self.lenb = int(self.lenb)
462 self.starta = int(self.starta) 456 self.starta = int(self.starta)
463 self.startb = int(self.startb) 457 self.startb = int(self.startb)
464 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b) 458 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
465 # if we hit eof before finishing out the hunk, the last line will 459 # if we hit eof before finishing out the hunk, the last line will
466 # be zero length. Lets try to fix it up. 460 # be zero length. Lets try to fix it up.
467 while len(self.hunk[-1]) == 0: 461 while len(self.hunk[-1]) == 0:
468 del self.hunk[-1] 462 del self.hunk[-1]
469 del self.a[-1] 463 del self.a[-1]
769 def __init__(self, fp): 763 def __init__(self, fp):
770 self.fp = fp 764 self.fp = fp
771 self.buf = [] 765 self.buf = []
772 766
773 def push(self, line): 767 def push(self, line):
774 self.buf.append(line) 768 if line is not None:
769 self.buf.append(line)
775 770
776 def readline(self): 771 def readline(self):
777 if self.buf: 772 if self.buf:
778 l = self.buf[0] 773 l = self.buf[0]
779 del self.buf[0] 774 del self.buf[0]
780 return l 775 return l
781 return self.fp.readline() 776 return self.fp.readline()
777
778 def __iter__(self):
779 while 1:
780 l = self.readline()
781 if not l:
782 break
783 yield l
784
785 def scangitpatch(lr, firstline):
786 """
787 Git patches can emit:
788 - rename a to b
789 - change b
790 - copy a to c
791 - change c
792
793 We cannot apply this sequence as-is, the renamed 'a' could not be
794 found for it would have been renamed already. And we cannot copy
795 from 'b' instead because 'b' would have been changed already. So
796 we scan the git patch for copy and rename commands so we can
797 perform the copies ahead of time.
798 """
799 pos = 0
800 try:
801 pos = lr.fp.tell()
802 fp = lr.fp
803 except IOError:
804 fp = cStringIO.StringIO(lr.fp.read())
805 gitlr = linereader(fp)
806 gitlr.push(firstline)
807 (dopatch, gitpatches) = readgitpatch(gitlr)
808 fp.seek(pos)
809 return fp, dopatch, gitpatches
782 810
783 def iterhunks(ui, fp, sourcefile=None): 811 def iterhunks(ui, fp, sourcefile=None):
784 """Read a patch and yield the following events: 812 """Read a patch and yield the following events:
785 - ("file", afile, bfile, firsthunk): select a new target file. 813 - ("file", afile, bfile, firsthunk): select a new target file.
786 - ("hunk", hunk): a new hunk is ready to be applied, follows a 814 - ("hunk", hunk): a new hunk is ready to be applied, follows a
787 "file" event. 815 "file" event.
788 - ("git", gitchanges): current diff is in git format, gitchanges 816 - ("git", gitchanges): current diff is in git format, gitchanges
789 maps filenames to gitpatch records. Unique event. 817 maps filenames to gitpatch records. Unique event.
790 """ 818 """
791
792 def scangitpatch(fp, firstline):
793 '''git patches can modify a file, then copy that file to
794 a new file, but expect the source to be the unmodified form.
795 So we scan the patch looking for that case so we can do
796 the copies ahead of time.'''
797
798 pos = 0
799 try:
800 pos = fp.tell()
801 except IOError:
802 fp = cStringIO.StringIO(fp.read())
803
804 (dopatch, gitpatches) = readgitpatch(fp, firstline)
805 fp.seek(pos)
806
807 return fp, dopatch, gitpatches
808
809 changed = {} 819 changed = {}
810 current_hunk = None 820 current_hunk = None
811 afile = "" 821 afile = ""
812 bfile = "" 822 bfile = ""
813 state = None 823 state = None
867 m = gitre.match(x) 877 m = gitre.match(x)
868 if m: 878 if m:
869 afile, bfile = m.group(1, 2) 879 afile, bfile = m.group(1, 2)
870 if not git: 880 if not git:
871 git = True 881 git = True
872 fp, dopatch, gitpatches = scangitpatch(fp, x) 882 fp, dopatch, gitpatches = scangitpatch(lr, x)
873 yield 'git', gitpatches 883 yield 'git', gitpatches
874 for gp in gitpatches: 884 for gp in gitpatches:
875 changed[gp.path] = gp 885 changed[gp.path] = gp
876 # else error? 886 # else error?
877 # copy/rename + modify should modify target, not source 887 # copy/rename + modify should modify target, not source