comparison mercurial/patch.py @ 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 cd79d9ab5e42
children 9658a79968c6
comparison
equal deleted inserted replaced
20136:1df77035c814 20137:9f1d4323c749
719 719
720 if isinstance(h, binhunk): 720 if isinstance(h, binhunk):
721 if self.remove: 721 if self.remove:
722 self.backend.unlink(self.fname) 722 self.backend.unlink(self.fname)
723 else: 723 else:
724 self.lines[:] = h.new() 724 l = h.new(self.lines)
725 self.offset += len(h.new()) 725 self.lines[:] = l
726 self.offset += len(l)
726 self.dirty = True 727 self.dirty = True
727 return 0 728 return 0
728 729
729 horig = h 730 horig = h
730 if (self.eolmode in ('crlf', 'lf') 731 if (self.eolmode in ('crlf', 'lf')
1014 if self.lenb and newstart > 0: 1015 if self.lenb and newstart > 0:
1015 newstart -= 1 1016 newstart -= 1
1016 return old, oldstart, new, newstart 1017 return old, oldstart, new, newstart
1017 1018
1018 class binhunk(object): 1019 class binhunk(object):
1019 'A binary patch file. Only understands literals so far.' 1020 'A binary patch file.'
1020 def __init__(self, lr, fname): 1021 def __init__(self, lr, fname):
1021 self.text = None 1022 self.text = None
1023 self.delta = False
1022 self.hunk = ['GIT binary patch\n'] 1024 self.hunk = ['GIT binary patch\n']
1023 self._fname = fname 1025 self._fname = fname
1024 self._read(lr) 1026 self._read(lr)
1025 1027
1026 def complete(self): 1028 def complete(self):
1027 return self.text is not None 1029 return self.text is not None
1028 1030
1029 def new(self): 1031 def new(self, lines):
1032 if self.delta:
1033 return [applybindelta(self.text, ''.join(lines))]
1030 return [self.text] 1034 return [self.text]
1031 1035
1032 def _read(self, lr): 1036 def _read(self, lr):
1033 def getline(lr, hunk): 1037 def getline(lr, hunk):
1034 l = lr.readline() 1038 l = lr.readline()
1035 hunk.append(l) 1039 hunk.append(l)
1036 return l.rstrip('\r\n') 1040 return l.rstrip('\r\n')
1037 1041
1042 size = 0
1038 while True: 1043 while True:
1039 line = getline(lr, self.hunk) 1044 line = getline(lr, self.hunk)
1040 if not line: 1045 if not line:
1041 raise PatchError(_('could not extract "%s" binary data') 1046 raise PatchError(_('could not extract "%s" binary data')
1042 % self._fname) 1047 % self._fname)
1043 if line.startswith('literal '): 1048 if line.startswith('literal '):
1049 size = int(line[8:].rstrip())
1044 break 1050 break
1045 size = int(line[8:].rstrip()) 1051 if line.startswith('delta '):
1052 size = int(line[6:].rstrip())
1053 self.delta = True
1054 break
1046 dec = [] 1055 dec = []
1047 line = getline(lr, self.hunk) 1056 line = getline(lr, self.hunk)
1048 while len(line) > 1: 1057 while len(line) > 1:
1049 l = line[0] 1058 l = line[0]
1050 if l <= 'Z' and l >= 'A': 1059 if l <= 'Z' and l >= 'A':
1262 hunknum = 0 1271 hunknum = 0
1263 1272
1264 while gitpatches: 1273 while gitpatches:
1265 gp = gitpatches.pop() 1274 gp = gitpatches.pop()
1266 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) 1275 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1276
1277 def applybindelta(binchunk, data):
1278 """Apply a binary delta hunk
1279 The algorithm used is the algorithm from git's patch-delta.c
1280 """
1281 def deltahead(binchunk):
1282 i = 0
1283 for c in binchunk:
1284 i += 1
1285 if not (ord(c) & 0x80):
1286 return i
1287 return i
1288 out = ""
1289 s = deltahead(binchunk)
1290 binchunk = binchunk[s:]
1291 s = deltahead(binchunk)
1292 binchunk = binchunk[s:]
1293 i = 0
1294 while i < len(binchunk):
1295 cmd = ord(binchunk[i])
1296 i += 1
1297 if (cmd & 0x80):
1298 offset = 0
1299 size = 0
1300 if (cmd & 0x01):
1301 offset = ord(binchunk[i])
1302 i += 1
1303 if (cmd & 0x02):
1304 offset |= ord(binchunk[i]) << 8
1305 i += 1
1306 if (cmd & 0x04):
1307 offset |= ord(binchunk[i]) << 16
1308 i += 1
1309 if (cmd & 0x08):
1310 offset |= ord(binchunk[i]) << 24
1311 i += 1
1312 if (cmd & 0x10):
1313 size = ord(binchunk[i])
1314 i += 1
1315 if (cmd & 0x20):
1316 size |= ord(binchunk[i]) << 8
1317 i += 1
1318 if (cmd & 0x40):
1319 size |= ord(binchunk[i]) << 16
1320 i += 1
1321 if size == 0:
1322 size = 0x10000
1323 offset_end = offset + size
1324 out += data[offset:offset_end]
1325 elif cmd != 0:
1326 offset_end = i + cmd
1327 out += binchunk[i:offset_end]
1328 i += cmd
1329 else:
1330 raise PatchError(_('unexpected delta opcode 0'))
1331 return out
1267 1332
1268 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): 1333 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1269 """Reads a patch from fp and tries to apply it. 1334 """Reads a patch from fp and tries to apply it.
1270 1335
1271 Returns 0 for a clean patch, -1 if any rejects were found and 1 if 1336 Returns 0 for a clean patch, -1 if any rejects were found and 1 if