Kaynağa Gözat

Merge branch 'update-git-diff-format' of git://github.com/comet-ml/dulwich

Jelmer Vernooij 6 yıl önce
ebeveyn
işleme
ed644d133b

+ 25 - 12
dulwich/patch.py

@@ -185,8 +185,8 @@ def write_object_diff(f, store, old_file, new_file, diff_binary=False):
     """
     (old_path, old_mode, old_id) = old_file
     (new_path, new_mode, new_id) = new_file
-    old_path = patch_filename(old_path, b"a")
-    new_path = patch_filename(new_path, b"b")
+    patched_old_path = patch_filename(old_path, b"a")
+    patched_new_path = patch_filename(new_path, b"b")
 
     def content(mode, hexsha):
         if hexsha is None:
@@ -207,11 +207,17 @@ def write_object_diff(f, store, old_file, new_file, diff_binary=False):
     new_content = content(new_mode, new_id)
     if not diff_binary and (
             is_binary(old_content.data) or is_binary(new_content.data)):
-        f.write(b"Binary files " + old_path + b" and " + new_path +
-                b" differ\n")
+        binary_diff = (
+            b"Binary files "
+            + patched_old_path
+            + b" and "
+            + patched_new_path
+            + b" differ\n"
+        )
+        f.write(binary_diff)
     else:
         f.writelines(unified_diff(lines(old_content), lines(new_content),
-                     old_path, new_path))
+                     patched_old_path, patched_new_path))
 
 
 # TODO(jelmer): Support writing unicode, rather than bytes.
@@ -225,16 +231,23 @@ def gen_diff_header(paths, modes, shas):
     (old_path, new_path) = paths
     (old_mode, new_mode) = modes
     (old_sha, new_sha) = shas
+    if old_path is None and new_path is not None:
+        old_path = new_path
+    if new_path is None and old_path is not None:
+        new_path = old_path
+    old_path = patch_filename(old_path, b"a")
+    new_path = patch_filename(new_path, b"b")
     yield b"diff --git " + old_path + b" " + new_path + b"\n"
+
     if old_mode != new_mode:
         if new_mode is not None:
             if old_mode is not None:
-                yield ("old mode %o\n" % old_mode).encode('ascii')
-            yield ("new mode %o\n" % new_mode).encode('ascii')
+                yield ("old file mode %o\n" % old_mode).encode('ascii')
+            yield ("new file mode %o\n" % new_mode).encode('ascii')
         else:
-            yield ("deleted mode %o\n" % old_mode).encode('ascii')
+            yield ("deleted file mode %o\n" % old_mode).encode('ascii')
     yield b"index " + shortid(old_sha) + b".." + shortid(new_sha)
-    if new_mode is not None:
+    if new_mode is not None and old_mode is not None:
         yield (" %o" % new_mode).encode('ascii')
     yield b"\n"
 
@@ -251,8 +264,8 @@ def write_blob_diff(f, old_file, new_file):
     """
     (old_path, old_mode, old_blob) = old_file
     (new_path, new_mode, new_blob) = new_file
-    old_path = patch_filename(old_path, b"a")
-    new_path = patch_filename(new_path, b"b")
+    patched_old_path = patch_filename(old_path, b"a")
+    patched_new_path = patch_filename(new_path, b"b")
 
     def lines(blob):
         if blob is not None:
@@ -265,7 +278,7 @@ def write_blob_diff(f, old_file, new_file):
     old_contents = lines(old_blob)
     new_contents = lines(new_blob)
     f.writelines(unified_diff(old_contents, new_contents,
-                 old_path, new_path))
+                 patched_old_path, patched_new_path))
 
 
 def write_tree_diff(f, store, old_tree, new_tree, diff_binary=False):

+ 122 - 0
dulwich/tests/compat/test_patch.py

