mercurial/patch.py
changeset 14566 d0c2cc11e611
parent 14565 3cacc232f27f
child 14609 f53dc0787424
--- a/mercurial/patch.py	Sat Jun 11 14:14:13 2011 +0200
+++ b/mercurial/patch.py	Sat Jun 11 14:17:25 2011 +0200
@@ -281,6 +281,14 @@
         isexec = mode & 0100
         self.mode = (islink, isexec)
 
+    def copy(self):
+        other = patchmeta(self.path)
+        other.oldpath = self.oldpath
+        other.mode = self.mode
+        other.op = self.op
+        other.binary = self.binary
+        return other
+
     def __repr__(self):
         return "<patchmeta %s %r>" % (self.op, self.path)
 
@@ -509,9 +517,8 @@
 eolmodes = ['strict', 'crlf', 'lf', 'auto']
 
 class patchfile(object):
-    def __init__(self, ui, fname, backend, store, mode, create, remove,
-                 eolmode='strict', copysource=None):
-        self.fname = fname
+    def __init__(self, ui, gp, backend, store, eolmode='strict'):
+        self.fname = gp.path
         self.eolmode = eolmode
         self.eol = None
         self.backend = backend
@@ -519,17 +526,17 @@
         self.lines = []
         self.exists = False
         self.missing = True
-        self.mode = mode
-        self.copysource = copysource
-        self.create = create
-        self.remove = remove
+        self.mode = gp.mode
+        self.copysource = gp.oldpath
+        self.create = gp.op in ('ADD', 'COPY', 'RENAME')
+        self.remove = gp.op == 'DELETE'
         try:
-            if copysource is None:
-                data, mode = backend.getfile(fname)
+            if self.copysource is None:
+                data, mode = backend.getfile(self.fname)
                 self.exists = True
             else:
-                data, mode = store.getfile(copysource)
-                self.exists = backend.exists(fname)
+                data, mode = store.getfile(self.copysource)
+                self.exists = backend.exists(self.fname)
             self.missing = False
             if data:
                 self.lines = data.splitlines(True)
@@ -549,7 +556,7 @@
                         nlines.append(l)
                     self.lines = nlines
         except IOError:
-            if create:
+            if self.create:
                 self.missing = False
             if self.mode is None:
                 self.mode = (False, False)
@@ -1016,14 +1023,7 @@
         count -= 1
     return path[:i].lstrip(), path[i:].rstrip()
 
-def selectfile(backend, afile_orig, bfile_orig, hunk, strip, gp):
-    if gp:
-        # Git patches do not play games. Excluding copies from the
-        # following heuristic avoids a lot of confusion
-        fname = pathstrip(gp.path, strip - 1)[1]
-        create = gp.op in ('ADD', 'COPY', 'RENAME')
-        remove = gp.op == 'DELETE'
-        return fname, create, remove
+def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
     nulla = afile_orig == "/dev/null"
     nullb = bfile_orig == "/dev/null"
     create = nulla and hunk.starta == 0 and hunk.lena == 0
@@ -1065,7 +1065,12 @@
         else:
             raise PatchError(_("undefined source and destination files"))
 
-    return fname, create, remove
+    gp = patchmeta(fname)
+    if create:
+        gp.op = 'ADD'
+    elif remove:
+        gp.op = 'DELETE'
+    return gp
 
 def scangitpatch(lr, firstline):
     """
@@ -1134,7 +1139,7 @@
             hunknum += 1
             if emitfile:
                 emitfile = False
-                yield 'file', (afile, bfile, h, gp)
+                yield 'file', (afile, bfile, h, gp and gp.copy() or None)
             yield 'hunk', h
         elif x.startswith('diff --git'):
             m = gitre.match(x)
@@ -1144,14 +1149,14 @@
                 # scan whole input for git metadata
                 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
                               in scangitpatch(lr, x)]
-                yield 'git', [g[2] for g in gitpatches
+                yield 'git', [g[2].copy() for g in gitpatches
                               if g[2].op in ('COPY', 'RENAME')]
                 gitpatches.reverse()
             afile = 'a/' + m.group(1)
             bfile = 'b/' + m.group(2)
             while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
                 gp = gitpatches.pop()[2]
-                yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
+                yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
             gp = gitpatches[-1][2]
             # copy/rename + modify should modify target, not source
             if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
@@ -1191,7 +1196,7 @@
 
     while gitpatches:
         gp = gitpatches.pop()[2]
-        yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
+        yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
 
 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
     """Reads a patch from fp and tries to apply it.
