Mercurial > public > mercurial-scm > hg-stable
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 |