patch: generalize the use of patchmeta in applydiff()
authorPatrick Mezard <pmezard@gmail.com>
Sat, 11 Jun 2011 14:17:25 +0200
changeset 14566 d0c2cc11e611
parent 14565 3cacc232f27f
child 14567 b72cef1b8b26
patch: generalize the use of patchmeta in applydiff() - Add patchmeta.copy() and emit copies from iterhunks. Modifying patchmeta instances in applydiff() makes things simpler. - Rename selectfile() into makepatchmeta(). It is responsible for creating patchmeta for regular patches. - Pass patchmeta objects to patchfile() directly patchmeta instances were associated with git patches, for regular patches we had to pass additional variables to tell the patch intent to patchfile(). Instead, we generate patchmeta for regular patches and pass them. This will also help with patch filtering by matcher objects.
hgext/keyword.py
mercurial/patch.py
--- a/hgext/keyword.py	Sat Jun 11 14:14:13 2011 +0200
+++ b/hgext/keyword.py	Sat Jun 11 14:17:25 2011 +0200
@@ -595,12 +595,10 @@
                 wlock.release()
 
     # monkeypatches
-    def kwpatchfile_init(orig, self, ui, fname, backend, store, mode, create,
-                         remove, eolmode=None, copysource=None):
+    def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
         '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
         rejects or conflicts due to expanded keywords in working dir.'''
-        orig(self, ui, fname, backend, store, mode, create, remove,
-             eolmode, copysource)
+        orig(self, ui, gp, backend, store, eolmode)
         # shrink keywords read from working dir
         self.lines = kwt.shrinklines(self.fname, self.lines)
 
--- 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