Sfoglia il codice sorgente

Add blob_normalizer support to update_working_tree

- Add blob_normalizer parameter to update_working_tree function to support
  line ending conversions during checkout operations
- Update porcelain.pull() and porcelain.reset() to pass blob_normalizer
  from repository configuration

This enables proper line ending handling when checking out files from
the repository to the working directory.
Jelmer Vernooij 1 mese fa
parent
commit
5551881169
4 ha cambiato i file con 101 aggiunte e 2 eliminazioni
  1. 7 0
      dulwich/index.py
  2. 6 1
      dulwich/porcelain.py
  3. 4 1
      dulwich/repo.py
  4. 84 0
      tests/test_index.py

+ 7 - 0
dulwich/index.py

@@ -1492,6 +1492,7 @@ def update_working_tree(
     validate_path_element: Optional[Callable[[bytes], bool]] = None,
     symlink_fn: Optional[Callable] = None,
     force_remove_untracked: bool = False,
+    blob_normalizer: Optional["BlobNormalizer"] = None,
 ) -> None:
     """Update the working tree and index to match a new tree.
 
@@ -1510,6 +1511,8 @@ def update_working_tree(
       symlink_fn: Function to use for creating symlinks
       force_remove_untracked: If True, remove files that exist in working
         directory but not in target tree, even if old_tree_id is None
+      blob_normalizer: An optional BlobNormalizer to use for converting line
+        endings when writing blobs to the working directory.
     """
     import os
 
@@ -1552,6 +1555,10 @@ def update_working_tree(
         if not isinstance(blob_obj, Blob):
             raise ValueError(f"Object {entry.sha!r} is not a blob")
 
+        # Apply blob normalization for checkout if normalizer is provided
+        if blob_normalizer is not None:
+            blob_obj = blob_normalizer.checkout_normalize(blob_obj, entry.path)
+
         # Ensure parent directory exists
         parent_dir = os.path.dirname(full_path)
         if parent_dir and not os.path.exists(parent_dir):

+ 6 - 1
dulwich/porcelain.py

@@ -1575,6 +1575,7 @@ def reset(repo, mode, treeish="HEAD") -> None:
                         f.write(source)
 
             # Update working tree and index
+            blob_normalizer = r.get_blob_normalizer()
             update_working_tree(
                 r,
                 current_tree,
@@ -1583,6 +1584,7 @@ def reset(repo, mode, treeish="HEAD") -> None:
                 validate_path_element=validate_path_element,
                 symlink_fn=symlink_fn,
                 force_remove_untracked=True,
+                blob_normalizer=blob_normalizer,
             )
         else:
             raise Error(f"Invalid reset mode: {mode}")
@@ -1799,7 +1801,10 @@ def pull(
         # Skip if merge was performed as merge already updates the working tree
         if not merged and old_tree_id is not None:
             new_tree_id = r[b"HEAD"].tree
-            update_working_tree(r, old_tree_id, new_tree_id)
+            blob_normalizer = r.get_blob_normalizer()
+            update_working_tree(
+                r, old_tree_id, new_tree_id, blob_normalizer=blob_normalizer
+            )
         if remote_name is not None:
             _import_remote_refs(r.refs, remote_name, fetch_result.refs)
 

+ 4 - 1
dulwich/repo.py

@@ -2039,7 +2039,10 @@ class Repo(BaseRepo):
         git_attributes = {}
         config_stack = self.get_config_stack()
         try:
-            tree = self.object_store[self.refs[b"HEAD"]].tree
+            head_sha = self.refs[b"HEAD"]
+            # Peel tags to get the underlying commit
+            _, obj = peel_sha(self.object_store, head_sha)
+            tree = obj.tree
             return TreeBlobNormalizer(
                 config_stack,
                 git_attributes,

+ 84 - 0
tests/test_index.py

@@ -1644,3 +1644,87 @@ class TestPathPrefixCompression(TestCase):
         compressed = _compress_path(b"short", b"very/long/path/file.txt")
         decompressed, _ = _decompress_path(compressed, 0, b"very/long/path/file.txt")
         self.assertEqual(b"short", decompressed)
+
+
+class TestUpdateWorkingTree(TestCase):
+    def setUp(self):
+        self.tempdir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.tempdir)
+        from dulwich.repo import Repo
+
+        self.repo = Repo.init(self.tempdir)
+
+    def test_update_working_tree_with_blob_normalizer(self):
+        """Test update_working_tree with a blob normalizer."""
+        from dulwich.index import update_working_tree
+        from dulwich.objects import Blob, Tree
+
+        # Create a simple blob normalizer that converts CRLF to LF
+        class TestBlobNormalizer:
+            def checkout_normalize(self, blob, path):
+                # Convert CRLF to LF during checkout
+                new_blob = Blob()
+                new_blob.data = blob.data.replace(b"\r\n", b"\n")
+                return new_blob
+
+        # Create a tree with a file containing CRLF
+        blob = Blob()
+        blob.data = b"Hello\r\nWorld\r\n"
+        self.repo.object_store.add_object(blob)
+
+        tree = Tree()
+        tree[b"test.txt"] = (0o100644, blob.id)
+        self.repo.object_store.add_object(tree)
+
+        # Update working tree with normalizer
+        normalizer = TestBlobNormalizer()
+        update_working_tree(
+            self.repo,
+            None,  # old_tree_id
+            tree.id,  # new_tree_id
+            blob_normalizer=normalizer,
+        )
+
+        # Check that the file was written with LF line endings
+        test_file = os.path.join(self.tempdir, "test.txt")
+        with open(test_file, "rb") as f:
+            content = f.read()
+
+        self.assertEqual(b"Hello\nWorld\n", content)
+
+        # Check that the index has the original blob SHA
+        index = self.repo.open_index()
+        self.assertEqual(blob.id, index[b"test.txt"].sha)
+
+    def test_update_working_tree_without_blob_normalizer(self):
+        """Test update_working_tree without a blob normalizer."""
+        from dulwich.index import update_working_tree
+        from dulwich.objects import Blob, Tree
+
+        # Create a tree with a file containing CRLF
+        blob = Blob()
+        blob.data = b"Hello\r\nWorld\r\n"
+        self.repo.object_store.add_object(blob)
+
+        tree = Tree()
+        tree[b"test.txt"] = (0o100644, blob.id)
+        self.repo.object_store.add_object(tree)
+
+        # Update working tree without normalizer
+        update_working_tree(
+            self.repo,
+            None,  # old_tree_id
+            tree.id,  # new_tree_id
+            blob_normalizer=None,
+        )
+
+        # Check that the file was written with original CRLF line endings
+        test_file = os.path.join(self.tempdir, "test.txt")
+        with open(test_file, "rb") as f:
+            content = f.read()
+
+        self.assertEqual(b"Hello\r\nWorld\r\n", content)
+
+        # Check that the index has the blob SHA
+        index = self.repo.open_index()
+        self.assertEqual(blob.id, index[b"test.txt"].sha)