@@ -0,0 +1,122 @@
+# test_patch.py -- test patch compatibility with CGit
+# Copyright (C) 2019 Boris Feld <boris@comet.ml>
+#
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as public by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
+"""Tests related to patch compatibility with CGit."""
+from io import BytesIO
+import os
+import shutil
+import tempfile
+
+from dulwich import porcelain
+from dulwich.repo import (
+    Repo,
+    )
+from dulwich.tests import (
+    TestCase,
+    )
+from dulwich.tests.compat.utils import (
+    run_git_or_fail,
+    )
+
+
+class CompatPatchTestCase(TestCase):
+
+    def setUp(self):
+        super(CompatPatchTestCase, self).setUp()
+        self.test_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.test_dir)
+        self.repo_path = os.path.join(self.test_dir, 'repo')
+        self.repo = Repo.init(self.repo_path, mkdir=True)
+        self.addCleanup(self.repo.close)
+
+    def test_patch_apply(self):
+        # Prepare the repository
+
+        # Create some files and commit them
+        file_list = ["to_exists", "to_modify", "to_delete"]
+        for file in file_list:
+            file_path = os.path.join(self.repo_path, file)
+
+            # Touch the files
+            with open(file_path, "w"):
+                pass
+
+        self.repo.stage(file_list)
+
+        first_commit = self.repo.do_commit(b"The first commit")
+
+        # Make a copy of the repository so we can apply the diff later
+        copy_path = os.path.join(self.test_dir, "copy")
+        shutil.copytree(self.repo_path, copy_path)
+
+        # Do some changes
+        with open(os.path.join(self.repo_path, "to_modify"), "w") as f:
+            f.write("Modified!")
+
+        os.remove(os.path.join(self.repo_path, "to_delete"))
+
+        with open(os.path.join(self.repo_path, "to_add"), "w"):
+            pass
+
+        self.repo.stage(["to_modify", "to_delete", "to_add"])
+
+        second_commit = self.repo.do_commit(b"The second commit")
+
+        # Get the patch
+        first_tree = self.repo[first_commit].tree
+        second_tree = self.repo[second_commit].tree
+
+        outstream = BytesIO()
+        porcelain.diff_tree(self.repo.path, first_tree, second_tree,
+                            outstream=outstream)
+
+        # Save it on disk
+        patch_path = os.path.join(self.test_dir, "patch.patch")
+        with open(patch_path, "wb") as patch:
+            patch.write(outstream.getvalue())
+
+        # And try to apply it to the copy directory
+        git_command = ["-C", copy_path, "apply", patch_path]
+        run_git_or_fail(git_command)
+
+        # And now check that the files contents are exactly the same between
+        # the two repositories
+        original_files = set(os.listdir(self.repo_path))
+        new_files = set(os.listdir(copy_path))
+
+        # Check that we have the exact same files in both repositories
+        self.assertEquals(original_files, new_files)
+
+        for file in original_files:
+            if file == ".git":
+                continue
+
+            original_file_path = os.path.join(self.repo_path, file)
+            copy_file_path = os.path.join(copy_path, file)
+
+            self.assertTrue(os.path.isfile(copy_file_path))
+
+            with open(original_file_path, "rb") as original_file:
+                original_content = original_file.read()
+
+            with open(copy_file_path, "rb") as copy_file:
+                copy_content = copy_file.read()
+
+            self.assertEquals(original_content, copy_content)

+ 22 - 22
dulwich/tests/test_patch.py

