From b22c9b8542cda2a5e45df0a919039d484378d2b9 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:32:15 +0300 Subject: [PATCH] feat: implement Co-authored-by attribution option --- aider/repo.py | 74 ++++++++++++++++++++-------------------- tests/basic/test_repo.py | 55 +++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/aider/repo.py b/aider/repo.py index 999a07269..efa0fda8c 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -125,7 +125,41 @@ class GitRepo: commit_message = self.get_commit_message(diffs, context) commit_message_trailer = "" + attribute_author = self.attribute_author + attribute_committer = self.attribute_committer + attribute_commit_message_author = self.attribute_commit_message_author + attribute_commit_message_committer = self.attribute_commit_message_committer + attribute_co_authored_by = False # Default if coder or args not available + + if coder and hasattr(coder, "args"): + attribute_author = coder.args.attribute_author + attribute_committer = coder.args.attribute_committer + attribute_commit_message_author = coder.args.attribute_commit_message_author + attribute_commit_message_committer = coder.args.attribute_commit_message_committer + attribute_co_authored_by = coder.args.attribute_co_authored_by + if aider_edits: + # Add Co-authored-by trailer if configured + if attribute_co_authored_by: + model_name = "unknown-model" + if coder and hasattr(coder, "main_model") and coder.main_model.name: + model_name = coder.main_model.name + commit_message_trailer = ( + f"\n\nCo-authored-by: aider ({model_name}) " + ) + + # Prefix commit message if configured + if attribute_commit_message_author or attribute_commit_message_committer: + commit_message = "aider: " + commit_message + + # Prepare author/committer modification flags (used later) + use_attribute_author = attribute_author + use_attribute_committer = attribute_committer + else: + # Don't modify author/committer/message for non-aider edits + use_attribute_author = False + use_attribute_committer = self.attribute_committer # Keep committer modification for non-aider edits if configured + # Use coder.args if available, otherwise default to config/defaults attribute_author = ( coder.args.attribute_author @@ -143,29 +177,6 @@ class GitRepo: else self.attribute_commit_message_author ) attribute_commit_message_committer = ( - coder.args.attribute_commit_message_committer - if coder and hasattr(coder, "args") - else self.attribute_commit_message_committer - ) - attribute_co_authored_by = ( - coder.args.attribute_co_authored_by - if coder and hasattr(coder, "args") - else False # Default to False if not found - ) - - # Add Co-authored-by trailer if configured - if attribute_co_authored_by: - model_name = "unknown-model" - if coder and hasattr(coder, "main_model") and coder.main_model.name: - model_name = coder.main_model.name - commit_message_trailer = ( - f"\n\nCo-authored-by: aider ({model_name}) " - ) - - # Prefix commit message if configured - if attribute_commit_message_author or attribute_commit_message_committer: - commit_message = "aider: " + commit_message - if not commit_message: commit_message = "(no commit message provided)" @@ -192,22 +203,11 @@ class GitRepo: original_author_name_env = os.environ.get("GIT_AUTHOR_NAME") committer_name = f"{original_user_name} (aider)" - # Use coder.args if available, otherwise default to config/defaults - use_attribute_committer = ( - coder.args.attribute_committer - if coder and hasattr(coder, "args") - else self.attribute_committer - ) - use_attribute_author = ( - coder.args.attribute_author - if coder and hasattr(coder, "args") - else self.attribute_author - ) - + # Apply author/committer modifications based on flags determined earlier if use_attribute_committer: os.environ["GIT_COMMITTER_NAME"] = committer_name - if aider_edits and use_attribute_author: + if use_attribute_author: # Already checks for aider_edits implicitly os.environ["GIT_AUTHOR_NAME"] = committer_name try: @@ -225,7 +225,7 @@ class GitRepo: elif "GIT_COMMITTER_NAME" in os.environ: del os.environ["GIT_COMMITTER_NAME"] - if aider_edits and use_attribute_author: + if use_attribute_author: # Already checks for aider_edits implicitly if original_author_name_env is not None: os.environ["GIT_AUTHOR_NAME"] = original_author_name_env elif "GIT_AUTHOR_NAME" in os.environ: diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 631e6f606..945de84f8 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -229,10 +229,11 @@ class TestRepo(unittest.TestCase): raw_repo.git.commit("-m", "initial commit") # Mock coder args + # Mock coder args: Co-authored-by enabled, author/committer modification disabled mock_coder = MagicMock() mock_coder.args.attribute_co_authored_by = True - mock_coder.args.attribute_author = None # Explicitly None to test override - mock_coder.args.attribute_committer = None # Explicitly None to test override + mock_coder.args.attribute_author = False # Explicitly disable name modification + mock_coder.args.attribute_committer = False # Explicitly disable name modification mock_coder.args.attribute_commit_message_author = False mock_coder.args.attribute_commit_message_committer = False # Set the model name correctly on the nested mock @@ -255,6 +256,51 @@ class TestRepo(unittest.TestCase): self.assertEqual(commit.author.name, "Test User") # Should NOT be modified self.assertEqual(commit.committer.name, "Test User") # Should NOT be modified + def test_commit_with_co_authored_by_and_name_modification(self): + # Test scenario where Co-authored-by is true AND author/committer modification is also true (default) + # Cleanup of the git temp dir explodes on windows + if platform.system() == "Windows": + return + + with GitTemporaryDirectory(): + # new repo + raw_repo = git.Repo() + raw_repo.config_writer().set_value("user", "name", "Test User").release() + raw_repo.config_writer().set_value("user", "email", "test@example.com").release() + + # add a file and commit it + fname = Path("file.txt") + fname.touch() + raw_repo.git.add(str(fname)) + raw_repo.git.commit("-m", "initial commit") + + # Mock coder args: Co-authored-by enabled, author/committer modification enabled (default) + mock_coder = MagicMock() + mock_coder.args.attribute_co_authored_by = True + mock_coder.args.attribute_author = True # Explicitly enable (or rely on default) + mock_coder.args.attribute_committer = True # Explicitly enable (or rely on default) + mock_coder.args.attribute_commit_message_author = False + mock_coder.args.attribute_commit_message_committer = False + # Set the model name correctly on the nested mock + mock_coder.main_model = MagicMock() + mock_coder.main_model.name = "gpt-test-combo" + + + io = InputOutput() + git_repo = GitRepo(io, None, None) + + # commit a change with aider_edits=True and combo flags + fname.write_text("new content combo") + git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider combo edit") + + # check the commit message and author/committer + commit = raw_repo.head.commit + self.assertIn("Co-authored-by: aider (gpt-test-combo) ", commit.message) + self.assertEqual(commit.message.splitlines()[0], "Aider combo edit") + self.assertEqual(commit.author.name, "Test User (aider)") # Should BE modified + self.assertEqual(commit.committer.name, "Test User (aider)") # Should BE modified + + def test_commit_without_co_authored_by(self): # Cleanup of the git temp dir explodes on windows if platform.system() == "Windows": @@ -279,7 +325,10 @@ class TestRepo(unittest.TestCase): mock_coder.args.attribute_committer = True mock_coder.args.attribute_commit_message_author = False mock_coder.args.attribute_commit_message_committer = False - mock_coder.model.name = "gpt-test" + # Set the model name correctly on the nested mock (though not used in this test assertion) + mock_coder.main_model = MagicMock() + mock_coder.main_model.name = "gpt-test-no-coauthor" + io = InputOutput() git_repo = GitRepo(io, None, None)