Ver código fonte

Merge branch 'fix-line-ending-convert-new-files' of git://github.com/pmrowla/dulwich

Jelmer Vernooij 4 anos atrás
pai
commit
7fc181155e
3 arquivos alterados com 73 adições e 3 exclusões
  1. 32 0
      dulwich/line_ending.py
  2. 12 2
      dulwich/repo.py
  3. 29 1
      dulwich/tests/test_porcelain.py

+ 32 - 0
dulwich/line_ending.py

@@ -31,6 +31,16 @@ The normalization is a two-fold process that happens at two moments:
   when doing a `git add` call. We call this process the write filter in this
   module.
 
+Note that when checking status (getting unstaged changes), whether or not
+normalization is done on write depends on whether or not the file in the
+working dir has also been normalized on read:
+
+- For autocrlf=true all files are always normalized on both read and write.
+- For autocrlf=input files are only normalized on write if they are newly
+  "added". Since files which are already committed are not normalized on
+  checkout into the working tree, they are also left alone when staging
+  modifications into the index.
+
 One thing to know is that Git does line-ending normalization only on text
 files. How does Git know that a file is text? We can either mark a file as a
 text file, a binary file or ask Git to automatically decides. Git has an
@@ -272,3 +282,25 @@ def normalize_blob(blob, conversion, binary_detection):
     new_blob.data = converted_data
 
     return new_blob
+
+
+class TreeBlobNormalizer(BlobNormalizer):
+    def __init__(self, config_stack, git_attributes, object_store, tree=None):
+        super().__init__(config_stack, git_attributes)
+        if tree:
+            self.existing_paths = {
+                name
+                for name, _, _ in object_store.iter_tree_contents(tree)
+            }
+        else:
+            self.existing_paths = set()
+
+    def checkin_normalize(self, blob, tree_path):
+        # Existing files should only be normalized on checkin if it was
+        # previously normalized on checkout
+        if (
+            self.fallback_read_filter is not None
+            or tree_path not in self.existing_paths
+        ):
+            return super().checkin_normalize(blob, tree_path)
+        return blob

+ 12 - 2
dulwich/repo.py

@@ -83,7 +83,7 @@ from dulwich.hooks import (
     PostReceiveShellHook,
 )
 
-from dulwich.line_ending import BlobNormalizer
+from dulwich.line_ending import BlobNormalizer, TreeBlobNormalizer
 
 from dulwich.refs import (  # noqa: F401
     ANNOTATED_TAG_SUFFIX,
@@ -1532,7 +1532,17 @@ class Repo(BaseRepo):
         """Return a BlobNormalizer object"""
         # TODO Parse the git attributes files
         git_attributes = {}
-        return BlobNormalizer(self.get_config_stack(), git_attributes)
+        config_stack = self.get_config_stack()
+        try:
+            tree = self.object_store[self.refs[b"HEAD"]].tree
+            return TreeBlobNormalizer(
+                config_stack,
+                git_attributes,
+                self.object_store,
+                tree,
+            )
+        except KeyError:
+            return BlobNormalizer(config_stack, git_attributes)
 
 
 class MemoryRepo(BaseRepo):

+ 29 - 1
dulwich/tests/test_porcelain.py

@@ -1825,7 +1825,7 @@ class StatusTests(PorcelainTestCase):
         self.assertListEqual(results.unstaged, [b"crlf"])
         self.assertListEqual(results.untracked, [])
 
-    def test_status_crlf_convert(self):
+    def test_status_autocrlf_true(self):
         # First make a commit as if the file has been added on a Linux system
         # or with core.autocrlf=True
         file_path = os.path.join(self.repo.path, "crlf")
@@ -1854,6 +1854,34 @@ class StatusTests(PorcelainTestCase):
         self.assertListEqual(results.unstaged, [])
         self.assertListEqual(results.untracked, [])
 
+    def test_status_autocrlf_input(self):
+        # Commit existing file with CRLF
+        file_path = os.path.join(self.repo.path, "crlf-exists")
+        with open(file_path, "wb") as f:
+            f.write(b"line1\r\nline2")
+        porcelain.add(repo=self.repo.path, paths=[file_path])
+        porcelain.commit(
+            repo=self.repo.path,
+            message=b"test status",
+            author=b"author <email>",
+            committer=b"committer <email>",
+        )
+
+        c = self.repo.get_config()
+        c.set("core", "autocrlf", "input")
+        c.write_to_path()
+
+        # Add new (untracked) file
+        file_path = os.path.join(self.repo.path, "crlf-new")
+        with open(file_path, "wb") as f:
+            f.write(b"line1\r\nline2")
+        porcelain.add(repo=self.repo.path, paths=[file_path])
+
+        results = porcelain.status(self.repo)
+        self.assertDictEqual({"add": [b"crlf-new"], "delete": [], "modify": []}, results.staged)
+        self.assertListEqual(results.unstaged, [])
+        self.assertListEqual(results.untracked, [])
+
     def test_get_tree_changes_add(self):
         """Unit test for get_tree_changes add."""