@@ -276,9 +276,9 @@ class DiffTests(TestCase):
             f, (None, None, None),
             (b"bar.txt", 0o644, Blob.from_string(b"new\nsame\n")))
         self.assertEqual([
-             b'diff --git /dev/null b/bar.txt',
-             b'new mode 644',
-             b'index 0000000..a116b51 644',
+             b'diff --git a/bar.txt b/bar.txt',
+             b'new file mode 644',
+             b'index 0000000..a116b51',
              b'--- /dev/null',
              b'+++ b/bar.txt',
              b'@@ -0,0 +1,2 @@',
@@ -292,8 +292,8 @@ class DiffTests(TestCase):
             f, (b"bar.txt", 0o644, Blob.from_string(b"new\nsame\n")),
             (None, None, None))
         self.assertEqual([
-            b'diff --git a/bar.txt /dev/null',
-            b'deleted mode 644',
+            b'diff --git a/bar.txt b/bar.txt',
+            b'deleted file mode 644',
             b'index a116b51..0000000',
             b'--- a/bar.txt',
             b'+++ /dev/null',
@@ -322,9 +322,9 @@ class DiffTests(TestCase):
             tree1, tree2, added, removed, changed1, changed2, unchanged]])
         write_tree_diff(f, store, tree1.id, tree2.id)
         self.assertEqual([
-            b'diff --git /dev/null b/added.txt',
-            b'new mode 644',
-            b'index 0000000..76d4bb8 644',
+            b'diff --git a/added.txt b/added.txt',
+            b'new file mode 644',
+            b'index 0000000..76d4bb8',
             b'--- /dev/null',
             b'+++ b/added.txt',
             b'@@ -0,0 +1 @@',
@@ -337,8 +337,8 @@ class DiffTests(TestCase):
             b' unchanged',
             b'-removed',
             b'+added',
-            b'diff --git a/removed.txt /dev/null',
-            b'deleted mode 644',
+            b'diff --git a/removed.txt b/removed.txt',
+            b'deleted file mode 644',
             b'index 2c3f0b3..0000000',
             b'--- a/removed.txt',
             b'+++ /dev/null',
@@ -394,9 +394,9 @@ class DiffTests(TestCase):
         write_object_diff(f, store, (None, None, None),
                                     (b"bar.txt", 0o644, b2.id))
         self.assertEqual([
-             b'diff --git /dev/null b/bar.txt',
-             b'new mode 644',
-             b'index 0000000..a116b51 644',
+             b'diff --git a/bar.txt b/bar.txt',
+             b'new file mode 644',
+             b'index 0000000..a116b51',
              b'--- /dev/null',
              b'+++ b/bar.txt',
              b'@@ -0,0 +1,2 @@',
@@ -412,8 +412,8 @@ class DiffTests(TestCase):
         write_object_diff(f, store, (b"bar.txt", 0o644, b1.id),
                                     (None, None, None))
         self.assertEqual([
-            b'diff --git a/bar.txt /dev/null',
-            b'deleted mode 644',
+            b'diff --git a/bar.txt b/bar.txt',
+            b'deleted file mode 644',
             b'index a116b51..0000000',
             b'--- a/bar.txt',
             b'+++ /dev/null',
@@ -492,9 +492,9 @@ class DiffTests(TestCase):
         write_object_diff(f, store, (None, None, None),
                                     (b'bar.png', 0o644, b2.id))
         self.assertEqual([
-            b'diff --git /dev/null b/bar.png',
-            b'new mode 644',
-            b'index 0000000..06364b7 644',
+            b'diff --git a/bar.png b/bar.png',
+            b'new file mode 644',
+            b'index 0000000..06364b7',
             b'Binary files /dev/null and b/bar.png differ'
             ], f.getvalue().splitlines())
 
@@ -510,8 +510,8 @@ class DiffTests(TestCase):
         write_object_diff(f, store, (b'foo.png', 0o644, b1.id),
                                     (None, None, None))
         self.assertEqual([
-            b'diff --git a/foo.png /dev/null',
-            b'deleted mode 644',
+            b'diff --git a/foo.png b/foo.png',
+            b'deleted file mode 644',
             b'index f73e47d..0000000',
             b'Binary files a/foo.png and /dev/null differ'
             ], f.getvalue().splitlines())
@@ -527,8 +527,8 @@ class DiffTests(TestCase):
                 b"06d0bdd9e2e20377b3180e4986b14c8549b393e4"))
         self.assertEqual([
             b'diff --git a/bar.txt b/bar.txt',
-            b'old mode 644',
-            b'new mode 160000',
+            b'old file mode 644',
+            b'new file mode 160000',
             b'index a116b51..06d0bdd 160000',
             b'--- a/bar.txt',
             b'+++ b/bar.txt',

+ 86 - 7
dulwich/tests/test_porcelain.py

@@ -52,6 +52,9 @@ from dulwich.tests.utils import (
     make_commit,
     make_object,
     )
+from dulwich.tests.compat.utils import (
+    run_git_or_fail,
+    )
 
 
 def flat_walk_dir(dir_to_walk):
@@ -72,7 +75,8 @@ class PorcelainTestCase(TestCase):
         super(PorcelainTestCase, self).setUp()
         self.test_dir = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, self.test_dir)
-        self.repo = Repo.init(os.path.join(self.test_dir, 'repo'), mkdir=True)
+        self.repo_path = os.path.join(self.test_dir, 'repo')
+        self.repo = Repo.init(self.repo_path, mkdir=True)
         self.addCleanup(self.repo.close)
 
 
@@ -568,9 +572,9 @@ Date:   Fri Jan 01 2010 00:00:00 +0000
 
 Test message.
 
-diff --git /dev/null b/somename
-new mode 100644
-index 0000000..ea5c7bf 100644
+diff --git a/somename b/somename
+new file mode 100644
+index 0000000..ea5c7bf
 --- /dev/null
 +++ b/somename
 @@ -0,0 +1 @@
@@ -605,9 +609,9 @@ Date:   Fri Jan 01 2010 00:00:00 +0000
 
 Test message.
 
-diff --git /dev/null b/somename
-new mode 100644
-index 0000000..ea5c7bf 100644
+diff --git a/somename b/somename
+new file mode 100644
+index 0000000..ea5c7bf
 --- /dev/null
 +++ b/somename
 @@ -0,0 +1 @@
@@ -702,6 +706,81 @@ class DiffTreeTests(PorcelainTestCase):
                             outstream=outstream)
         self.assertEqual(outstream.getvalue(), b"")
 
+    def test_diff_apply(self):
+        # Prepare the repository
+
+        # Create some files and commit them
+        file_list = ["to_exists", "to_modify", "to_delete"]
+        for file in file_list:
+            file_path = os.path.join(self.repo_path, file)
+
+            # Touch the files
+            with open(file_path, "w"):
+                pass
+
+        self.repo.stage(file_list)
+
+        first_commit = self.repo.do_commit(b"The first commit")
+
+        # Make a copy of the repository so we can apply the diff later
+        copy_path = os.path.join(self.test_dir, "copy")
+        shutil.copytree(self.repo_path, copy_path)
+
+        # Do some changes
+        with open(os.path.join(self.repo_path, "to_modify"), "w") as f:
+            f.write("Modified!")
+
+        os.remove(os.path.join(self.repo_path, "to_delete"))
+
+        with open(os.path.join(self.repo_path, "to_add"), "w"):
+            pass
+
+        self.repo.stage(["to_modify", "to_delete", "to_add"])
+
+        second_commit = self.repo.do_commit(b"The second commit")
+
+        # Get the patch
+        first_tree = self.repo[first_commit].tree
+        second_tree = self.repo[second_commit].tree
+
+        outstream = BytesIO()
+        porcelain.diff_tree(self.repo.path, first_tree, second_tree,
+                            outstream=outstream)
+
+        # Save it on disk
+        patch_path = os.path.join(self.test_dir, "patch.patch")
+        with open(patch_path, "wb") as patch:
+            patch.write(outstream.getvalue())
+
+        # And try to apply it to the copy directory
+        git_command = ["-C", copy_path, "apply", patch_path]
+        run_git_or_fail(git_command)
+
+        # And now check that the files contents are exactly the same between
+        # the two repositories
+        original_files = set(os.listdir(self.repo_path))
+        new_files = set(os.listdir(copy_path))
+
+        # Check that we have the exact same files in both repositories
+        assert original_files == new_files
+
+        for file in original_files:
+            if file == ".git":
+                continue
+
+            original_file_path = os.path.join(self.repo_path, file)
+            copy_file_path = os.path.join(copy_path, file)
+
+            assert os.path.isfile(copy_file_path)
+
+            with open(original_file_path, "rb") as original_file:
+                original_content = original_file.read()
+
+            with open(copy_file_path, "rb") as copy_file:
+                copy_content = copy_file.read()
+
+            assert original_content == copy_content
+
 
 class CommitTreeTests(PorcelainTestCase):