changeset 20137:9f1d4323c749

patch: add support for git delta hunks When creating patches modifying binary files using "git format-patch", git creates 'literal' and 'delta' hunks. Mercurial currently supports 'literal' hunks only, which makes it impossible to import patches with 'delta' hunks. This changeset adds support for 'delta' hunks. It is a reimplementation of patch-delta.c from git : http://git.kernel.org/cgit/git/git.git/tree/patch-delta.c
author Nicolas Vigier <boklm@mars-attacks.org>
date Wed, 27 Nov 2013 18:39:00 +0100
parents 1df77035c814
children db0f8738d3d4
files mercurial/patch.py tests/test-import-git.t
diffstat 2 files changed, 183 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/patch.py	Mon Nov 25 12:12:42 2013 -0500
+++ b/mercurial/patch.py	Wed Nov 27 18:39:00 2013 +0100
@@ -721,8 +721,9 @@
             if self.remove:
                 self.backend.unlink(self.fname)
             else:
-                self.lines[:] = h.new()
-                self.offset += len(h.new())
+                l = h.new(self.lines)
+                self.lines[:] = l
+                self.offset += len(l)
                 self.dirty = True
             return 0
 
@@ -1016,9 +1017,10 @@
         return old, oldstart, new, newstart
 
 class binhunk(object):
-    'A binary patch file. Only understands literals so far.'
+    'A binary patch file.'
     def __init__(self, lr, fname):
         self.text = None
+        self.delta = False
         self.hunk = ['GIT binary patch\n']
         self._fname = fname
         self._read(lr)
@@ -1026,7 +1028,9 @@
     def complete(self):
         return self.text is not None
 
-    def new(self):
+    def new(self, lines):
+        if self.delta:
+            return [applybindelta(self.text, ''.join(lines))]
         return [self.text]
 
     def _read(self, lr):
@@ -1035,14 +1039,19 @@
             hunk.append(l)
             return l.rstrip('\r\n')
 
+        size = 0
         while True:
             line = getline(lr, self.hunk)
             if not line:
                 raise PatchError(_('could not extract "%s" binary data')
                                  % self._fname)
             if line.startswith('literal '):
+                size = int(line[8:].rstrip())
                 break
-        size = int(line[8:].rstrip())
+            if line.startswith('delta '):
+                size = int(line[6:].rstrip())
+                self.delta = True
+                break
         dec = []
         line = getline(lr, self.hunk)
         while len(line) > 1:
@@ -1265,6 +1274,62 @@
         gp = gitpatches.pop()
         yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
 
+def applybindelta(binchunk, data):
+    """Apply a binary delta hunk
+    The algorithm used is the algorithm from git's patch-delta.c
+    """
+    def deltahead(binchunk):
+        i = 0
+        for c in binchunk:
+            i += 1
+            if not (ord(c) & 0x80):
+                return i
+        return i
+    out = ""
+    s = deltahead(binchunk)
+    binchunk = binchunk[s:]
+    s = deltahead(binchunk)
+    binchunk = binchunk[s:]
+    i = 0
+    while i < len(binchunk):
+        cmd = ord(binchunk[i])
+        i += 1
+        if (cmd & 0x80):
+            offset = 0
+            size = 0
+            if (cmd & 0x01):
+                offset = ord(binchunk[i])
+                i += 1
+            if (cmd & 0x02):
+                offset |= ord(binchunk[i]) << 8
+                i += 1
+            if (cmd & 0x04):
+                offset |= ord(binchunk[i]) << 16
+                i += 1
+            if (cmd & 0x08):
+                offset |= ord(binchunk[i]) << 24
+                i += 1
+            if (cmd & 0x10):
+                size = ord(binchunk[i])
+                i += 1
+            if (cmd & 0x20):
+                size |= ord(binchunk[i]) << 8
+                i += 1
+            if (cmd & 0x40):
+                size |= ord(binchunk[i]) << 16
+                i += 1
+            if size == 0:
+                size = 0x10000
+            offset_end = offset + size
+            out += data[offset:offset_end]
+        elif cmd != 0:
+            offset_end = i + cmd
+            out += binchunk[i:offset_end]
+            i += cmd
+        else:
+            raise PatchError(_('unexpected delta opcode 0'))
+    return out
+
 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
     """Reads a patch from fp and tries to apply it.
 
--- a/tests/test-import-git.t	Mon Nov 25 12:12:42 2013 -0500
+++ b/tests/test-import-git.t	Wed Nov 27 18:39:00 2013 +0100
@@ -320,6 +320,115 @@
   045c85ba38952325e126c70962cc0f9d9077bc67 644   mbinary1
   a874b471193996e7cb034bb301cac7bdaf3e3f46 644   mbinary2
 
+Binary file and delta hunk:
+
+  $ hg import -d "1000000 0" -m delta - <<'EOF'
+  > diff --git a/delta b/delta
+  > new file mode 100644
+  > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
+  > GIT binary patch
+  > literal 3749
+  > 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
+  > P00000NkvXXu0mjfZ5|Er
+  > 
+  > literal 0
+  > HcmV?d00001
+  > 
+  > EOF
+  applying patch from stdin
+
+  $ hg manifest --debug | grep delta
+  9600f98bb60ce732634d126aaa4ac1ec959c573e 644   delta
+
+  $ hg import -d "1000000 0" -m delta - <<'EOF'
+  > diff --git a/delta b/delta
+  > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
+  > GIT binary patch
+  > delta 49
+  > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
+  > FSO9ZT4bA`n
+  > 
+  > delta 49
+  > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
+  > HOA1;9yU-AD
+  > 
+  > EOF
+  applying patch from stdin
+
+  $ hg manifest --debug | grep delta
+  56094bbea136dcf8dbd4088f6af469bde1a98b75 644   delta
+
 Filenames with spaces:
 
   $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
@@ -334,7 +443,7 @@
   applying patch from stdin
 
   $ hg tip -q
-  12:47500ce1614e
+  14:4b79479c9a6d
 
   $ cat "foo bar"
   foo
@@ -357,7 +466,7 @@
   applying patch from stdin
 
   $ hg tip -q
-  13:6757efb07ea9
+  15:9cbe44af4ae9
 
   $ cat foo3
   foo
@@ -392,8 +501,8 @@
 Invalid base85 content
 
   $ hg rollback
-  repository tip rolled back to revision 14 (undo import)
-  working directory now based on revision 14
+  repository tip rolled back to revision 16 (undo import)
+  working directory now based on revision 16
   $ hg revert -aq
   $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
   > diff --git a/text2 b/binary2