소스 검색

Add support for -a to commit command

Jelmer Vernooij 1 개월 전
부모
커밋
1bef862504
5개의 변경된 파일196개의 추가작업 그리고 2개의 파일을 삭제
  1. 3 0
      NEWS
  2. 7 1
      dulwich/cli.py
  3. 22 1
      dulwich/porcelain.py
  4. 86 0
      tests/test_cli.py
  5. 78 0
      tests/test_porcelain.py

+ 3 - 0
NEWS

@@ -1,5 +1,8 @@
 0.23.3	UNRELEASED
 
+ * Add support for ``-a`` argument to
+   ``dulwich.cli.commit``. (Jelmer Vernooij)
+
  * Add support for merge drivers.
    (Jelmer Vernooij)
 

+ 7 - 1
dulwich/cli.py

@@ -463,8 +463,14 @@ class cmd_commit(Command):
     def run(self, args) -> None:
         parser = argparse.ArgumentParser()
         parser.add_argument("--message", "-m", required=True, help="Commit message")
+        parser.add_argument(
+            "-a",
+            "--all",
+            action="store_true",
+            help="Automatically stage all tracked files that have been modified",
+        )
         args = parser.parse_args(args)
-        porcelain.commit(".", message=args.message)
+        porcelain.commit(".", message=args.message, all=args.all)
 
 
 class cmd_commit_tree(Command):

+ 22 - 1
dulwich/porcelain.py

@@ -474,6 +474,7 @@ def commit(
     encoding=None,
     no_verify=False,
     signoff=False,
+    all=False,
 ):
     """Create a new commit.
 
@@ -488,9 +489,9 @@ def commit(
       signoff: GPG Sign the commit (bool, defaults to False,
         pass True to use default GPG key,
         pass a str containing Key ID to use a specific GPG key)
+      all: Automatically stage all tracked files that have been modified
     Returns: SHA1 of the new commit
     """
-    # FIXME: Support --all argument
     if getattr(message, "encode", None):
         message = message.encode(encoding or DEFAULT_ENCODING)
     if getattr(author, "encode", None):
