patch: pass linereader to scangitpatch(), extract from iterhunks()
authorPatrick Mezard <pmezard@gmail.com>
Sat, 18 Oct 2008 23:45:46 +0200
changeset 7152 f0055cec8446
parent 7151 b5bc5293021c
child 7153 353141d74ca8
patch: pass linereader to scangitpatch(), extract from iterhunks() It's good because it unifies the patch file access methods.
mercurial/patch.py
--- a/mercurial/patch.py	Sat Oct 18 23:45:45 2008 +0200
+++ b/mercurial/patch.py	Sat Oct 18 23:45:46 2008 +0200
@@ -166,15 +166,9 @@
         isexec = mode & 0100
         self.mode = (islink, isexec)
 
-def readgitpatch(fp, firstline=None):
+def readgitpatch(lr):
     """extract git-style metadata about patches from <patchname>"""
 
-    def reader(fp, firstline):
-        if firstline is not None:
-            yield firstline
-        for line in fp:
-            yield line
-
     # Filter patch for git information
     gitre = re.compile('diff --git a/(.*) b/(.*)')
     gp = None
@@ -183,7 +177,7 @@
     dopatch = 0
 
     lineno = 0
-    for line in reader(fp, firstline):
+    for line in lr:
         lineno += 1
         if line.startswith('diff --git'):
             m = gitre.match(line)
@@ -461,7 +455,7 @@
             self.lenb = int(self.lenb)
         self.starta = int(self.starta)
         self.startb = int(self.startb)
-        diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
+        diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
         # if we hit eof before finishing out the hunk, the last line will
         # be zero length.  Lets try to fix it up.
         while len(self.hunk[-1]) == 0:
@@ -771,7 +765,8 @@
         self.buf = []
 
     def push(self, line):
-        self.buf.append(line)
+        if line is not None:
+            self.buf.append(line)
 
     def readline(self):
         if self.buf:
@@ -780,6 +775,39 @@
             return l
         return self.fp.readline()
 
+    def __iter__(self):
+        while 1:
+            l = self.readline()
+            if not l:
+                break
+            yield l
+
+def scangitpatch(lr, firstline):
+    """        
+    Git patches can emit:
+    - rename a to b
+    - change b
+    - copy a to c
+    - change c
+        
+    We cannot apply this sequence as-is, the renamed 'a' could not be
+    found for it would have been renamed already. And we cannot copy
+    from 'b' instead because 'b' would have been changed already. So
+    we scan the git patch for copy and rename commands so we can
+    perform the copies ahead of time.
+    """
+    pos = 0
+    try:
+        pos = lr.fp.tell()
+        fp = lr.fp
+    except IOError:
+        fp = cStringIO.StringIO(lr.fp.read())
+    gitlr = linereader(fp)
+    gitlr.push(firstline)
+    (dopatch, gitpatches) = readgitpatch(gitlr)
+    fp.seek(pos)
+    return fp, dopatch, gitpatches
+
 def iterhunks(ui, fp, sourcefile=None):
     """Read a patch and yield the following events:
     - ("file", afile, bfile, firsthunk): select a new target file.
@@ -788,24 +816,6 @@
     - ("git", gitchanges): current diff is in git format, gitchanges
     maps filenames to gitpatch records. Unique event.
     """
-
-    def scangitpatch(fp, firstline):
-        '''git patches can modify a file, then copy that file to
-        a new file, but expect the source to be the unmodified form.
-        So we scan the patch looking for that case so we can do
-        the copies ahead of time.'''
-
-        pos = 0
-        try:
-            pos = fp.tell()
-        except IOError:
-            fp = cStringIO.StringIO(fp.read())
-
-        (dopatch, gitpatches) = readgitpatch(fp, firstline)
-        fp.seek(pos)
-
-        return fp, dopatch, gitpatches
-
     changed = {}
     current_hunk = None
     afile = ""
@@ -869,7 +879,7 @@
                 afile, bfile = m.group(1, 2)
                 if not git:
                     git = True
-                    fp, dopatch, gitpatches = scangitpatch(fp, x)
+                    fp, dopatch, gitpatches = scangitpatch(lr, x)
                     yield 'git', gitpatches
                     for gp in gitpatches:
                         changed[gp.path] = gp