@@ -1228,41 +1233,38 @@
                 rejects += current_file.close()
                 current_file = None
             afile, bfile, first_hunk, gp = values
-            copysource = None
             if gp:
                 path = pstrip(gp.path)
+                gp.path = pstrip(gp.path)
                 if gp.oldpath:
-                    copysource = pstrip(gp.oldpath)
-                if gp.op == 'RENAME':
-                    backend.unlink(copysource)
-                if not first_hunk:
-                    if gp.op == 'DELETE':
-                        backend.unlink(path)
-                        continue
-                    data, mode = None, None
-                    if gp.op in ('RENAME', 'COPY'):
-                        data, mode = store.getfile(copysource)
-                    if gp.mode:
-                        mode = gp.mode
-                        if gp.op == 'ADD':
-                            # Added files without content have no hunk and
-                            # must be created
-                            data = ''
-                    if data or mode:
-                        if (gp.op in ('ADD', 'RENAME', 'COPY')
-                            and backend.exists(path)):
-                            raise PatchError(_("cannot create %s: destination "
-                                               "already exists") % path)
-                        backend.setfile(path, data, mode, copysource)
+                    gp.oldpath = pstrip(gp.oldpath)
+            else:
+                gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
+            if gp.op == 'RENAME':
+                backend.unlink(gp.oldpath)
             if not first_hunk:
+                if gp.op == 'DELETE':
+                    backend.unlink(gp.path)
+                    continue
+                data, mode = None, None
+                if gp.op in ('RENAME', 'COPY'):
+                    data, mode = store.getfile(gp.oldpath)
+                if gp.mode:
+                    mode = gp.mode
+                    if gp.op == 'ADD':
+                        # Added files without content have no hunk and
+                        # must be created
+                        data = ''
+                if data or mode:
+                    if (gp.op in ('ADD', 'RENAME', 'COPY')
+                        and backend.exists(gp.path)):
+                        raise PatchError(_("cannot create %s: destination "
+                                           "already exists") % gp.path)
+                    backend.setfile(gp.path, data, mode, gp.oldpath)
                 continue
             try:
-                mode = gp and gp.mode or None
-                current_file, create, remove = selectfile(
-                    backend, afile, bfile, first_hunk, strip, gp)
-                current_file = patcher(ui, current_file, backend, store, mode,
-                                       create, remove, eolmode=eolmode,
-                                       copysource=copysource)
+                current_file = patcher(ui, gp, backend, store,
+                                       eolmode=eolmode)
             except PatchError, inst:
                 ui.warn(str(inst) + '\n')
                 current_file = None
@@ -1395,14 +1397,14 @@
             if state == 'file':
                 afile, bfile, first_hunk, gp = values
                 if gp:
-                    changed.add(pathstrip(gp.path, strip - 1)[1])
-                    if gp.op == 'RENAME':
-                        changed.add(pathstrip(gp.oldpath, strip - 1)[1])
-                if not first_hunk:
-                    continue
-                current_file, create, remove = selectfile(
-                    backend, afile, bfile, first_hunk, strip, gp)
-                changed.add(current_file)
+                    gp.path = pathstrip(gp.path, strip - 1)[1]
+                    if gp.oldpath:
+                        gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
+                else:
+                    gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
+                changed.add(gp.path)
+                if gp.op == 'RENAME':
+                    changed.add(gp.oldpath)
             elif state not in ('hunk', 'git'):
                 raise util.Abort(_('unsupported parser state: %s') % state)
         return changed