@@ -502,7 +503,27 @@ def commit(
         author_timezone = local_timezone[0]
     if commit_timezone is None:
         commit_timezone = local_timezone[1]
+
     with open_repo_closing(repo) as r:
+        # If -a flag is used, stage all modified tracked files
+        if all:
+            index = r.open_index()
+            normalizer = r.get_blob_normalizer()
+            filter_callback = normalizer.checkin_normalize
+            unstaged_changes = list(
+                get_unstaged_changes(index, r.path, filter_callback)
+            )
+
+            if unstaged_changes:
+                # Convert bytes paths to strings for add function
+                modified_files = []
+                for path in unstaged_changes:
+                    if isinstance(path, bytes):
+                        path = path.decode()
+                    modified_files.append(path)
+
+                add(r, paths=modified_files)
+
         return r.do_commit(
             message=message,
             author=author,

+ 86 - 0
tests/test_cli.py

@@ -179,6 +179,92 @@ class CommitCommandTest(DulwichCliTestCase):
         # Check that HEAD points to a commit
         self.assertIsNotNone(self.repo.head())
 
+    def test_commit_all_flag(self):
+        # Create initial commit
+        test_file = os.path.join(self.repo_path, "test.txt")
+        with open(test_file, "w") as f:
+            f.write("initial content")
+        self._run_cli("add", "test.txt")
+        self._run_cli("commit", "--message=Initial commit")
+
+        # Modify the file (don't stage it)
+        with open(test_file, "w") as f:
+            f.write("modified content")
+
+        # Create another file and don't add it (untracked)
+        untracked_file = os.path.join(self.repo_path, "untracked.txt")
+        with open(untracked_file, "w") as f:
+            f.write("untracked content")
+
+        # Commit with -a flag should stage and commit the modified file,
+        # but not the untracked file
+        result, stdout, stderr = self._run_cli(
+            "commit", "-a", "--message=Modified commit"
+        )
+        self.assertIsNotNone(self.repo.head())
+
+        # Check that the modification was committed
+        with open(test_file) as f:
+            content = f.read()
+        self.assertEqual(content, "modified content")
+
+        # Check that untracked file is still untracked
+        self.assertTrue(os.path.exists(untracked_file))
+
+    def test_commit_all_flag_no_changes(self):
+        # Create initial commit
+        test_file = os.path.join(self.repo_path, "test.txt")
+        with open(test_file, "w") as f:
+            f.write("initial content")
+        self._run_cli("add", "test.txt")
+        self._run_cli("commit", "--message=Initial commit")
+
+        # Try to commit with -a when there are no changes
+        # This should still work (git allows this)
+        result, stdout, stderr = self._run_cli(
+            "commit", "-a", "--message=No changes commit"
+        )
+        self.assertIsNotNone(self.repo.head())
+
+    def test_commit_all_flag_multiple_files(self):
+        # Create initial commit with multiple files
+        file1 = os.path.join(self.repo_path, "file1.txt")
+        file2 = os.path.join(self.repo_path, "file2.txt")
+
+        with open(file1, "w") as f:
+            f.write("content1")
+        with open(file2, "w") as f:
+            f.write("content2")
+
+        self._run_cli("add", "file1.txt", "file2.txt")
+        self._run_cli("commit", "--message=Initial commit")
+
+        # Modify both files
+        with open(file1, "w") as f:
+            f.write("modified content1")
+        with open(file2, "w") as f:
+            f.write("modified content2")
+
+        # Create an untracked file
+        untracked_file = os.path.join(self.repo_path, "untracked.txt")
+        with open(untracked_file, "w") as f:
+            f.write("untracked content")
+
+        # Commit with -a should stage both modified files but not untracked
+        result, stdout, stderr = self._run_cli(
+            "commit", "-a", "--message=Modified both files"
+        )
+        self.assertIsNotNone(self.repo.head())
+
+        # Verify modifications were committed
+        with open(file1) as f:
+            self.assertEqual(f.read(), "modified content1")
+        with open(file2) as f:
+            self.assertEqual(f.read(), "modified content2")
+
+        # Verify untracked file still exists
+        self.assertTrue(os.path.exists(untracked_file))
+
 
 class LogCommandTest(DulwichCliTestCase):
     """Tests for log command."""

+ 78 - 0
tests/test_porcelain.py

@@ -462,6 +462,84 @@ class CommitTests(PorcelainTestCase):
         self.assertEqual(commit._author_timezone, local_timezone)
         self.assertEqual(commit._commit_timezone, local_timezone)
 
+    def test_commit_all(self) -> None:
+        # Create initial commit
+        filename = os.path.join(self.repo.path, "test.txt")
+        with open(filename, "wb") as f:
+            f.write(b"initial content")
+        porcelain.add(self.repo.path, paths=["test.txt"])
+        initial_sha = porcelain.commit(self.repo.path, message=b"Initial commit")
+
+        # Modify the file without staging
+        with open(filename, "wb") as f:
+            f.write(b"modified content")
+
+        # Create an untracked file
+        untracked_file = os.path.join(self.repo.path, "untracked.txt")
+        with open(untracked_file, "wb") as f:
+            f.write(b"untracked content")
+
+        # Commit with all=True should stage modified files but not untracked
+        sha = porcelain.commit(self.repo.path, message=b"Modified commit", all=True)
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+        self.assertNotEqual(sha, initial_sha)
+
+        # Verify the commit contains the modification
+        commit = self.repo.get_object(sha)
+        assert isinstance(commit, Commit)
+        tree = self.repo.get_object(commit.tree)
+        # The modified file should be in the commit
+        self.assertIn(b"test.txt", tree)
+        # The untracked file should not be in the commit
+        self.assertNotIn(b"untracked.txt", tree)
+
+    def test_commit_all_no_changes(self) -> None:
+        # Create initial commit
+        filename = os.path.join(self.repo.path, "test.txt")
+        with open(filename, "wb") as f:
+            f.write(b"initial content")
+        porcelain.add(self.repo.path, paths=["test.txt"])
+        initial_sha = porcelain.commit(self.repo.path, message=b"Initial commit")
+
+        # Try to commit with all=True when there are no unstaged changes
+        sha = porcelain.commit(self.repo.path, message=b"No changes commit", all=True)
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+        self.assertNotEqual(sha, initial_sha)
+
+    def test_commit_all_multiple_files(self) -> None:
+        # Create initial commit with multiple files
+        file1 = os.path.join(self.repo.path, "file1.txt")
+        file2 = os.path.join(self.repo.path, "file2.txt")
+
+        with open(file1, "wb") as f:
+            f.write(b"content1")
+        with open(file2, "wb") as f:
+            f.write(b"content2")
+
+        porcelain.add(self.repo.path, paths=["file1.txt", "file2.txt"])
+        initial_sha = porcelain.commit(self.repo.path, message=b"Initial commit")
+
+        # Modify both files
+        with open(file1, "wb") as f:
+            f.write(b"modified content1")
+        with open(file2, "wb") as f:
+            f.write(b"modified content2")
+
+        # Commit with all=True should stage both modified files
+        sha = porcelain.commit(self.repo.path, message=b"Modified both files", all=True)
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+        self.assertNotEqual(sha, initial_sha)
+
+        # Verify both modifications are in the commit
+        commit = self.repo.get_object(sha)
+        assert isinstance(commit, Commit)
+        tree = self.repo.get_object(commit.tree)
+        self.assertIn(b"file1.txt", tree)
+        self.assertIn(b"file2.txt", tree)
+
 
 @skipIf(
     platform.python_implementation() == "PyPy" or sys.platform == "win32",