changeset 14388:37c997d21752

patch: stop handling hunkless git blocks out of stream Patch changes are emitted by iterhunks() in two separate events: 'file' when hunks have to be applied and 'git' to describe other modifications like copies or mode changes. Note that a file which mode is changed and which content is modified by the same patch will be emitted in both events. It is more convenient to handle all file modifications in a single event. This patch "zips" git actions with regular changes so both kinds can be emitted at the same place.
author Patrick Mezard <pmezard@gmail.com>
date Thu, 19 May 2011 22:44:01 +0200
parents e1b4a7a7263a
children 909ac6b9636b
files mercurial/patch.py
diffstat 1 files changed, 43 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/patch.py	Thu May 19 22:44:01 2011 +0200
+++ b/mercurial/patch.py	Thu May 19 22:44:01 2011 +0200
@@ -1113,13 +1113,12 @@
     - ("git", gitchanges): current diff is in git format, gitchanges
     maps filenames to gitpatch records. Unique event.
     """
-    changed = {}
     afile = ""
     bfile = ""
     state = None
     hunknum = 0
     emitfile = newfile = False
-    git = False
+    gitpatches = None
 
     # our states
     BFILE = 1
@@ -1134,7 +1133,9 @@
             (not context and x[0] == '@')
             or (context is not False and x.startswith('***************'))
             or x.startswith('GIT binary patch')):
-            gp = changed.get(bfile)
+            gp = None
+            if gitpatches and gitpatches[-1][0] == bfile:
+                gp = gitpatches.pop()[1]
             if x.startswith('GIT binary patch'):
                 h = binhunk(gp, lr)
             else:
@@ -1146,22 +1147,24 @@
             hunknum += 1
             if emitfile:
                 emitfile = False
-                yield 'file', (afile, bfile, h, gp and gp.mode or None)
+                yield 'file', (afile, bfile, h, gp)
             yield 'hunk', h
         elif x.startswith('diff --git'):
             m = gitre.match(x)
             if not m:
                 continue
-            if not git:
+            if gitpatches is None:
                 # scan whole input for git metadata
-                git = True
-                gitpatches = scangitpatch(lr, x)
-                for gp in gitpatches:
-                    changed['b/' + gp.path] = gp
-                yield 'git', gitpatches
+                gitpatches = [('b/' + gp.path, gp) for gp
+                              in scangitpatch(lr, x)]
+                yield 'git', [g[1] for g in gitpatches]
+                gitpatches.reverse()
             afile = 'a/' + m.group(1)
             bfile = 'b/' + m.group(2)
-            gp = changed[bfile]
+            while bfile != gitpatches[-1][0]:
+                gp = gitpatches.pop()[1]
+                yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
+            gp = gitpatches[-1][1]
             # copy/rename + modify should modify target, not source
             if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
                 afile = bfile
@@ -1198,6 +1201,10 @@
             state = BFILE
             hunknum = 0
 
+    while gitpatches:
+        gp = gitpatches.pop()[1]
+        yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
+
 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'):
     """Reads a patch from fp and tries to apply it.
 
@@ -1229,8 +1236,25 @@
         elif state == 'file':
             if current_file:
                 rejects += current_file.close()
-            afile, bfile, first_hunk, mode = values
+                current_file = None
+            afile, bfile, first_hunk, gp = values
+            if gp:
+                changed[gp.path] = gp
+                if gp.op == 'DELETE':
+                    backend.unlink(gp.path)
+                    continue
+                if gp.op == 'RENAME':
+                    backend.unlink(gp.oldpath)
+                if gp.mode and not first_hunk:
+                    if gp.op == 'ADD':
+                        # Added files without content have no hunk and must be created
+                        backend.writelines(gp.path, [], gp.mode)
+                    else:
+                        backend.setmode(gp.path, gp.mode[0], gp.mode[1])
+            if not first_hunk:
+                continue
             try:
+                mode = gp and gp.mode or None
                 current_file, missing = selectfile(backend, afile, bfile,
                                                    first_hunk, strip)
                 current_file = patcher(ui, current_file, backend, mode,
@@ -1247,32 +1271,12 @@
                     gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
                 if gp.op in ('COPY', 'RENAME'):
                     backend.copy(gp.oldpath, gp.path)
-                changed[gp.path] = gp
         else:
             raise util.Abort(_('unsupported parser state: %s') % state)
 
     if current_file:
         rejects += current_file.close()
 
-    # Handle mode changes without hunk
-    removed = set()
-    for gp in changed.itervalues():
-        if not gp:
-            continue
-        if gp.op == 'DELETE':
-            removed.add(gp.path)
-            continue
-        if gp.op == 'RENAME':
-            removed.add(gp.oldpath)
-        if gp.mode:
-            if gp.op == 'ADD' and not backend.exists(gp.path):
-                # Added files without content have no hunk and must be created
-                backend.writelines(gp.path, [], gp.mode)
-            else:
-                backend.setmode(gp.path, gp.mode[0], gp.mode[1])
-    for path in sorted(removed):
-        backend.unlink(path)
-
     if rejects:
         return -1
     return err
@@ -1386,18 +1390,21 @@
             if state == 'hunk':
                 continue
             elif state == 'file':
-                afile, bfile, first_hunk, mode = values
+                afile, bfile, first_hunk, gp = values
+                if gp:
+                    changed.add(gp.path)
+                    if gp.op == 'RENAME':
+                        changed.add(gp.oldpath)
+                if not first_hunk:
+                    continue
                 current_file, missing = selectfile(backend, afile, bfile,
                                                    first_hunk, strip)
                 changed.add(current_file)
             elif state == 'git':
                 for gp in values:
                     gp.path = pathstrip(gp.path, strip - 1)[1]
-                    changed.add(gp.path)
                     if gp.oldpath:
                         gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
-                        if gp.op == 'RENAME':
-                            changed.add(gp.oldpath)
             else:
                 raise util.Abort(_('unsupported parser state: %s') % state)
         return changed