Explorar o código

Merge branch 'issue-577' of https://github.com/jonashaag/dulwich

Jelmer Vernooij %!s(int64=7) %!d(string=hai) anos
pai
achega
d4ced71b4f
Modificáronse 3 ficheiros con 48 adicións e 5 borrados
  1. 5 0
      NEWS
  2. 14 1
      dulwich/archive.py
  3. 29 4
      dulwich/tests/test_archive.py

+ 5 - 0
NEWS

@@ -1,5 +1,10 @@
 0.18.7	UNRELEASED
 
+ BUG FIXES
+
+  * Make `dulwich.archive` set the gzip header file modification time so that
+    archives created from the same Git tree are always identical.
+    (#577, Jonas Haag)
 0.18.6	2017-11-11
 
  BUG FIXES

+ 14 - 1
dulwich/archive.py

@@ -26,6 +26,8 @@
 import posixpath
 import stat
 import tarfile
+import struct
+from os import SEEK_END
 from io import BytesIO
 from contextlib import closing
 
@@ -76,12 +78,23 @@ def tar_stream(store, tree, mtime, format=''):
     :param store: Object store to retrieve objects from
     :param tree: Tree object for the tree root
     :param mtime: UNIX timestamp that is assigned as the modification time for
-        all files
+        all files, and the gzip header modification time if format='gz'
     :param format: Optional compression format for tarball
     :return: Bytestrings
     """
     buf = BytesIO()
     with closing(tarfile.open(None, "w:%s" % format, buf)) as tar:
+        if format == 'gz':
+            # Manually correct the gzip header file modification time so that
+            # archives created from the same Git tree are always identical. 
+            # The gzip header file modification time is not currenctly
+            # accessible from the tarfile API, see: https://bugs.python.org/issue31526
+            buf.seek(0)
+            assert buf.read(2) == b'\x1f\x8b', 'Invalid gzip header'
+            buf.seek(4)
+            buf.write(struct.pack('<L', mtime))
+            buf.seek(0, SEEK_END)
+
         for entry_abspath, entry in _walk_tree(store, tree):
             try:
                 blob = store[entry.sha]

+ 29 - 4
dulwich/tests/test_archive.py

@@ -22,6 +22,8 @@
 
 from io import BytesIO
 import tarfile
+import struct
+from unittest import skipUnless
 
 from dulwich.archive import tar_stream
 from dulwich.object_store import (
@@ -38,6 +40,11 @@ from dulwich.tests.utils import (
     build_commit_graph,
     )
 
+try:
+    from mock import patch
+except ImportError:
+    patch = None
+
 
 class ArchiveTests(TestCase):
 
@@ -51,15 +58,33 @@ class ArchiveTests(TestCase):
         self.addCleanup(tf.close)
         self.assertEqual([], tf.getnames())
 
-    def test_simple(self):
+    def _get_example_tar_stream(self, *tar_stream_args, **tar_stream_kwargs):
         store = MemoryObjectStore()
         b1 = Blob.from_string(b"somedata")
         store.add_object(b1)
         t1 = Tree()
         t1.add(b"somename", 0o100644, b1.id)
         store.add_object(t1)
-        stream = b''.join(tar_stream(store, t1, 10))
-        out = BytesIO(stream)
-        tf = tarfile.TarFile(fileobj=out)
+        stream = b''.join(tar_stream(store, t1, *tar_stream_args, **tar_stream_kwargs))
+        return BytesIO(stream)
+
+    def test_simple(self):
+        stream = self._get_example_tar_stream(mtime=0)
+        tf = tarfile.TarFile(fileobj=stream)
         self.addCleanup(tf.close)
         self.assertEqual(["somename"], tf.getnames())
+
+    def test_gzip_mtime(self):
+        stream = self._get_example_tar_stream(mtime=1234, format='gz')
+        expected_mtime = struct.pack('<L', 1234)
+        self.assertEqual(stream.getvalue()[4:8], expected_mtime)
+
+    @skipUnless(patch, "Required mock.patch")
+    def test_same_file(self):
+        contents = [None, None]
+        for format in ['', 'gz', 'bz2']:
+            for i in [0, 1]:
+                with patch('time.time', return_value=i):
+                    stream = self._get_example_tar_stream(mtime=0, format=format)
+                    contents[i] = stream.getvalue()
+            self.assertEqual(contents[0], contents[1], "Different file contents for format %r" % format)