[PATCH V3] patch: add support for git delta hunks

Nicolas Vigier boklm at mars-attacks.org
Wed Nov 27 11:46:13 CST 2013


# HG changeset patch
# User Nicolas Vigier <boklm at mars-attacks.org>
# Date 1385573940 -3600
# Node ID 9d15bd7b26ab5b379ea346bba0cd607c12b8ce3f
# Parent  1c46b18b0e1c47fa4cecf21b78c083a54ae9903f
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

diff -r 1c46b18b0e1c -r 9d15bd7b26ab mercurial/patch.py
--- a/mercurial/patch.py	Fri Nov 22 17:26:58 2013 -0600
+++ 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.
 
diff -r 1c46b18b0e1c -r 9d15bd7b26ab tests/test-import-git.t
--- a/tests/test-import-git.t	Fri Nov 22 17:26:58 2013 -0600
+++ 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
+  > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
+  > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M at d9MRCwC#oC!>o#}>x{(W-y~UN*tK
+  > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
+  > zmGvS5isFJI;Pd_7J+EKxyHZeu`^t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
+  > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
+  > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
+  > zjxH`PPEJmgD7U?IK&h;(EGQ at _DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
+  > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
+  > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
+  > z%*@R2 at bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC at X~
+  > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
+  > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
+  > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
+  > zGiS21vn>WCCr+GLx^!uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H at 8N~%&V%Z
+  > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc^)dfdvA^5u`Xf;Zzu<ZQHgG?28V-#s<;T
+  > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
+  > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c at k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
+  > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK^z_$W
+  > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
+  > zunCgwT at 1|CU9+;X^4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV at B)Nt))J7
+  > zh>{5j2 at f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
+  > z at 57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
+  > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt}^73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
+  > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
+  > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY at u~Ua~K3%tN
+  > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{^{P9Ot5vecD?%1&-E-ntBFj87(
+  > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
+  > zARqv^bm-85xnRCng3OT|MyVSmR3ND7^?KaQGG!^(aTbo1N;Nz;X3Q9FJbwK6`0?Yp
+  > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E^WAsfrK=x&@B!`w7Hik81sPz4
+  > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k^7>9!)56DbjmA~`OJJ40v
+  > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x at sY#t7LxwoEcVw*)cWhhQW at l%!
+  > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv!^5Lfr%vKg
+  > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g^R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
+  > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl at xH#kS~VYE
+  > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS at J3
+  > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
+  > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2 at L^Bgds$(&P7M4dhfmWe)!=B
+  > zR3X=Y{P9N}p at -##@1ZNW1YbVaiP~D at 8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
+  > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
+  > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
+  > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
+  > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj at i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
+  > zrok&TPU40rL0ZYDwenNrrmPZ`gjo at DEF`7^cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
+  > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p^{}n$q
+  > z3 at 9W=KmGI*Ng_Q#AzA%-z|Z^|#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
+  > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt^asFCe0&a at tqp
+  > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
+  > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
+  > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~^QD#sw3&vS$FwlTk
+  > zp1#Gw!Qo-$LtvpXt#ApV0g)^F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
+  > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
+  > zk+^N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
+  > zld+peue4aYbg3C0!+4mu+}vE^j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
+  > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o^?yV4$|UQ(j&UWCH>M=o_&N
+  > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3^aJP!&D4RvvGfoyo(>C>la
+  > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
+  > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
+  > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
+  > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
+  > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
+  > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
+  > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@^xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
+  > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
+  > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
+  > z at Bs)w3=lzfKoufH$SFuPG at uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
+  > zManE8^dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
+  > zA?R$|wvC+m^Ups=*(4lDh*=UN8{5h(A?p#D^2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
+  > z#~**JC8hE4 at n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl^*=8x3n&;akBf^-
+  > zJd&86kq$%%907v^tgWoQdwI`|oNK%VvU~S#C<o^F?6c48?Cjj#-4P<>HFD%&|Ni~t
+  > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X>^L}J at W6osP3piLD_5?R!`S}*{xLBzFiL4@
+  > zX+}l{`A%?f at T5tT%ztu60p;)be`fWC`tP at WpO=?cpf8Xuf1OSj6d3f at Ki(ovDYq%0
+  > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
+  > 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


More information about the Mercurial-devel mailing list