diff --git a/README.rst b/README.rst index 34ec63c..09bb289 100644 --- a/README.rst +++ b/README.rst @@ -41,7 +41,8 @@ differences: - AArch64 There is also a wrapper of `HDiffPatch`_, implementing basic -operations. +operations. Adds a custom header, so it is not compatible with +original HDiffPatch implementation as of today. Project homepage: https://github.com/eerimoq/detools @@ -135,7 +136,8 @@ the original HDiffPatch program. $ ls -l foo-hdiffpatch.patch -rw-rw-r-- 1 erik erik 261 Apr 22 18:20 foo-hdiffpatch.patch -Lower hdiffpatch memory usage with --match-block-size. +Lower hdiffpatch memory usage with ``--match-block-size``. Mainly +useful for big files. .. code-block:: text diff --git a/detools/apply.py b/detools/apply.py index b9ce439..18757d7 100644 --- a/detools/apply.py +++ b/detools/apply.py @@ -18,6 +18,7 @@ from .common import COMPRESSION_LZ4 from .common import PATCH_TYPE_NORMAL from .common import PATCH_TYPE_IN_PLACE +from .common import PATCH_TYPE_HDIFFPATCH from .common import format_bad_compression_string from .common import format_bad_compression_number from .common import file_size @@ -155,7 +156,9 @@ def read_header_normal(fpatch): patch_type, compression = unpack_header(header) if patch_type != PATCH_TYPE_NORMAL: - raise Error("Expected patch type 0, but got {}.".format(patch_type)) + raise Error( + "Expected patch type {}, but got {}.".format(PATCH_TYPE_NORMAL, + patch_type)) compression = convert_compression(compression) to_size = unpack_size(fpatch) @@ -163,6 +166,30 @@ def read_header_normal(fpatch): return compression, to_size +def read_header_hdiffpatch(fpatch): + """Read a hdiffpatch header. + + """ + + header = fpatch.read(1) + + if len(header) != 1: + raise Error('Failed to read the patch header.') + + patch_type, compression = unpack_header(header) + + if patch_type != PATCH_TYPE_HDIFFPATCH: + raise Error( + "Expected patch type {}, but got {}.".format(PATCH_TYPE_HDIFFPATCH, + patch_type)) + + compression = convert_compression(compression) + to_size = unpack_size(fpatch) + patch_size = unpack_size(fpatch) + + return compression, to_size, patch_size + + def read_header_in_place(fpatch): """Read an in-place header. @@ -176,7 +203,9 @@ def read_header_in_place(fpatch): patch_type, compression = unpack_header(header) if patch_type != PATCH_TYPE_IN_PLACE: - raise Error("Expected patch type 1, but got {}.".format(patch_type)) + raise Error( + "Expected patch type {}, but got {}.".format(PATCH_TYPE_IN_PLACE, + patch_type)) compression = convert_compression(compression) memory_size = unpack_size(fpatch) @@ -487,7 +516,14 @@ def apply_patch_hdiffpatch(ffrom, fpatch, fto): """ - to_data = hdiffpatch.apply_patch(file_read(ffrom), file_read(fpatch)) + compression, to_size, patch_size = read_header_hdiffpatch(fpatch) + + if to_size == 0: + return to_size + + patch_reader = PatchReader(fpatch, compression) + to_data = hdiffpatch.apply_patch(file_read(ffrom), + patch_reader.read(patch_size)) return fto.write(to_data) diff --git a/detools/common.py b/detools/common.py index b52480e..755c01f 100644 --- a/detools/common.py +++ b/detools/common.py @@ -9,8 +9,9 @@ from .bsdiff import pack_size -PATCH_TYPE_NORMAL = 0 -PATCH_TYPE_IN_PLACE = 1 +PATCH_TYPE_NORMAL = 0 +PATCH_TYPE_IN_PLACE = 1 +PATCH_TYPE_HDIFFPATCH = 2 COMPRESSION_NONE = 0 COMPRESSION_LZMA = 1 diff --git a/detools/create.py b/detools/create.py index ea42f52..b1facab 100644 --- a/detools/create.py +++ b/detools/create.py @@ -11,6 +11,7 @@ from .compression.lz4 import Lz4Compressor from .common import PATCH_TYPE_NORMAL from .common import PATCH_TYPE_IN_PLACE +from .common import PATCH_TYPE_HDIFFPATCH from .common import DATA_FORMATS from .common import format_bad_compression_string from .common import compression_string_to_number @@ -259,11 +260,21 @@ def create_patch_bsdiff(ffrom, fto, fpatch): fpatch.write(fextra.getvalue()) -def create_patch_hdiffpatch(ffrom, fto, fpatch, match_block_size): +def create_patch_hdiffpatch(ffrom, + fto, + fpatch, + compression, + match_block_size): patch = hdiffpatch.create_patch(file_read(ffrom), file_read(fto), match_block_size) - fpatch.write(patch) + compressor = create_compressor(compression) + fpatch.write(pack_header(PATCH_TYPE_HDIFFPATCH, + compression_string_to_number(compression))) + fpatch.write(pack_size(file_size(fto))) + fpatch.write(pack_size(len(patch))) + fpatch.write(compressor.compress(patch)) + fpatch.write(compressor.flush()) def create_patch(ffrom, @@ -348,7 +359,11 @@ def create_patch(ffrom, elif patch_type == 'bsdiff': create_patch_bsdiff(ffrom, fto, fpatch) elif patch_type == 'hdiffpatch': - create_patch_hdiffpatch(ffrom, fto, fpatch, match_block_size) + create_patch_hdiffpatch(ffrom, + fto, + fpatch, + compression, + match_block_size) else: raise Error("Bad patch type '{}'.".format(patch_type)) diff --git a/detools/version.py b/detools/version.py index 3c92ca2..2670d05 100644 --- a/detools/version.py +++ b/detools/version.py @@ -1 +1 @@ -__version__ = '0.34.2' +__version__ = '0.35.0' diff --git a/src/c/detools.h b/src/c/detools.h index 2828e02..60c90bf 100644 --- a/src/c/detools.h +++ b/src/c/detools.h @@ -60,7 +60,7 @@ #include #include -#define PBTOOLS_VERSION "0.34.2" +#define PBTOOLS_VERSION "0.35.0" /* Error codes. */ #define DETOOLS_OK 0 diff --git a/tests/files/foo/hdiffpatch-match-block-size-64.patch b/tests/files/foo/hdiffpatch-match-block-size-64.patch index 5260955..b7da8e6 100644 Binary files a/tests/files/foo/hdiffpatch-match-block-size-64.patch and b/tests/files/foo/hdiffpatch-match-block-size-64.patch differ diff --git a/tests/files/foo/hdiffpatch-none.patch b/tests/files/foo/hdiffpatch-none.patch new file mode 100644 index 0000000..682fa47 Binary files /dev/null and b/tests/files/foo/hdiffpatch-none.patch differ diff --git a/tests/files/foo/hdiffpatch.patch b/tests/files/foo/hdiffpatch.patch index 969dd56..0e8259a 100644 Binary files a/tests/files/foo/hdiffpatch.patch and b/tests/files/foo/hdiffpatch.patch differ diff --git a/tests/test_command_line.py b/tests/test_command_line.py index 30bffc1..f71a8ea 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -2031,6 +2031,27 @@ def test_command_line_create_patch_foo_hdiffpatch(self): self.assertEqual(read_file(foo_patch), read_file('tests/files/foo/hdiffpatch.patch')) + def test_command_line_create_patch_foo_hdiffpatch_none(self): + foo_patch = 'foo.patch' + argv = [ + 'detools', + 'create_patch', + '--type', 'hdiffpatch', + '-c', 'none', + 'tests/files/foo/old', + 'tests/files/foo/new', + foo_patch + ] + + if os.path.exists(foo_patch): + os.remove(foo_patch) + + with patch('sys.argv', argv): + detools._main() + + self.assertEqual(read_file(foo_patch), + read_file('tests/files/foo/hdiffpatch-none.patch')) + def test_command_line_create_patch_foo_hdiffpatch_match_block_size_64(self): foo_patch = 'foo.patch' argv = [ @@ -2072,6 +2093,25 @@ def test_command_line_apply_patch_foo_hdiffpatch(self): self.assertEqual(read_file(foo_new), read_file('tests/files/foo/new')) + def test_command_line_apply_patch_foo_hdiffpatch_none(self): + foo_new = 'foo.new' + argv = [ + 'detools', + 'apply_patch_hdiffpatch', + 'tests/files/foo/old', + 'tests/files/foo/hdiffpatch-none.patch', + foo_new + ] + + if os.path.exists(foo_new): + os.remove(foo_new) + + with patch('sys.argv', argv): + detools._main() + + self.assertEqual(read_file(foo_new), + read_file('tests/files/foo/new')) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_detools.py b/tests/test_detools.py index 3af5ba8..2665df7 100644 --- a/tests/test_detools.py +++ b/tests/test_detools.py @@ -835,7 +835,7 @@ def test_create_and_apply_patch_micropython_bsdiff(self): 'bsdiff.patch', patch_type='bsdiff') - def test_create_and_apply_patch_micropython_hdiffpatch(self): + def test_create_and_apply_patch_foo_hdiffpatch(self): self.assert_create_and_apply_patch('tests/files/foo/old', 'tests/files/foo/new', 'tests/files/foo/hdiffpatch.patch', diff --git a/tests/test_hdiffpatch.py b/tests/test_hdiffpatch.py deleted file mode 100644 index cad7a34..0000000 --- a/tests/test_hdiffpatch.py +++ /dev/null @@ -1,27 +0,0 @@ -import unittest -import struct - -import detools.hdiffpatch - - -def read_file(filename): - with open(filename, 'rb') as fin: - return fin.read() - - -class DetoolsHDiffPatchTest(unittest.TestCase): - - maxDiff = None - - def test_diff_patch(self): - diff = detools.hdiffpatch.create_patch(read_file('tests/files/foo/old'), - read_file('tests/files/foo/new'), - 0) - self.assertEqual(diff, read_file('tests/files/foo/hdiffpatch.patch')) - to = detools.hdiffpatch.apply_patch(read_file('tests/files/foo/old'), - bytes(diff)) - self.assertEqual(to, read_file('tests/files/foo/new')) - - -if __name__ == '__main__': - unittest.main()