Kaynağa Gözat

Various bundle fixes (#1965)

Jelmer Vernooij 2 ay önce
ebeveyn
işleme
8df4e28b04
3 değiştirilmiş dosya ile 126 ekleme ve 26 silme
  1. 16 26
      dulwich/bundle.py
  2. 47 0
      tests/compat/test_bundle.py
  3. 63 0
      tests/test_bundle.py

+ 16 - 26
dulwich/bundle.py

@@ -272,32 +272,22 @@ def create_bundle_from_repo(
     bundle_prerequisites = []
     have_objects = set()
     for prereq in prerequisites:
-        if isinstance(prereq, str):
-            prereq = prereq.encode("utf-8")
-        if isinstance(prereq, bytes):
-            if len(prereq) == 40:  # SHA1 hex string
-                try:
-                    # Validate it's actually hex
-                    bytes.fromhex(prereq.decode("utf-8"))
-                    # Store hex in bundle and for pack generation
-                    bundle_prerequisites.append((prereq, b""))
-                    have_objects.add(prereq)
-                except ValueError:
-                    # Not a valid hex string, invalid prerequisite
-                    raise ValueError(f"Invalid prerequisite format: {prereq!r}")
-            elif len(prereq) == 20:
-                # Binary SHA, convert to hex for both bundle and pack generation
-                hex_prereq = prereq.hex().encode("ascii")
-                bundle_prerequisites.append((hex_prereq, b""))
-                have_objects.add(hex_prereq)
-            else:
-                # Invalid length
-                raise ValueError(f"Invalid prerequisite SHA length: {len(prereq)}")
-        else:
-            # Assume it's already a binary SHA
-            hex_prereq = prereq.hex().encode("ascii")
-            bundle_prerequisites.append((hex_prereq, b""))
-            have_objects.append(hex_prereq)
+        if not isinstance(prereq, bytes):
+            raise TypeError(
+                f"Invalid prerequisite type: {type(prereq)}, expected bytes"
+            )
+        if len(prereq) != 40:
+            raise ValueError(
+                f"Invalid prerequisite SHA length: {len(prereq)}, expected 40 hex characters"
+            )
+        try:
+            # Validate it's actually hex
+            bytes.fromhex(prereq.decode("utf-8"))
+        except ValueError:
+            raise ValueError(f"Invalid prerequisite format: {prereq!r}")
+        # Store hex in bundle and for pack generation
+        bundle_prerequisites.append((prereq, b""))
+        have_objects.add(prereq)
 
     # Generate pack data containing all objects needed for the refs
     pack_count, pack_objects = repo.generate_pack_data(

+ 47 - 0
tests/compat/test_bundle.py

@@ -283,3 +283,50 @@ class CompatBundleTestCase(CompatTestCase):
         # Verify the cloned repository exists and has content
         self.assertTrue(os.path.exists(clone_path))
         self.assertTrue(os.path.exists(os.path.join(clone_path, "test.txt")))
+
+    def test_unbundle_git_bundle(self) -> None:
+        """Test unbundling a bundle created by git using dulwich CLI."""
+        # Create a repository with commits using git
+        run_git_or_fail(["config", "user.name", "Test User"], cwd=self.repo_path)
+        run_git_or_fail(
+            ["config", "user.email", "test@example.com"], cwd=self.repo_path
+        )
+
+        # Create commits
+        test_file = os.path.join(self.repo_path, "test.txt")
+        with open(test_file, "w") as f:
+            f.write("content 1\n")
+        run_git_or_fail(["add", "test.txt"], cwd=self.repo_path)
+        run_git_or_fail(["commit", "-m", "Commit 1"], cwd=self.repo_path)
+
+        with open(test_file, "a") as f:
+            f.write("content 2\n")
+        run_git_or_fail(["add", "test.txt"], cwd=self.repo_path)
+        run_git_or_fail(["commit", "-m", "Commit 2"], cwd=self.repo_path)
+
+        # Get commit SHA for verification
+        head_sha = run_git_or_fail(["rev-parse", "HEAD"], cwd=self.repo_path).strip()
+
+        # Create bundle using git
+        bundle_path = os.path.join(self.test_dir, "unbundle_test.bundle")
+        run_git_or_fail(["bundle", "create", bundle_path, "master"], cwd=self.repo_path)
+
+        # Create a new empty repository to unbundle into
+        unbundle_repo_path = os.path.join(self.test_dir, "unbundle_repo")
+        unbundle_repo = Repo.init(unbundle_repo_path, mkdir=True)
+        self.addCleanup(unbundle_repo.close)
+
+        # Read the bundle and store objects using dulwich
+        with open(bundle_path, "rb") as f:
+            bundle = read_bundle(f)
+
+        # Use the bundle's store_objects method to unbundle
+        bundle.store_objects(unbundle_repo.object_store)
+
+        # Verify objects are now in the repository
+        # Check that the HEAD commit exists
+        self.assertIn(head_sha, unbundle_repo.object_store)
+
+        # Verify we can retrieve the commit
+        commit = unbundle_repo.object_store[head_sha]
+        self.assertEqual(b"Commit 2\n", commit.message)

+ 63 - 0
tests/test_bundle.py

@@ -449,3 +449,66 @@ class BundleTests(TestCase):
         # Verify capabilities are included
         self.assertEqual(bundle.capabilities, capabilities)
         self.assertEqual(bundle.version, 3)
+
+    def test_create_bundle_with_hex_bytestring_prerequisite(self) -> None:
+        """Test creating a bundle with prerequisite as 40-byte hex bytestring."""
+        repo = MemoryRepo()
+
+        # Create minimal objects
+        blob = Blob.from_string(b"Hello world")
+        repo.object_store.add_object(blob)
+
+        tree = Tree()
+        tree.add(b"hello.txt", 0o100644, blob.id)
+        repo.object_store.add_object(tree)
+
+        commit = Commit()
+        commit.tree = tree.id
+        commit.message = b"Initial commit"
+        commit.author = commit.committer = b"Test User <test@example.com>"
+        commit.commit_time = commit.author_time = 1234567890
+        commit.commit_timezone = commit.author_timezone = 0
+        repo.object_store.add_object(commit)
+
+        repo.refs[b"refs/heads/master"] = commit.id
+
+        # Create another blob to use as prerequisite
+        prereq_blob = Blob.from_string(b"prerequisite")
+
+        # Use blob.id directly (40-byte hex bytestring)
+        bundle = create_bundle_from_repo(repo, prerequisites=[prereq_blob.id])
+
+        # Verify the prerequisite was added correctly
+        self.assertEqual(len(bundle.prerequisites), 1)
+        self.assertEqual(bundle.prerequisites[0][0], prereq_blob.id)
+
+    def test_create_bundle_with_hex_bytestring_prerequisite_simple(self) -> None:
+        """Test creating a bundle with prerequisite as 40-byte hex bytestring."""
+        repo = MemoryRepo()
+
+        # Create minimal objects
+        blob = Blob.from_string(b"Hello world")
+        repo.object_store.add_object(blob)
+
+        tree = Tree()
+        tree.add(b"hello.txt", 0o100644, blob.id)
+        repo.object_store.add_object(tree)
+
+        commit = Commit()
+        commit.tree = tree.id
+        commit.message = b"Initial commit"
+        commit.author = commit.committer = b"Test User <test@example.com>"
+        commit.commit_time = commit.author_time = 1234567890
+        commit.commit_timezone = commit.author_timezone = 0
+        repo.object_store.add_object(commit)
+
+        repo.refs[b"refs/heads/master"] = commit.id
+
+        # Use a 40-byte hex bytestring as prerequisite
+        prereq_hex = b"aa" * 20
+
+        bundle = create_bundle_from_repo(repo, prerequisites=[prereq_hex])
+
+        # Verify the prerequisite was added correctly
+        self.assertEqual(len(bundle.prerequisites), 1)
+        self.assertEqual(bundle.prerequisites[0][0], prereq_hex)