From 67bb4f95524d1d4d47cf55f17be2189626784569 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 16:52:45 +0300 Subject: [PATCH 01/44] feat: add co-authored-by commit attribution --- aider/args.py | 6 +++ aider/coders/base_coder.py | 4 +- aider/repo.py | 76 +++++++++++++++++++++++++++++++------- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/aider/args.py b/aider/args.py index 6df19778b..4f91a9cbd 100644 --- a/aider/args.py +++ b/aider/args.py @@ -448,6 +448,12 @@ def get_parser(default_config_files, git_root): default=False, help="Prefix all commit messages with 'aider: ' (default: False)", ) + group.add_argument( + "--attribute-co-authored-by", + action=argparse.BooleanOptionalAction, + default=False, + help="Attribute aider edits using the Co-authored-by trailer in the commit message (default: False).", + ) group.add_argument( "--git-commit-verify", action=argparse.BooleanOptionalAction, diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 19d375afb..5c64475eb 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -2248,7 +2248,7 @@ class Coder: context = self.get_context_from_history(self.cur_messages) try: - res = self.repo.commit(fnames=edited, context=context, aider_edits=True) + res = self.repo.commit(fnames=edited, context=context, aider_edits=True, coder=self) if res: self.show_auto_commit_outcome(res) commit_hash, commit_message = res @@ -2284,7 +2284,7 @@ class Coder: if not self.repo: return - self.repo.commit(fnames=self.need_commit_before_edits) + self.repo.commit(fnames=self.need_commit_before_edits, coder=self) # files changed, move cur messages back behind the files messages # self.move_back_cur_messages(self.gpt_prompts.files_content_local_edits) diff --git a/aider/repo.py b/aider/repo.py index 5ece5147c..999a07269 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -111,7 +111,7 @@ class GitRepo: if aider_ignore_file: self.aider_ignore_file = Path(aider_ignore_file) - def commit(self, fnames=None, context=None, message=None, aider_edits=False): + def commit(self, fnames=None, context=None, message=None, aider_edits=False, coder=None): if not fnames and not self.repo.is_dirty(): return @@ -124,15 +124,52 @@ class GitRepo: else: commit_message = self.get_commit_message(diffs, context) - if aider_edits and self.attribute_commit_message_author: - commit_message = "aider: " + commit_message - elif self.attribute_commit_message_committer: - commit_message = "aider: " + commit_message + commit_message_trailer = "" + if aider_edits: + # Use coder.args if available, otherwise default to config/defaults + attribute_author = ( + coder.args.attribute_author + if coder and hasattr(coder, "args") + else self.attribute_author + ) + attribute_committer = ( + coder.args.attribute_committer + if coder and hasattr(coder, "args") + else self.attribute_committer + ) + attribute_commit_message_author = ( + coder.args.attribute_commit_message_author + if coder and hasattr(coder, "args") + 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)" - full_commit_message = commit_message + full_commit_message = commit_message + commit_message_trailer # if context: # full_commit_message += "\n\n# Aider chat conversation:\n\n" + context @@ -152,13 +189,25 @@ class GitRepo: original_user_name = self.repo.git.config("--get", "user.name") original_committer_name_env = os.environ.get("GIT_COMMITTER_NAME") + original_author_name_env = os.environ.get("GIT_AUTHOR_NAME") committer_name = f"{original_user_name} (aider)" - if self.attribute_committer: + # 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 + ) + + if use_attribute_committer: os.environ["GIT_COMMITTER_NAME"] = committer_name - if aider_edits and self.attribute_author: - original_author_name_env = os.environ.get("GIT_AUTHOR_NAME") + if aider_edits and use_attribute_author: os.environ["GIT_AUTHOR_NAME"] = committer_name try: @@ -170,17 +219,16 @@ class GitRepo: self.io.tool_error(f"Unable to commit: {err}") finally: # Restore the env - - if self.attribute_committer: + if use_attribute_committer: if original_committer_name_env is not None: os.environ["GIT_COMMITTER_NAME"] = original_committer_name_env - else: + elif "GIT_COMMITTER_NAME" in os.environ: del os.environ["GIT_COMMITTER_NAME"] - if aider_edits and self.attribute_author: + if aider_edits and use_attribute_author: if original_author_name_env is not None: os.environ["GIT_AUTHOR_NAME"] = original_author_name_env - else: + elif "GIT_AUTHOR_NAME" in os.environ: del os.environ["GIT_AUTHOR_NAME"] def get_rel_repo_dir(self): From b6b8f30378cae0cab844989142eec5efb39f6554 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 16:57:21 +0300 Subject: [PATCH 02/44] test: add tests for co-authored-by commit attribution --- tests/basic/test_repo.py | 83 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index aa863c570..7e288e063 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -4,7 +4,7 @@ import tempfile import time import unittest from pathlib import Path -from unittest.mock import patch +from unittest.mock import MagicMock, patch import git @@ -211,6 +211,87 @@ class TestRepo(unittest.TestCase): original_author_name = os.environ.get("GIT_AUTHOR_NAME") self.assertIsNone(original_author_name) + def test_commit_with_co_authored_by(self): + # 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 + 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_commit_message_author = False + mock_coder.args.attribute_commit_message_committer = False + mock_coder.model.name = "gpt-test" + + io = InputOutput() + git_repo = GitRepo(io, None, None) + + # commit a change with aider_edits=True and co-authored-by flag + fname.write_text("new content") + git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") + + # check the commit message and author/committer + commit = raw_repo.head.commit + self.assertIn("Co-authored-by: aider (gpt-test) ", commit.message) + self.assertEqual(commit.message.splitlines()[0], "Aider edit") + 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_without_co_authored_by(self): + # 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 (default behavior) + mock_coder = MagicMock() + mock_coder.args.attribute_co_authored_by = False + mock_coder.args.attribute_author = True + 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" + + io = InputOutput() + git_repo = GitRepo(io, None, None) + + # commit a change with aider_edits=True and default flags + fname.write_text("new content") + git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") + + # check the commit message and author/committer + commit = raw_repo.head.commit + self.assertNotIn("Co-authored-by:", commit.message) + self.assertEqual(commit.message.splitlines()[0], "Aider 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_get_tracked_files(self): # Create a temporary directory tempdir = Path(tempfile.mkdtemp()) From eb28e228913fdc0c90b62168bb4b57475a91e22c Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:07:41 +0300 Subject: [PATCH 03/44] test: fix mock setup for model name in co-authored-by test --- tests/basic/test_repo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 7e288e063..4ff694714 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -235,7 +235,9 @@ class TestRepo(unittest.TestCase): mock_coder.args.attribute_committer = None # Explicitly None to test override 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 + mock_coder.model = MagicMock(name="gpt-test") + io = InputOutput() git_repo = GitRepo(io, None, None) From 192f8bec26ca0348cf43b1ec7bf00ac7e459f565 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:09:12 +0300 Subject: [PATCH 04/44] test: fix mock model name setup in co-authored-by test --- tests/basic/test_repo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 4ff694714..d0441a4da 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -236,7 +236,8 @@ class TestRepo(unittest.TestCase): 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.model = MagicMock(name="gpt-test") + mock_coder.model = MagicMock() + mock_coder.model.configure_mock(name="gpt-test") io = InputOutput() From a5327af5e9ef31b68a4345a6679b66b72a9c09fc Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:12:30 +0300 Subject: [PATCH 05/44] test: fix mock setup for co-authored-by commit test --- tests/basic/test_repo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index d0441a4da..631e6f606 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -236,8 +236,9 @@ class TestRepo(unittest.TestCase): 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.model = MagicMock() - mock_coder.model.configure_mock(name="gpt-test") + # The code uses coder.main_model.name for the co-authored-by line + mock_coder.main_model = MagicMock() + mock_coder.main_model.name = "gpt-test" io = InputOutput() From b22c9b8542cda2a5e45df0a919039d484378d2b9 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:32:15 +0300 Subject: [PATCH 06/44] 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) From c73b987cd099cc73e1be17d3084df444b1e70491 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:32:57 +0300 Subject: [PATCH 07/44] fix: fix syntax error in commit logic --- aider/repo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aider/repo.py b/aider/repo.py index efa0fda8c..5ad84d36d 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -177,6 +177,10 @@ 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 + ) if not commit_message: commit_message = "(no commit message provided)" From e9511643998d4868bc261b3ec0479e6a2a362b3a Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:35:45 +0300 Subject: [PATCH 08/44] chore: Add test comment to dump function --- aider/dump.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/dump.py b/aider/dump.py index 2c8bf31c2..6098d0b73 100644 --- a/aider/dump.py +++ b/aider/dump.py @@ -12,6 +12,7 @@ def cvt(s): def dump(*vals): + # This is a test comment # http://docs.python.org/library/traceback.html stack = traceback.extract_stack() vars = stack[-2][3] From 482e0c2d0b5cdfe60281f13bab2ead4aa540e9e6 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:37:00 +0300 Subject: [PATCH 09/44] chore: Add test comment --- aider/prompts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/prompts.py b/aider/prompts.py index 84ed75e9b..1dacf10d0 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -1,5 +1,6 @@ # flake8: noqa: E501 +# This is a test comment. # COMMIT From 4783ad3a73530c174f664505b65fe2e587ef27aa Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:39:49 +0300 Subject: [PATCH 10/44] feat: add attribute-co-authored-by option for commit attribution --- aider/args.py | 2 +- aider/repo.py | 83 +++++++++++++++++++++++---------------------------- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/aider/args.py b/aider/args.py index 4f91a9cbd..3571faa5e 100644 --- a/aider/args.py +++ b/aider/args.py @@ -428,7 +428,7 @@ def get_parser(default_config_files, git_root): "--attribute-author", action=argparse.BooleanOptionalAction, default=True, - help="Attribute aider code changes in the git author name (default: True)", + help="Attribute aider code changes in the git author name (default: True, ignored if attribute-co-authored-by is True unless explicitly set)", ) group.add_argument( "--attribute-committer", diff --git a/aider/repo.py b/aider/repo.py index 5ad84d36d..e0bcef2de 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -69,6 +69,8 @@ class GitRepo: self.attribute_committer = attribute_committer self.attribute_commit_message_author = attribute_commit_message_author self.attribute_commit_message_committer = attribute_commit_message_committer + # Ensure attribute_co_authored_by is initialized, default to False if not provided + self.attribute_co_authored_by = getattr(self, 'attribute_co_authored_by', False) self.commit_prompt = commit_prompt self.subtree_only = subtree_only self.git_commit_verify = git_commit_verify @@ -124,66 +126,56 @@ class GitRepo: else: 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 - + # Retrieve attribute settings, prioritizing coder.args if 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 + else: + # Fallback to self attributes (initialized from config/defaults) + 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 = getattr(self, 'attribute_co_authored_by', False) + + commit_message_trailer = "" + prefix_commit_message = False + use_attribute_author = False + use_attribute_committer = False if aider_edits: - # Add Co-authored-by trailer if configured + # Determine commit message prefixing + if attribute_commit_message_author or attribute_commit_message_committer: + prefix_commit_message = True + + # Determine author/committer modification and trailer 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 + commit_message_trailer = f"\n\nCo-authored-by: aider ({model_name}) " + # Only modify author/committer if explicitly requested alongside co-authored-by + use_attribute_author = attribute_author + use_attribute_committer = attribute_committer + else: + # Original behavior when co-authored-by is false + use_attribute_author = attribute_author + use_attribute_committer = attribute_committer + else: # not aider_edits + # Keep original behavior for non-aider edits use_attribute_author = False - use_attribute_committer = self.attribute_committer # Keep committer modification for non-aider edits if configured + use_attribute_committer = attribute_committer # Respect config for committer + prefix_commit_message = False # Don't prefix non-aider commits - # Use coder.args if available, otherwise default to config/defaults - attribute_author = ( - coder.args.attribute_author - if coder and hasattr(coder, "args") - else self.attribute_author - ) - attribute_committer = ( - coder.args.attribute_committer - if coder and hasattr(coder, "args") - else self.attribute_committer - ) - attribute_commit_message_author = ( - coder.args.attribute_commit_message_author - if coder and hasattr(coder, "args") - 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 - ) if not commit_message: commit_message = "(no commit message provided)" + if prefix_commit_message: + commit_message = "aider: " + commit_message + full_commit_message = commit_message + commit_message_trailer # if context: # full_commit_message += "\n\n# Aider chat conversation:\n\n" + context @@ -210,8 +202,7 @@ class GitRepo: # Apply author/committer modifications based on flags determined earlier if use_attribute_committer: os.environ["GIT_COMMITTER_NAME"] = committer_name - - if use_attribute_author: # Already checks for aider_edits implicitly + if use_attribute_author: os.environ["GIT_AUTHOR_NAME"] = committer_name try: @@ -229,7 +220,7 @@ class GitRepo: elif "GIT_COMMITTER_NAME" in os.environ: del os.environ["GIT_COMMITTER_NAME"] - if use_attribute_author: # Already checks for aider_edits implicitly + if use_attribute_author: if original_author_name_env is not None: os.environ["GIT_AUTHOR_NAME"] = original_author_name_env elif "GIT_AUTHOR_NAME" in os.environ: From 43cb4d68f7b6a31beddf130b9b09c5c448221005 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:45:48 +0300 Subject: [PATCH 11/44] test: Temporarily disable co-author attribution to verify test failure --- aider/repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/repo.py b/aider/repo.py index e0bcef2de..aa6cffb4f 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -132,7 +132,7 @@ class GitRepo: 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 + # attribute_co_authored_by = coder.args.attribute_co_authored_by # <-- Intentionally commented out for testing else: # Fallback to self attributes (initialized from config/defaults) attribute_author = self.attribute_author From dede701423342f2dd40d1493c21874e92111a4ba Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:47:20 +0300 Subject: [PATCH 12/44] test: intentionally break co-authored-by logic --- aider/repo.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/aider/repo.py b/aider/repo.py index aa6cffb4f..4ba96d920 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -132,7 +132,7 @@ class GitRepo: 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 # <-- Intentionally commented out for testing + attribute_co_authored_by = coder.args.attribute_co_authored_by # <-- Restored else: # Fallback to self attributes (initialized from config/defaults) attribute_author = self.attribute_author @@ -152,7 +152,13 @@ class GitRepo: prefix_commit_message = True # Determine author/committer modification and trailer - if attribute_co_authored_by: + + # --- Intentionally break the behavior for testing --- + original_attribute_co_authored_by = attribute_co_authored_by # Keep original value if needed elsewhere + attribute_co_authored_by = False # Force to False to ignore the setting + # --- End intentional break --- + + if attribute_co_authored_by: # This condition will now always be false model_name = "unknown-model" if coder and hasattr(coder, "main_model") and coder.main_model.name: model_name = coder.main_model.name From 80114e7a24faa004d7131f47364a28f8fae8d694 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:48:52 +0300 Subject: [PATCH 13/44] chore: revert intentional break introduced for testing --- aider/repo.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/aider/repo.py b/aider/repo.py index 4ba96d920..7e04d89ee 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -153,12 +153,7 @@ class GitRepo: # Determine author/committer modification and trailer - # --- Intentionally break the behavior for testing --- - original_attribute_co_authored_by = attribute_co_authored_by # Keep original value if needed elsewhere - attribute_co_authored_by = False # Force to False to ignore the setting - # --- End intentional break --- - - if attribute_co_authored_by: # This condition will now always be false + 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 From d5671c2879c85f581bd504a962e8b54b6567c776 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:50:16 +0300 Subject: [PATCH 14/44] chore: Add test comment --- tests/fixtures/sample-code-base/sample.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fixtures/sample-code-base/sample.js b/tests/fixtures/sample-code-base/sample.js index f3f2eaf58..a934b428c 100644 --- a/tests/fixtures/sample-code-base/sample.js +++ b/tests/fixtures/sample-code-base/sample.js @@ -1,3 +1,4 @@ +// Aider test commit // Sample JavaScript script with 7 functions // 1. A simple greeting function From 48f89f226fac3d407d40574d921bf5df065cda71 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:51:58 +0300 Subject: [PATCH 15/44] fix: prevent name modification when using co-authored-by --- aider/repo.py | 10 +++++----- tests/basic/test_repo.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/aider/repo.py b/aider/repo.py index 7e04d89ee..f0d436526 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -58,6 +58,7 @@ class GitRepo: commit_prompt=None, subtree_only=False, git_commit_verify=True, + attribute_co_authored_by=False, # Added parameter ): self.io = io self.models = models @@ -69,8 +70,7 @@ class GitRepo: self.attribute_committer = attribute_committer self.attribute_commit_message_author = attribute_commit_message_author self.attribute_commit_message_committer = attribute_commit_message_committer - # Ensure attribute_co_authored_by is initialized, default to False if not provided - self.attribute_co_authored_by = getattr(self, 'attribute_co_authored_by', False) + self.attribute_co_authored_by = attribute_co_authored_by # Assign from parameter self.commit_prompt = commit_prompt self.subtree_only = subtree_only self.git_commit_verify = git_commit_verify @@ -158,9 +158,9 @@ class GitRepo: 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}) " - # Only modify author/committer if explicitly requested alongside co-authored-by - use_attribute_author = attribute_author - use_attribute_committer = attribute_committer + # If co-authored-by is used, disable author/committer name modification + use_attribute_author = False + use_attribute_committer = False else: # Original behavior when co-authored-by is false use_attribute_author = attribute_author diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 945de84f8..2c68c8f00 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -297,8 +297,9 @@ class TestRepo(unittest.TestCase): 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 + # When co-authored-by is true, name modification should be disabled + 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_without_co_authored_by(self): From 072bd30443ce0b3bb8cb7df2f9550446b4edb240 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:53:11 +0300 Subject: [PATCH 16/44] test: add comment for testing --- tests/fixtures/sample-code-base/sample.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fixtures/sample-code-base/sample.js b/tests/fixtures/sample-code-base/sample.js index a934b428c..31280539f 100644 --- a/tests/fixtures/sample-code-base/sample.js +++ b/tests/fixtures/sample-code-base/sample.js @@ -1,4 +1,5 @@ // Aider test commit +// Another test comment // Sample JavaScript script with 7 functions // 1. A simple greeting function From f648a018a2a738f03576acb4e4998e8b2be17341 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:55:13 +0300 Subject: [PATCH 17/44] fix: Pass attribute_co_authored_by arg to GitRepo constructor --- aider/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/main.py b/aider/main.py index 9da90e161..a8535d02c 100644 --- a/aider/main.py +++ b/aider/main.py @@ -904,6 +904,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F commit_prompt=args.commit_prompt, subtree_only=args.subtree_only, git_commit_verify=args.git_commit_verify, + attribute_co_authored_by=args.attribute_co_authored_by, # Pass the arg ) except FileNotFoundError: pass From ff8e9850ba2dd61a5ff1cc6f5b2cb30d0469082e Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:55:54 +0300 Subject: [PATCH 18/44] chore: add test comment to dump function --- aider/dump.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/dump.py b/aider/dump.py index 6098d0b73..85065a97d 100644 --- a/aider/dump.py +++ b/aider/dump.py @@ -13,6 +13,7 @@ def cvt(s): def dump(*vals): # This is a test comment + # This is another test comment # http://docs.python.org/library/traceback.html stack = traceback.extract_stack() vars = stack[-2][3] From d1437b76665a4e4f51d6ea21200a0b817e6157c3 Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:57:04 +0300 Subject: [PATCH 19/44] chore: add debug prints for attribute_co_authored_by --- aider/main.py | 2 ++ aider/repo.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/aider/main.py b/aider/main.py index a8535d02c..61100d14a 100644 --- a/aider/main.py +++ b/aider/main.py @@ -501,6 +501,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F # Parse again to include any arguments that might have been defined in .env args = parser.parse_args(argv) + print(f"DEBUG: After final parse, args.attribute_co_authored_by = {args.attribute_co_authored_by}") # DEBUG if git is None: args.git = False @@ -891,6 +892,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F repo = None if args.git: try: + print(f"DEBUG: Before GitRepo init, args.attribute_co_authored_by = {args.attribute_co_authored_by}") # DEBUG repo = GitRepo( io, fnames, diff --git a/aider/repo.py b/aider/repo.py index f0d436526..5a57ffb46 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -60,6 +60,7 @@ class GitRepo: git_commit_verify=True, attribute_co_authored_by=False, # Added parameter ): + print(f"DEBUG: GitRepo.__init__ received attribute_co_authored_by = {attribute_co_authored_by}") # DEBUG self.io = io self.models = models @@ -71,6 +72,7 @@ class GitRepo: self.attribute_commit_message_author = attribute_commit_message_author self.attribute_commit_message_committer = attribute_commit_message_committer self.attribute_co_authored_by = attribute_co_authored_by # Assign from parameter + print(f"DEBUG: GitRepo.__init__ set self.attribute_co_authored_by = {self.attribute_co_authored_by}") # DEBUG self.commit_prompt = commit_prompt self.subtree_only = subtree_only self.git_commit_verify = git_commit_verify @@ -117,6 +119,8 @@ class GitRepo: if not fnames and not self.repo.is_dirty(): return + print(f"DEBUG: GitRepo.commit start, self.attribute_co_authored_by = {self.attribute_co_authored_by}") # DEBUG + diffs = self.get_diffs(fnames) if not diffs: return @@ -141,6 +145,8 @@ class GitRepo: attribute_commit_message_committer = self.attribute_commit_message_committer attribute_co_authored_by = getattr(self, 'attribute_co_authored_by', False) + print(f"DEBUG: GitRepo.commit after retrieval, attribute_co_authored_by = {attribute_co_authored_by}") # DEBUG + commit_message_trailer = "" prefix_commit_message = False use_attribute_author = False @@ -153,6 +159,7 @@ class GitRepo: # Determine author/committer modification and trailer + print(f"DEBUG: GitRepo.commit before logic check, attribute_co_authored_by = {attribute_co_authored_by}") # DEBUG if attribute_co_authored_by: model_name = "unknown-model" if coder and hasattr(coder, "main_model") and coder.main_model.name: @@ -201,6 +208,7 @@ class GitRepo: committer_name = f"{original_user_name} (aider)" # Apply author/committer modifications based on flags determined earlier + print(f"DEBUG: GitRepo.commit before setting env, use_attribute_author={use_attribute_author}, use_attribute_committer={use_attribute_committer}") # DEBUG if use_attribute_committer: os.environ["GIT_COMMITTER_NAME"] = committer_name if use_attribute_author: From 15d623f2c085478e43ceafda98dffefcc44eec7d Mon Sep 17 00:00:00 2001 From: "Andrew Grigorev (aider)" Date: Sat, 12 Apr 2025 17:57:44 +0300 Subject: [PATCH 20/44] chore: add another test comment to prompts --- aider/prompts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/prompts.py b/aider/prompts.py index 1dacf10d0..87f14a5ec 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -1,6 +1,7 @@ # flake8: noqa: E501 # This is a test comment. +# This is another test comment. # COMMIT From 316d8f8e9bf919fa431cda7250ea33d4f5b230bd Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:06:09 +0300 Subject: [PATCH 21/44] chore: add third test comment Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/prompts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/prompts.py b/aider/prompts.py index 87f14a5ec..50830978a 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -2,6 +2,7 @@ # This is a test comment. # This is another test comment. +# This is a third test comment. # COMMIT From 66fdeceb3b39f1ca25ed9cd7cf2a041b9d1e5f66 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:06 +0300 Subject: [PATCH 22/44] Revert "chore: add third test comment" This reverts commit 316d8f8e9bf919fa431cda7250ea33d4f5b230bd. --- aider/prompts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aider/prompts.py b/aider/prompts.py index 50830978a..87f14a5ec 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -2,7 +2,6 @@ # This is a test comment. # This is another test comment. -# This is a third test comment. # COMMIT From 0a59c38f31ff82137b219013693386efd4a13b96 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:07 +0300 Subject: [PATCH 23/44] Revert "chore: add another test comment to prompts" This reverts commit 15d623f2c085478e43ceafda98dffefcc44eec7d. --- aider/prompts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aider/prompts.py b/aider/prompts.py index 87f14a5ec..1dacf10d0 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -1,7 +1,6 @@ # flake8: noqa: E501 # This is a test comment. -# This is another test comment. # COMMIT From e1820522db7eb006306d6396e09f5f1b94b633f2 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:07 +0300 Subject: [PATCH 24/44] Revert "chore: add debug prints for attribute_co_authored_by" This reverts commit d1437b76665a4e4f51d6ea21200a0b817e6157c3. --- aider/main.py | 2 -- aider/repo.py | 8 -------- 2 files changed, 10 deletions(-) diff --git a/aider/main.py b/aider/main.py index 61100d14a..a8535d02c 100644 --- a/aider/main.py +++ b/aider/main.py @@ -501,7 +501,6 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F # Parse again to include any arguments that might have been defined in .env args = parser.parse_args(argv) - print(f"DEBUG: After final parse, args.attribute_co_authored_by = {args.attribute_co_authored_by}") # DEBUG if git is None: args.git = False @@ -892,7 +891,6 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F repo = None if args.git: try: - print(f"DEBUG: Before GitRepo init, args.attribute_co_authored_by = {args.attribute_co_authored_by}") # DEBUG repo = GitRepo( io, fnames, diff --git a/aider/repo.py b/aider/repo.py index 5a57ffb46..f0d436526 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -60,7 +60,6 @@ class GitRepo: git_commit_verify=True, attribute_co_authored_by=False, # Added parameter ): - print(f"DEBUG: GitRepo.__init__ received attribute_co_authored_by = {attribute_co_authored_by}") # DEBUG self.io = io self.models = models @@ -72,7 +71,6 @@ class GitRepo: self.attribute_commit_message_author = attribute_commit_message_author self.attribute_commit_message_committer = attribute_commit_message_committer self.attribute_co_authored_by = attribute_co_authored_by # Assign from parameter - print(f"DEBUG: GitRepo.__init__ set self.attribute_co_authored_by = {self.attribute_co_authored_by}") # DEBUG self.commit_prompt = commit_prompt self.subtree_only = subtree_only self.git_commit_verify = git_commit_verify @@ -119,8 +117,6 @@ class GitRepo: if not fnames and not self.repo.is_dirty(): return - print(f"DEBUG: GitRepo.commit start, self.attribute_co_authored_by = {self.attribute_co_authored_by}") # DEBUG - diffs = self.get_diffs(fnames) if not diffs: return @@ -145,8 +141,6 @@ class GitRepo: attribute_commit_message_committer = self.attribute_commit_message_committer attribute_co_authored_by = getattr(self, 'attribute_co_authored_by', False) - print(f"DEBUG: GitRepo.commit after retrieval, attribute_co_authored_by = {attribute_co_authored_by}") # DEBUG - commit_message_trailer = "" prefix_commit_message = False use_attribute_author = False @@ -159,7 +153,6 @@ class GitRepo: # Determine author/committer modification and trailer - print(f"DEBUG: GitRepo.commit before logic check, attribute_co_authored_by = {attribute_co_authored_by}") # DEBUG if attribute_co_authored_by: model_name = "unknown-model" if coder and hasattr(coder, "main_model") and coder.main_model.name: @@ -208,7 +201,6 @@ class GitRepo: committer_name = f"{original_user_name} (aider)" # Apply author/committer modifications based on flags determined earlier - print(f"DEBUG: GitRepo.commit before setting env, use_attribute_author={use_attribute_author}, use_attribute_committer={use_attribute_committer}") # DEBUG if use_attribute_committer: os.environ["GIT_COMMITTER_NAME"] = committer_name if use_attribute_author: From 02bc9a85c026ab4dd8aadea73156247f0d5d3131 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:08 +0300 Subject: [PATCH 25/44] Revert "chore: add test comment to dump function" This reverts commit ff8e9850ba2dd61a5ff1cc6f5b2cb30d0469082e. --- aider/dump.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aider/dump.py b/aider/dump.py index 85065a97d..6098d0b73 100644 --- a/aider/dump.py +++ b/aider/dump.py @@ -13,7 +13,6 @@ def cvt(s): def dump(*vals): # This is a test comment - # This is another test comment # http://docs.python.org/library/traceback.html stack = traceback.extract_stack() vars = stack[-2][3] From cf7b35f90d45766f33327d7dbcd31c5a90c130d1 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:08 +0300 Subject: [PATCH 26/44] Revert "test: add comment for testing" This reverts commit 072bd30443ce0b3bb8cb7df2f9550446b4edb240. --- tests/fixtures/sample-code-base/sample.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/fixtures/sample-code-base/sample.js b/tests/fixtures/sample-code-base/sample.js index 31280539f..a934b428c 100644 --- a/tests/fixtures/sample-code-base/sample.js +++ b/tests/fixtures/sample-code-base/sample.js @@ -1,5 +1,4 @@ // Aider test commit -// Another test comment // Sample JavaScript script with 7 functions // 1. A simple greeting function From 7b8c7edfd5a122967ee03eefa0c5077ae1031d90 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:08 +0300 Subject: [PATCH 27/44] Revert "chore: Add test comment" This reverts commit d5671c2879c85f581bd504a962e8b54b6567c776. --- tests/fixtures/sample-code-base/sample.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/fixtures/sample-code-base/sample.js b/tests/fixtures/sample-code-base/sample.js index a934b428c..f3f2eaf58 100644 --- a/tests/fixtures/sample-code-base/sample.js +++ b/tests/fixtures/sample-code-base/sample.js @@ -1,4 +1,3 @@ -// Aider test commit // Sample JavaScript script with 7 functions // 1. A simple greeting function From aa07e16f1833fb78269286f114e2eb2ccb07b2e9 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:09 +0300 Subject: [PATCH 28/44] Revert "chore: Add test comment" This reverts commit 482e0c2d0b5cdfe60281f13bab2ead4aa540e9e6. --- aider/prompts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aider/prompts.py b/aider/prompts.py index 1dacf10d0..84ed75e9b 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -1,6 +1,5 @@ # flake8: noqa: E501 -# This is a test comment. # COMMIT From 427f9c5b00fb3964d71344e86452209df1422616 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:14:09 +0300 Subject: [PATCH 29/44] Revert "chore: Add test comment to dump function" This reverts commit e9511643998d4868bc261b3ec0479e6a2a362b3a. --- aider/dump.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aider/dump.py b/aider/dump.py index 6098d0b73..2c8bf31c2 100644 --- a/aider/dump.py +++ b/aider/dump.py @@ -12,7 +12,6 @@ def cvt(s): def dump(*vals): - # This is a test comment # http://docs.python.org/library/traceback.html stack = traceback.extract_stack() vars = stack[-2][3] From c56e836d22be0139adb3d976ac49ed4db8a45ab8 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:19:55 +0300 Subject: [PATCH 30/44] refactor: simplify commit logic and use context manager for git env Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/args.py | 5 ++- aider/repo.py | 105 +++++++++++++++++++++++++------------------------- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/aider/args.py b/aider/args.py index 3571faa5e..6d62b6dbf 100644 --- a/aider/args.py +++ b/aider/args.py @@ -428,7 +428,10 @@ def get_parser(default_config_files, git_root): "--attribute-author", action=argparse.BooleanOptionalAction, default=True, - help="Attribute aider code changes in the git author name (default: True, ignored if attribute-co-authored-by is True unless explicitly set)", + help=( + "Attribute aider code changes in the git author name (default: True). This is ignored" + " if --attribute-co-authored-by is True." + ), ) group.add_argument( "--attribute-committer", diff --git a/aider/repo.py b/aider/repo.py index f0d436526..f8a5cacfc 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -1,3 +1,4 @@ +import contextlib import os import time from pathlib import Path, PurePosixPath @@ -34,6 +35,19 @@ ANY_GIT_ERROR += [ ANY_GIT_ERROR = tuple(ANY_GIT_ERROR) +@contextlib.contextmanager +def set_git_env(var_name, value, original_value): + """Temporarily set a Git environment variable.""" + os.environ[var_name] = value + try: + yield + finally: + if original_value is not None: + os.environ[var_name] = original_value + elif var_name in os.environ: + del os.environ[var_name] + + class GitRepo: repo = None aider_ignore_file = None @@ -139,37 +153,29 @@ class GitRepo: 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 = getattr(self, 'attribute_co_authored_by', False) + attribute_co_authored_by = getattr(self, "attribute_co_authored_by", False) + # Determine commit message prefixing + prefix_commit_message = aider_edits and ( + attribute_commit_message_author or attribute_commit_message_committer + ) + + # Determine Co-authored-by trailer commit_message_trailer = "" - prefix_commit_message = False - use_attribute_author = False - use_attribute_committer = False + if aider_edits and 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}) " + ) - if aider_edits: - # Determine commit message prefixing - if attribute_commit_message_author or attribute_commit_message_committer: - prefix_commit_message = True - - # Determine author/committer modification and trailer - - 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}) " - # If co-authored-by is used, disable author/committer name modification - use_attribute_author = False - use_attribute_committer = False - else: - # Original behavior when co-authored-by is false - use_attribute_author = attribute_author - use_attribute_committer = attribute_committer - else: # not aider_edits - # Keep original behavior for non-aider edits - use_attribute_author = False - use_attribute_committer = attribute_committer # Respect config for committer - prefix_commit_message = False # Don't prefix non-aider commits + # Determine if author/committer names should be modified + # If co-authored-by is used for aider edits, it takes precedence over direct name modification. + use_attribute_author = attribute_author and aider_edits and not attribute_co_authored_by + use_attribute_committer = attribute_committer and not ( + aider_edits and attribute_co_authored_by + ) if not commit_message: commit_message = "(no commit message provided)" @@ -178,8 +184,6 @@ class GitRepo: commit_message = "aider: " + commit_message full_commit_message = commit_message + commit_message_trailer - # if context: - # full_commit_message += "\n\n# Aider chat conversation:\n\n" + context cmd = ["-m", full_commit_message] if not self.git_commit_verify: @@ -200,32 +204,27 @@ class GitRepo: original_author_name_env = os.environ.get("GIT_AUTHOR_NAME") committer_name = f"{original_user_name} (aider)" - # Apply author/committer modifications based on flags determined earlier - if use_attribute_committer: - os.environ["GIT_COMMITTER_NAME"] = committer_name - if use_attribute_author: - os.environ["GIT_AUTHOR_NAME"] = committer_name - try: - self.repo.git.commit(cmd) - commit_hash = self.get_head_commit_sha(short=True) - self.io.tool_output(f"Commit {commit_hash} {commit_message}", bold=True) - return commit_hash, commit_message + # Use context managers to handle environment variables + with contextlib.ExitStack() as stack: + if use_attribute_committer: + stack.enter_context( + set_git_env("GIT_COMMITTER_NAME", committer_name, original_committer_name_env) + ) + if use_attribute_author: + stack.enter_context( + set_git_env("GIT_AUTHOR_NAME", committer_name, original_author_name_env) + ) + + # Perform the commit + self.repo.git.commit(cmd) + commit_hash = self.get_head_commit_sha(short=True) + self.io.tool_output(f"Commit {commit_hash} {commit_message}", bold=True) + return commit_hash, commit_message + except ANY_GIT_ERROR as err: self.io.tool_error(f"Unable to commit: {err}") - finally: - # Restore the env - if use_attribute_committer: - if original_committer_name_env is not None: - os.environ["GIT_COMMITTER_NAME"] = original_committer_name_env - elif "GIT_COMMITTER_NAME" in os.environ: - del os.environ["GIT_COMMITTER_NAME"] - - if use_attribute_author: - if original_author_name_env is not None: - os.environ["GIT_AUTHOR_NAME"] = original_author_name_env - elif "GIT_AUTHOR_NAME" in os.environ: - del os.environ["GIT_AUTHOR_NAME"] + # No return here, implicitly returns None def get_rel_repo_dir(self): try: From dd4b61da207dfe45cca6e9dee16490b048b3ed8e Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 18:26:46 +0300 Subject: [PATCH 31/44] test: add test for co-authored-by precedence over author/committer Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- tests/basic/test_repo.py | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 2c68c8f00..57bad6625 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -301,6 +301,60 @@ 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_co_authored_by_precedence(self): + # Test that co-authored-by takes precedence over name modification when both are enabled + 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: All relevant flags enabled + 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 + mock_coder.main_model = MagicMock() + mock_coder.main_model.name = "gpt-precedence" + + io = InputOutput() + # Initialize GitRepo directly with flags, simulating config/defaults if coder wasn't passed + # This ensures the test covers the case where coder might not provide args + git_repo = GitRepo( + io, + None, + None, + attribute_co_authored_by=True, + attribute_author=True, + attribute_committer=True, + ) + + + # commit a change with aider_edits=True and conflicting flags + fname.write_text("new content precedence") + # Pass the coder object here to ensure its args are used if available, + # but the GitRepo init already set the fallback values. + git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider precedence edit") + + # check the commit message and author/committer + commit = raw_repo.head.commit + self.assertIn("Co-authored-by: aider (gpt-precedence) ", commit.message) + self.assertEqual(commit.message.splitlines()[0], "Aider precedence edit") + # Co-authored-by should take precedence, names should NOT be modified + self.assertEqual(commit.author.name, "Test User") + self.assertEqual(commit.committer.name, "Test User") + def test_commit_without_co_authored_by(self): # Cleanup of the git temp dir explodes on windows From ea74f31b3e98550611e95e7108a9a63c0dd8d5c5 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:09:46 +0300 Subject: [PATCH 32/44] feat: Explicit author/committer flags override co-authored-by Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/args.py | 13 +++--- aider/repo.py | 28 ++++++++++--- tests/basic/test_repo.py | 87 +++++++++++++++++++++------------------- 3 files changed, 77 insertions(+), 51 deletions(-) diff --git a/aider/args.py b/aider/args.py index 6d62b6dbf..7aaf10f2a 100644 --- a/aider/args.py +++ b/aider/args.py @@ -427,17 +427,20 @@ def get_parser(default_config_files, git_root): group.add_argument( "--attribute-author", action=argparse.BooleanOptionalAction, - default=True, + default=None, help=( - "Attribute aider code changes in the git author name (default: True). This is ignored" - " if --attribute-co-authored-by is True." + "Attribute aider code changes in the git author name (default: True). If explicitly set" + " to True, overrides --attribute-co-authored-by precedence." ), ) group.add_argument( "--attribute-committer", action=argparse.BooleanOptionalAction, - default=True, - help="Attribute aider commits in the git committer name (default: True)", + default=None, + help=( + "Attribute aider commits in the git committer name (default: True). If explicitly set" + " to True, overrides --attribute-co-authored-by precedence for aider edits." + ), ) group.add_argument( "--attribute-commit-message-author", diff --git a/aider/repo.py b/aider/repo.py index f8a5cacfc..31bcc7957 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -153,7 +153,16 @@ class GitRepo: 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 = getattr(self, "attribute_co_authored_by", False) + attribute_co_authored_by = getattr(self, "attribute_co_authored_by", False) # Should be False if not set + + # Determine explicit settings (None means use default behavior) + author_explicit = attribute_author is not None + committer_explicit = attribute_committer is not None + + # Determine effective settings (apply default True if not explicit) + effective_author = True if attribute_author is None else attribute_author + effective_committer = True if attribute_committer is None else attribute_committer + # Determine commit message prefixing prefix_commit_message = aider_edits and ( @@ -171,12 +180,21 @@ class GitRepo: ) # Determine if author/committer names should be modified - # If co-authored-by is used for aider edits, it takes precedence over direct name modification. - use_attribute_author = attribute_author and aider_edits and not attribute_co_authored_by - use_attribute_committer = attribute_committer and not ( - aider_edits and attribute_co_authored_by + # Author modification applies only to aider edits. + # It's used if effective_author is True AND (co-authored-by is False OR author was explicitly set). + use_attribute_author = ( + aider_edits + and effective_author + and (not attribute_co_authored_by or author_explicit) ) + # Committer modification applies regardless of aider_edits (based on tests). + # It's used if effective_committer is True AND (it's not an aider edit with co-authored-by OR committer was explicitly set). + use_attribute_committer = effective_committer and ( + not (aider_edits and attribute_co_authored_by) or committer_explicit + ) + + if not commit_message: commit_message = "(no commit message provided)" diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 57bad6625..e7026a242 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -185,26 +185,35 @@ class TestRepo(unittest.TestCase): raw_repo.git.commit("-m", "initial commit") io = InputOutput() - git_repo = GitRepo(io, None, None) + # Initialize GitRepo with default None values for attributes + git_repo = GitRepo(io, None, None, attribute_author=None, attribute_committer=None) - # commit a change + # commit a change with aider_edits=True (using default attributes) fname.write_text("new content") git_repo.commit(fnames=[str(fname)], aider_edits=True) - # check the committer name + # check the committer name (defaults interpreted as True) commit = raw_repo.head.commit self.assertEqual(commit.author.name, "Test User (aider)") self.assertEqual(commit.committer.name, "Test User (aider)") - # commit a change without aider_edits + # commit a change without aider_edits (using default attributes) fname.write_text("new content again!") git_repo.commit(fnames=[str(fname)], aider_edits=False) - # check the committer name + # check the committer name (author not modified, committer still modified by default) commit = raw_repo.head.commit self.assertEqual(commit.author.name, "Test User") self.assertEqual(commit.committer.name, "Test User (aider)") + # Now test with explicit False + git_repo_explicit_false = GitRepo(io, None, None, attribute_author=False, attribute_committer=False) + fname.write_text("explicit false content") + git_repo_explicit_false.commit(fnames=[str(fname)], aider_edits=True) + commit = raw_repo.head.commit + self.assertEqual(commit.author.name, "Test User") # Explicit False + self.assertEqual(commit.committer.name, "Test User") # Explicit False + # check that the original committer name is restored original_committer_name = os.environ.get("GIT_COMMITTER_NAME") self.assertIsNone(original_committer_name) @@ -228,15 +237,13 @@ class TestRepo(unittest.TestCase): raw_repo.git.add(str(fname)) raw_repo.git.commit("-m", "initial commit") - # Mock coder args - # Mock coder args: Co-authored-by enabled, author/committer modification disabled + # Mock coder args: Co-authored-by enabled, author/committer use default (None) mock_coder = MagicMock() mock_coder.args.attribute_co_authored_by = True - mock_coder.args.attribute_author = False # Explicitly disable name modification - mock_coder.args.attribute_committer = False # Explicitly disable name modification + mock_coder.args.attribute_author = None # Default + mock_coder.args.attribute_committer = None # 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 # The code uses coder.main_model.name for the co-authored-by line mock_coder.main_model = MagicMock() mock_coder.main_model.name = "gpt-test" @@ -253,16 +260,17 @@ class TestRepo(unittest.TestCase): commit = raw_repo.head.commit self.assertIn("Co-authored-by: aider (gpt-test) ", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider edit") + # With default (None), co-authored-by takes precedence 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 + def test_commit_co_authored_by_with_explicit_name_modification(self): + # Test scenario where Co-authored-by is true AND author/committer modification are explicitly True if platform.system() == "Windows": return with GitTemporaryDirectory(): + # Setup repo... # new repo raw_repo = git.Repo() raw_repo.config_writer().set_value("user", "name", "Test User").release() @@ -274,14 +282,13 @@ class TestRepo(unittest.TestCase): 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 args: Co-authored-by enabled, author/committer modification explicitly enabled 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_author = True # Explicitly enable + mock_coder.args.attribute_committer = True # Explicitly enable 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" @@ -297,12 +304,12 @@ class TestRepo(unittest.TestCase): 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") - # When co-authored-by is true, name modification should be disabled - self.assertEqual(commit.author.name, "Test User") # Should NOT be modified - self.assertEqual(commit.committer.name, "Test User") # Should NOT be modified + # When co-authored-by is true BUT author/committer are explicit True, modification SHOULD happen + 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_co_authored_by_precedence(self): - # Test that co-authored-by takes precedence over name modification when both are enabled + def test_commit_co_authored_by_precedence_over_default(self): + # Test that co-authored-by takes precedence over default (None) name modification if platform.system() == "Windows": return @@ -318,30 +325,29 @@ class TestRepo(unittest.TestCase): raw_repo.git.add(str(fname)) raw_repo.git.commit("-m", "initial commit") - # Mock coder args: All relevant flags enabled + # Mock coder args: Co-authored-by enabled, author/committer use default (None) 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_author = None # Default + mock_coder.args.attribute_committer = None # Default mock_coder.args.attribute_commit_message_author = False mock_coder.args.attribute_commit_message_committer = False - mock_coder.main_model = MagicMock() + mock_coder.main_model = MagicMock() # Define main_model before accessing name mock_coder.main_model.name = "gpt-precedence" io = InputOutput() - # Initialize GitRepo directly with flags, simulating config/defaults if coder wasn't passed - # This ensures the test covers the case where coder might not provide args + # Initialize GitRepo directly with flags, simulating config/defaults + # Use None for author/committer to test default behavior git_repo = GitRepo( io, None, None, attribute_co_authored_by=True, - attribute_author=True, - attribute_committer=True, + attribute_author=None, # Default + attribute_committer=None, # Default ) - - # commit a change with aider_edits=True and conflicting flags + # commit a change with aider_edits=True and default flags fname.write_text("new content precedence") # Pass the coder object here to ensure its args are used if available, # but the GitRepo init already set the fallback values. @@ -351,13 +357,13 @@ class TestRepo(unittest.TestCase): commit = raw_repo.head.commit self.assertIn("Co-authored-by: aider (gpt-precedence) ", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider precedence edit") - # Co-authored-by should take precedence, names should NOT be modified + # Co-authored-by should take precedence over default (None), names should NOT be modified self.assertEqual(commit.author.name, "Test User") self.assertEqual(commit.committer.name, "Test User") def test_commit_without_co_authored_by(self): - # Cleanup of the git temp dir explodes on windows + # Test standard name modification when co-authored-by is False if platform.system() == "Windows": return @@ -373,14 +379,13 @@ class TestRepo(unittest.TestCase): raw_repo.git.add(str(fname)) raw_repo.git.commit("-m", "initial commit") - # Mock coder args (default behavior) + # Mock coder args (co-authored-by False, author/committer default None) mock_coder = MagicMock() mock_coder.args.attribute_co_authored_by = False - mock_coder.args.attribute_author = True - mock_coder.args.attribute_committer = True + mock_coder.args.attribute_author = None # Default + mock_coder.args.attribute_committer = None # 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 (though not used in this test assertion) mock_coder.main_model = MagicMock() mock_coder.main_model.name = "gpt-test-no-coauthor" @@ -392,12 +397,12 @@ class TestRepo(unittest.TestCase): fname.write_text("new content") git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") - # check the commit message and author/committer + # check the commit message and author/committer (defaults interpreted as True) commit = raw_repo.head.commit self.assertNotIn("Co-authored-by:", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider edit") - self.assertEqual(commit.author.name, "Test User (aider)") # Should be modified - self.assertEqual(commit.committer.name, "Test User (aider)") # Should be modified + self.assertEqual(commit.author.name, "Test User (aider)") # Should be modified (default True) + self.assertEqual(commit.committer.name, "Test User (aider)") # Should be modified (default True) def test_get_tracked_files(self): From 278a596bfafd8f9e596f64732d83194271bc0e4d Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:21:03 +0300 Subject: [PATCH 33/44] docs: clarify commit author/committer/co-authored-by logic Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/repo.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/aider/repo.py b/aider/repo.py index 31bcc7957..ad87a379a 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -128,6 +128,70 @@ class GitRepo: self.aider_ignore_file = Path(aider_ignore_file) def commit(self, fnames=None, context=None, message=None, aider_edits=False, coder=None): + """ + Commit the specified files or all dirty files if none are specified. + + Args: + fnames (list, optional): List of filenames to commit. Defaults to None (commit all + dirty files). + context (str, optional): Context for generating the commit message. Defaults to None. + message (str, optional): Explicit commit message. Defaults to None (generate message). + aider_edits (bool, optional): Whether the changes were made by Aider. Defaults to False. + This affects attribution logic. + coder (Coder, optional): The Coder instance, used to access config and model info. + Defaults to None. + + Returns: + tuple(str, str) or None: The commit hash and commit message if successful, else None. + + Attribution Logic: + ------------------ + This method handles Git commit attribution based on configuration flags and whether + Aider generated the changes (`aider_edits`). + + Key Concepts: + - Author: The person who originally wrote the code changes. + - Committer: The person who last applied the commit to the repository. + - aider_edits=True: Changes were generated by Aider (LLM). + - aider_edits=False: Commit is user-driven (e.g., /commit manually staged changes). + - Explicit Setting: A flag (--attribute-...) is set to True or False via command line + or config file. + - Implicit Default: A flag is not explicitly set, defaulting to None in args, which is + interpreted as True unless overridden by other logic. + + Flags: + - --attribute-author: Modify Author name to "User Name (aider)". + - --attribute-committer: Modify Committer name to "User Name (aider)". + - --attribute-co-authored-by: Add "Co-authored-by: aider () " + trailer to the commit message. + + Behavior Summary: + + 1. When aider_edits = True (AI Changes): + - If --attribute-co-authored-by=True: + - Co-authored-by trailer IS ADDED. + - Author/Committer names are NOT modified by default (co-authored-by takes precedence). + - EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY True, + the respective name IS modified (explicit overrides precedence). + - If --attribute-co-authored-by=False: + - Co-authored-by trailer is NOT added. + - Author/Committer names ARE modified by default (implicit True). + - EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY False, + the respective name is NOT modified. + + 2. When aider_edits = False (User Changes): + - --attribute-co-authored-by is IGNORED (trailer never added). + - Author name is NEVER modified (--attribute-author ignored). + - Committer name IS modified by default (implicit True, as Aider runs `git commit`). + - EXCEPTION: If --attribute-committer is EXPLICITLY False, the name is NOT modified. + + Resulting Scenarios: + - Standard AI edit (defaults): Co-authored-by=False -> Author=You(aider), Committer=You(aider) + - AI edit with Co-authored-by (default): Co-authored-by=True -> Author=You, Committer=You, Trailer added + - AI edit with Co-authored-by + Explicit Author: Co-authored-by=True, --attribute-author -> Author=You(aider), Committer=You, Trailer added + - User commit (defaults): aider_edits=False -> Author=You, Committer=You(aider) + - User commit with explicit no-committer: aider_edits=False, --no-attribute-committer -> Author=You, Committer=You + """ if not fnames and not self.repo.is_dirty(): return From 5664b5b195aa53f507376efc407dc49e204f3a0b Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:41:24 +0300 Subject: [PATCH 34/44] test: Assert commit return value in more tests Co-authored-by: aider (vertex_ai/gemini-2.0-flash-lite-001) --- tests/basic/test_repo.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index e7026a242..287d5ea16 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -190,7 +190,8 @@ class TestRepo(unittest.TestCase): # commit a change with aider_edits=True (using default attributes) fname.write_text("new content") - git_repo.commit(fnames=[str(fname)], aider_edits=True) + commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True) + self.assertIsNotNone(commit_result) # check the committer name (defaults interpreted as True) commit = raw_repo.head.commit @@ -199,7 +200,8 @@ class TestRepo(unittest.TestCase): # commit a change without aider_edits (using default attributes) fname.write_text("new content again!") - git_repo.commit(fnames=[str(fname)], aider_edits=False) + commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=False) + self.assertIsNotNone(commit_result) # check the committer name (author not modified, committer still modified by default) commit = raw_repo.head.commit @@ -209,7 +211,8 @@ class TestRepo(unittest.TestCase): # Now test with explicit False git_repo_explicit_false = GitRepo(io, None, None, attribute_author=False, attribute_committer=False) fname.write_text("explicit false content") - git_repo_explicit_false.commit(fnames=[str(fname)], aider_edits=True) + commit_result = git_repo_explicit_false.commit(fnames=[str(fname)], aider_edits=True) + self.assertIsNotNone(commit_result) commit = raw_repo.head.commit self.assertEqual(commit.author.name, "Test User") # Explicit False self.assertEqual(commit.committer.name, "Test User") # Explicit False @@ -254,7 +257,8 @@ class TestRepo(unittest.TestCase): # commit a change with aider_edits=True and co-authored-by flag fname.write_text("new content") - git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") + commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") + self.assertIsNotNone(commit_result) # check the commit message and author/committer commit = raw_repo.head.commit @@ -298,7 +302,8 @@ class TestRepo(unittest.TestCase): # 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") + commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider combo edit") + self.assertIsNotNone(commit_result) # check the commit message and author/committer commit = raw_repo.head.commit @@ -351,7 +356,8 @@ class TestRepo(unittest.TestCase): fname.write_text("new content precedence") # Pass the coder object here to ensure its args are used if available, # but the GitRepo init already set the fallback values. - git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider precedence edit") + commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider precedence edit") + self.assertIsNotNone(commit_result) # check the commit message and author/committer commit = raw_repo.head.commit @@ -395,7 +401,8 @@ class TestRepo(unittest.TestCase): # commit a change with aider_edits=True and default flags fname.write_text("new content") - git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") + commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") + self.assertIsNotNone(commit_result) # check the commit message and author/committer (defaults interpreted as True) commit = raw_repo.head.commit @@ -598,7 +605,8 @@ class TestRepo(unittest.TestCase): git_repo = GitRepo(InputOutput(), None, None) - git_repo.commit(fnames=[str(fname)]) + commit_result = git_repo.commit(fnames=[str(fname)]) + self.assertIsNotNone(commit_result) def test_git_commit_verify(self): """Test that git_commit_verify controls whether --no-verify is passed to git commit""" From 37a252748a68bb26398ea38e92472e94166abbb5 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:45:01 +0300 Subject: [PATCH 35/44] test: Fix commit result assertion in test_noop_commit --- tests/basic/test_repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 287d5ea16..88c4b4edf 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -606,7 +606,7 @@ class TestRepo(unittest.TestCase): git_repo = GitRepo(InputOutput(), None, None) commit_result = git_repo.commit(fnames=[str(fname)]) - self.assertIsNotNone(commit_result) + self.assertIsNone(commit_result) def test_git_commit_verify(self): """Test that git_commit_verify controls whether --no-verify is passed to git commit""" From d991cb67219e0728679bf5d00773f12619c97279 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:54:56 +0300 Subject: [PATCH 36/44] test: cover user commit with no committer attribution Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- tests/basic/test_repo.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 88c4b4edf..9633ec208 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -223,6 +223,15 @@ class TestRepo(unittest.TestCase): original_author_name = os.environ.get("GIT_AUTHOR_NAME") self.assertIsNone(original_author_name) + # Test user commit with explicit no-committer attribution + git_repo_user_no_committer = GitRepo(io, None, None, attribute_committer=False) + fname.write_text("user no committer content") + commit_result = git_repo_user_no_committer.commit(fnames=[str(fname)], aider_edits=False) + self.assertIsNotNone(commit_result) + commit = raw_repo.head.commit + self.assertEqual(commit.author.name, "Test User") # Author never modified for user commits + self.assertEqual(commit.committer.name, "Test User") # Explicit False prevents modification + def test_commit_with_co_authored_by(self): # Cleanup of the git temp dir explodes on windows if platform.system() == "Windows": From 3e1bc77bf20ceec54699fc1141b6d90aaf1adc1a Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:56:18 +0300 Subject: [PATCH 37/44] test: add tests for commit author/committer attribution logic Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- tests/basic/test_repo.py | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 9633ec208..5d1ff335b 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -420,6 +420,121 @@ class TestRepo(unittest.TestCase): self.assertEqual(commit.author.name, "Test User (aider)") # Should be modified (default True) self.assertEqual(commit.committer.name, "Test User (aider)") # Should be modified (default True) + def test_commit_ai_edits_no_coauthor_explicit_false(self): + # Test AI edits (aider_edits=True) when co-authored-by is False, + # but author or committer attribution is explicitly disabled. + if platform.system() == "Windows": + return + + with GitTemporaryDirectory(): + # Setup 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() + fname = Path("file.txt") + fname.touch() + raw_repo.git.add(str(fname)) + raw_repo.git.commit("-m", "initial commit") + + io = InputOutput() + + # Case 1: attribute_author = False, attribute_committer = None (default True) + mock_coder_no_author = MagicMock() + mock_coder_no_author.args.attribute_co_authored_by = False + mock_coder_no_author.args.attribute_author = False # Explicit False + mock_coder_no_author.args.attribute_committer = None # Default True + mock_coder_no_author.args.attribute_commit_message_author = False + mock_coder_no_author.args.attribute_commit_message_committer = False + mock_coder_no_author.main_model = MagicMock() + mock_coder_no_author.main_model.name = "gpt-test-no-author" + + git_repo_no_author = GitRepo(io, None, None) + fname.write_text("no author content") + commit_result = git_repo_no_author.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder_no_author, message="Aider no author") + self.assertIsNotNone(commit_result) + commit = raw_repo.head.commit + self.assertNotIn("Co-authored-by:", commit.message) + self.assertEqual(commit.author.name, "Test User") # Explicit False + self.assertEqual(commit.committer.name, "Test User (aider)") # Default True + + # Case 2: attribute_author = None (default True), attribute_committer = False + mock_coder_no_committer = MagicMock() + mock_coder_no_committer.args.attribute_co_authored_by = False + mock_coder_no_committer.args.attribute_author = None # Default True + mock_coder_no_committer.args.attribute_committer = False # Explicit False + mock_coder_no_committer.args.attribute_commit_message_author = False + mock_coder_no_committer.args.attribute_commit_message_committer = False + mock_coder_no_committer.main_model = MagicMock() + mock_coder_no_committer.main_model.name = "gpt-test-no-committer" + + git_repo_no_committer = GitRepo(io, None, None) + fname.write_text("no committer content") + commit_result = git_repo_no_committer.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder_no_committer, message="Aider no committer") + self.assertIsNotNone(commit_result) + commit = raw_repo.head.commit + self.assertNotIn("Co-authored-by:", commit.message) + self.assertEqual(commit.author.name, "Test User (aider)") # Default True + self.assertEqual(commit.committer.name, "Test User") # Explicit False + + + def test_commit_user_edits_ignores_co_author(self): + # Test user edits (aider_edits=False) behavior regarding co-author and name attribution. + # Co-author should be ignored, Author never modified, Committer follows flag. + if platform.system() == "Windows": + return + + with GitTemporaryDirectory(): + # Setup 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() + fname = Path("file.txt") + fname.touch() + raw_repo.git.add(str(fname)) + raw_repo.git.commit("-m", "initial commit") + + io = InputOutput() + + # Case 1: co_author=True, committer=None (default True) + mock_coder_user_default = MagicMock() + mock_coder_user_default.args.attribute_co_authored_by = True # Should be ignored + mock_coder_user_default.args.attribute_author = True # Should be ignored + mock_coder_user_default.args.attribute_committer = None # Default True + mock_coder_user_default.args.attribute_commit_message_author = False + mock_coder_user_default.args.attribute_commit_message_committer = False + mock_coder_user_default.main_model = MagicMock() + mock_coder_user_default.main_model.name = "gpt-user-default" + + git_repo_user_default = GitRepo(io, None, None) + fname.write_text("user default content") + commit_result = git_repo_user_default.commit(fnames=[str(fname)], aider_edits=False, coder=mock_coder_user_default, message="User default") + self.assertIsNotNone(commit_result) + commit = raw_repo.head.commit + self.assertNotIn("Co-authored-by:", commit.message) # Ignored + self.assertEqual(commit.author.name, "Test User") # Never modified + self.assertEqual(commit.committer.name, "Test User (aider)") # Default True + + # Case 2: co_author=True, committer=False + mock_coder_user_no_committer = MagicMock() + mock_coder_user_no_committer.args.attribute_co_authored_by = True # Should be ignored + mock_coder_user_no_committer.args.attribute_author = True # Should be ignored + mock_coder_user_no_committer.args.attribute_committer = False # Explicit False + mock_coder_user_no_committer.args.attribute_commit_message_author = False + mock_coder_user_no_committer.args.attribute_commit_message_committer = False + mock_coder_user_no_committer.main_model = MagicMock() + mock_coder_user_no_committer.main_model.name = "gpt-user-no-committer" + + # Need to init GitRepo with attribute_committer=False for this case + git_repo_user_no_committer = GitRepo(io, None, None, attribute_committer=False) + fname.write_text("user no committer content") + # Pass coder to simulate args being present, though GitRepo init matters more here + commit_result = git_repo_user_no_committer.commit(fnames=[str(fname)], aider_edits=False, coder=mock_coder_user_no_committer, message="User no committer") + self.assertIsNotNone(commit_result) + commit = raw_repo.head.commit + self.assertNotIn("Co-authored-by:", commit.message) # Ignored + self.assertEqual(commit.author.name, "Test User") # Never modified + self.assertEqual(commit.committer.name, "Test User") # Explicit False + def test_get_tracked_files(self): # Create a temporary directory From 9e91e8f1b2d2a0b13567da85da85fbfe046314bb Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:58:06 +0300 Subject: [PATCH 38/44] test: remove redundant commit attribution tests Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- tests/basic/test_repo.py | 104 --------------------------------------- 1 file changed, 104 deletions(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 5d1ff335b..a1224ce02 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -376,50 +376,6 @@ class TestRepo(unittest.TestCase): self.assertEqual(commit.author.name, "Test User") self.assertEqual(commit.committer.name, "Test User") - - def test_commit_without_co_authored_by(self): - # Test standard name modification when co-authored-by is False - 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 False, author/committer default None) - mock_coder = MagicMock() - mock_coder.args.attribute_co_authored_by = False - mock_coder.args.attribute_author = None # Default - mock_coder.args.attribute_committer = None # Default - mock_coder.args.attribute_commit_message_author = False - mock_coder.args.attribute_commit_message_committer = False - mock_coder.main_model = MagicMock() - mock_coder.main_model.name = "gpt-test-no-coauthor" - - - io = InputOutput() - git_repo = GitRepo(io, None, None) - - # commit a change with aider_edits=True and default flags - fname.write_text("new content") - commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit") - self.assertIsNotNone(commit_result) - - # check the commit message and author/committer (defaults interpreted as True) - commit = raw_repo.head.commit - self.assertNotIn("Co-authored-by:", commit.message) - self.assertEqual(commit.message.splitlines()[0], "Aider edit") - self.assertEqual(commit.author.name, "Test User (aider)") # Should be modified (default True) - self.assertEqual(commit.committer.name, "Test User (aider)") # Should be modified (default True) - def test_commit_ai_edits_no_coauthor_explicit_false(self): # Test AI edits (aider_edits=True) when co-authored-by is False, # but author or committer attribution is explicitly disabled. @@ -476,66 +432,6 @@ class TestRepo(unittest.TestCase): self.assertEqual(commit.author.name, "Test User (aider)") # Default True self.assertEqual(commit.committer.name, "Test User") # Explicit False - - def test_commit_user_edits_ignores_co_author(self): - # Test user edits (aider_edits=False) behavior regarding co-author and name attribution. - # Co-author should be ignored, Author never modified, Committer follows flag. - if platform.system() == "Windows": - return - - with GitTemporaryDirectory(): - # Setup 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() - fname = Path("file.txt") - fname.touch() - raw_repo.git.add(str(fname)) - raw_repo.git.commit("-m", "initial commit") - - io = InputOutput() - - # Case 1: co_author=True, committer=None (default True) - mock_coder_user_default = MagicMock() - mock_coder_user_default.args.attribute_co_authored_by = True # Should be ignored - mock_coder_user_default.args.attribute_author = True # Should be ignored - mock_coder_user_default.args.attribute_committer = None # Default True - mock_coder_user_default.args.attribute_commit_message_author = False - mock_coder_user_default.args.attribute_commit_message_committer = False - mock_coder_user_default.main_model = MagicMock() - mock_coder_user_default.main_model.name = "gpt-user-default" - - git_repo_user_default = GitRepo(io, None, None) - fname.write_text("user default content") - commit_result = git_repo_user_default.commit(fnames=[str(fname)], aider_edits=False, coder=mock_coder_user_default, message="User default") - self.assertIsNotNone(commit_result) - commit = raw_repo.head.commit - self.assertNotIn("Co-authored-by:", commit.message) # Ignored - self.assertEqual(commit.author.name, "Test User") # Never modified - self.assertEqual(commit.committer.name, "Test User (aider)") # Default True - - # Case 2: co_author=True, committer=False - mock_coder_user_no_committer = MagicMock() - mock_coder_user_no_committer.args.attribute_co_authored_by = True # Should be ignored - mock_coder_user_no_committer.args.attribute_author = True # Should be ignored - mock_coder_user_no_committer.args.attribute_committer = False # Explicit False - mock_coder_user_no_committer.args.attribute_commit_message_author = False - mock_coder_user_no_committer.args.attribute_commit_message_committer = False - mock_coder_user_no_committer.main_model = MagicMock() - mock_coder_user_no_committer.main_model.name = "gpt-user-no-committer" - - # Need to init GitRepo with attribute_committer=False for this case - git_repo_user_no_committer = GitRepo(io, None, None, attribute_committer=False) - fname.write_text("user no committer content") - # Pass coder to simulate args being present, though GitRepo init matters more here - commit_result = git_repo_user_no_committer.commit(fnames=[str(fname)], aider_edits=False, coder=mock_coder_user_no_committer, message="User no committer") - self.assertIsNotNone(commit_result) - commit = raw_repo.head.commit - self.assertNotIn("Co-authored-by:", commit.message) # Ignored - self.assertEqual(commit.author.name, "Test User") # Never modified - self.assertEqual(commit.committer.name, "Test User") # Explicit False - - def test_get_tracked_files(self): # Create a temporary directory tempdir = Path(tempfile.mkdtemp()) From 6a970c35152c350797bf44ee10f69ca23cd61dfa Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 19:58:29 +0300 Subject: [PATCH 39/44] test: remove redundant co-authored-by precedence test Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- tests/basic/test_repo.py | 54 ---------------------------------------- 1 file changed, 54 deletions(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index a1224ce02..11523996f 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -322,60 +322,6 @@ class TestRepo(unittest.TestCase): 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_co_authored_by_precedence_over_default(self): - # Test that co-authored-by takes precedence over default (None) name modification - 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 use default (None) - mock_coder = MagicMock() - mock_coder.args.attribute_co_authored_by = True - mock_coder.args.attribute_author = None # Default - mock_coder.args.attribute_committer = None # Default - mock_coder.args.attribute_commit_message_author = False - mock_coder.args.attribute_commit_message_committer = False - mock_coder.main_model = MagicMock() # Define main_model before accessing name - mock_coder.main_model.name = "gpt-precedence" - - io = InputOutput() - # Initialize GitRepo directly with flags, simulating config/defaults - # Use None for author/committer to test default behavior - git_repo = GitRepo( - io, - None, - None, - attribute_co_authored_by=True, - attribute_author=None, # Default - attribute_committer=None, # Default - ) - - # commit a change with aider_edits=True and default flags - fname.write_text("new content precedence") - # Pass the coder object here to ensure its args are used if available, - # but the GitRepo init already set the fallback values. - commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider precedence edit") - self.assertIsNotNone(commit_result) - - # check the commit message and author/committer - commit = raw_repo.head.commit - self.assertIn("Co-authored-by: aider (gpt-precedence) ", commit.message) - self.assertEqual(commit.message.splitlines()[0], "Aider precedence edit") - # Co-authored-by should take precedence over default (None), names should NOT be modified - self.assertEqual(commit.author.name, "Test User") - self.assertEqual(commit.committer.name, "Test User") - def test_commit_ai_edits_no_coauthor_explicit_false(self): # Test AI edits (aider_edits=True) when co-authored-by is False, # but author or committer attribution is explicitly disabled. From 5851d66174af65ffea837c57c22e51893898b229 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 20:00:44 +0300 Subject: [PATCH 40/44] test: improve test clarity with skipIf and assertion messages Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- tests/basic/test_repo.py | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 11523996f..4d5abac86 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -165,14 +165,11 @@ class TestRepo(unittest.TestCase): args = mock_send.call_args[0] # Get positional args self.assertEqual(args[0][0]["content"], custom_prompt) # Check first message content + @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows") @patch("aider.repo.GitRepo.get_commit_message") def test_commit_with_custom_committer_name(self, mock_send): mock_send.return_value = '"a good commit message"' - # Cleanup of the git temp dir explodes on windows - if platform.system() == "Windows": - return - with GitTemporaryDirectory(): # new repo raw_repo = git.Repo() @@ -229,14 +226,11 @@ class TestRepo(unittest.TestCase): commit_result = git_repo_user_no_committer.commit(fnames=[str(fname)], aider_edits=False) self.assertIsNotNone(commit_result) commit = raw_repo.head.commit - self.assertEqual(commit.author.name, "Test User") # Author never modified for user commits - self.assertEqual(commit.committer.name, "Test User") # Explicit False prevents modification + self.assertEqual(commit.author.name, "Test User", msg="Author name should not be modified for user commits") + self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified when attribute_committer=False") + @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows") def test_commit_with_co_authored_by(self): - # Cleanup of the git temp dir explodes on windows - if platform.system() == "Windows": - return - with GitTemporaryDirectory(): # new repo raw_repo = git.Repo() @@ -274,14 +268,12 @@ class TestRepo(unittest.TestCase): self.assertIn("Co-authored-by: aider (gpt-test) ", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider edit") # With default (None), co-authored-by takes precedence - self.assertEqual(commit.author.name, "Test User") # Should NOT be modified - self.assertEqual(commit.committer.name, "Test User") # Should NOT be modified + self.assertEqual(commit.author.name, "Test User", msg="Author name should not be modified when co-authored-by takes precedence") + self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified when co-authored-by takes precedence") + @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows") def test_commit_co_authored_by_with_explicit_name_modification(self): # Test scenario where Co-authored-by is true AND author/committer modification are explicitly True - if platform.system() == "Windows": - return - with GitTemporaryDirectory(): # Setup repo... # new repo @@ -319,15 +311,13 @@ class TestRepo(unittest.TestCase): self.assertIn("Co-authored-by: aider (gpt-test-combo) ", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider combo edit") # When co-authored-by is true BUT author/committer are explicit True, modification SHOULD happen - self.assertEqual(commit.author.name, "Test User (aider)") # Should be modified - self.assertEqual(commit.committer.name, "Test User (aider)") # Should be modified + self.assertEqual(commit.author.name, "Test User (aider)", msg="Author name should be modified when explicitly True, even with co-author") + self.assertEqual(commit.committer.name, "Test User (aider)", msg="Committer name should be modified when explicitly True, even with co-author") + @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows") def test_commit_ai_edits_no_coauthor_explicit_false(self): # Test AI edits (aider_edits=True) when co-authored-by is False, # but author or committer attribution is explicitly disabled. - if platform.system() == "Windows": - return - with GitTemporaryDirectory(): # Setup repo raw_repo = git.Repo() @@ -375,8 +365,8 @@ class TestRepo(unittest.TestCase): self.assertIsNotNone(commit_result) commit = raw_repo.head.commit self.assertNotIn("Co-authored-by:", commit.message) - self.assertEqual(commit.author.name, "Test User (aider)") # Default True - self.assertEqual(commit.committer.name, "Test User") # Explicit False + self.assertEqual(commit.author.name, "Test User (aider)", msg="Author name should be modified (default True) when co-author=False") + self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified (explicit False) when co-author=False") def test_get_tracked_files(self): # Create a temporary directory @@ -574,12 +564,9 @@ class TestRepo(unittest.TestCase): commit_result = git_repo.commit(fnames=[str(fname)]) self.assertIsNone(commit_result) + @unittest.skipIf(platform.system() == "Windows", "Git hook execution differs on Windows") def test_git_commit_verify(self): """Test that git_commit_verify controls whether --no-verify is passed to git commit""" - # Skip on Windows as hook execution works differently - if platform.system() == "Windows": - return - with GitTemporaryDirectory(): # Create a new repo raw_repo = git.Repo() From 38dfd6f4f96110d0e9bb5fcd9fcea044f744b3c5 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 20:07:33 +0300 Subject: [PATCH 41/44] docs: clarify --attribute-co-authored-by precedence --- aider/args.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aider/args.py b/aider/args.py index 7aaf10f2a..dfa84b6c2 100644 --- a/aider/args.py +++ b/aider/args.py @@ -458,7 +458,11 @@ def get_parser(default_config_files, git_root): "--attribute-co-authored-by", action=argparse.BooleanOptionalAction, default=False, - help="Attribute aider edits using the Co-authored-by trailer in the commit message (default: False).", + help=( + "Attribute aider edits using the Co-authored-by trailer in the commit message" + " (default: False). If True, this takes precedence over default --attribute-author and" + " --attribute-committer behavior unless they are explicitly set to True." + ), ) group.add_argument( "--git-commit-verify", From 165e237be78c36437d1f2b5f0101546a1f4e2026 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 20:25:01 +0300 Subject: [PATCH 42/44] chore: remove unnecessary comment in repo.py Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/repo.py b/aider/repo.py index ad87a379a..85130e105 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -210,7 +210,7 @@ class GitRepo: 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 # <-- Restored + attribute_co_authored_by = coder.args.attribute_co_authored_by else: # Fallback to self attributes (initialized from config/defaults) attribute_author = self.attribute_author From 3f94fd5e4edd2205a24d5716c4dec3e9075f3b56 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 20:38:25 +0300 Subject: [PATCH 43/44] refactor: Simplify access to attribute_co_authored_by Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/repo.py b/aider/repo.py index 85130e105..0fa58ab7d 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -217,7 +217,7 @@ class GitRepo: 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 = getattr(self, "attribute_co_authored_by", False) # Should be False if not set + attribute_co_authored_by = self.attribute_co_authored_by # Determine explicit settings (None means use default behavior) author_explicit = attribute_author is not None From 1d42690824f4208c612210b73b6d002830aa7882 Mon Sep 17 00:00:00 2001 From: Andrew Grigorev Date: Sat, 12 Apr 2025 20:50:29 +0300 Subject: [PATCH 44/44] fix: update co-authored-by domain to aider.chat Co-authored-by: aider (vertex_ai/gemini-2.5-pro-exp-03-25) --- aider/repo.py | 4 ++-- tests/basic/test_repo.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aider/repo.py b/aider/repo.py index 0fa58ab7d..aa2d525f7 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -162,7 +162,7 @@ class GitRepo: Flags: - --attribute-author: Modify Author name to "User Name (aider)". - --attribute-committer: Modify Committer name to "User Name (aider)". - - --attribute-co-authored-by: Add "Co-authored-by: aider () " + - --attribute-co-authored-by: Add "Co-authored-by: aider () " trailer to the commit message. Behavior Summary: @@ -240,7 +240,7 @@ class GitRepo: 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}) " + f"\n\nCo-authored-by: aider ({model_name}) " ) # Determine if author/committer names should be modified diff --git a/tests/basic/test_repo.py b/tests/basic/test_repo.py index 4d5abac86..400c307a8 100644 --- a/tests/basic/test_repo.py +++ b/tests/basic/test_repo.py @@ -265,7 +265,7 @@ class TestRepo(unittest.TestCase): # check the commit message and author/committer commit = raw_repo.head.commit - self.assertIn("Co-authored-by: aider (gpt-test) ", commit.message) + self.assertIn("Co-authored-by: aider (gpt-test) ", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider edit") # With default (None), co-authored-by takes precedence self.assertEqual(commit.author.name, "Test User", msg="Author name should not be modified when co-authored-by takes precedence") @@ -308,7 +308,7 @@ class TestRepo(unittest.TestCase): # check the commit message and author/committer commit = raw_repo.head.commit - self.assertIn("Co-authored-by: aider (gpt-test-combo) ", commit.message) + self.assertIn("Co-authored-by: aider (gpt-test-combo) ", commit.message) self.assertEqual(commit.message.splitlines()[0], "Aider combo edit") # When co-authored-by is true BUT author/committer are explicit True, modification SHOULD happen self.assertEqual(commit.author.name, "Test User (aider)", msg="Author name should be modified when explicitly True, even with co-author")