This commit is contained in:
Paul Gauthier 2025-05-08 15:13:38 -07:00
parent b40baaceea
commit 38e7f04e60
5 changed files with 115 additions and 45 deletions

View file

@ -1,5 +1,4 @@
import os import os
import oslex
import re import re
import shlex import shlex
import subprocess import subprocess
@ -9,6 +8,7 @@ import warnings
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import oslex
from grep_ast import TreeContext, filename_to_lang from grep_ast import TreeContext, filename_to_lang
from grep_ast.tsl import get_parser # noqa: E402 from grep_ast.tsl import get_parser # noqa: E402

View file

@ -227,7 +227,6 @@ class GitRepo:
effective_author = True if attribute_author is None else attribute_author effective_author = True if attribute_author is None else attribute_author
effective_committer = True if attribute_committer is None else attribute_committer effective_committer = True if attribute_committer is None else attribute_committer
# Determine commit message prefixing # Determine commit message prefixing
prefix_commit_message = aider_edits and ( prefix_commit_message = aider_edits and (
attribute_commit_message_author or attribute_commit_message_committer attribute_commit_message_author or attribute_commit_message_committer
@ -247,9 +246,7 @@ class GitRepo:
# Author modification applies only to aider edits. # 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). # It's used if effective_author is True AND (co-authored-by is False OR author was explicitly set).
use_attribute_author = ( use_attribute_author = (
aider_edits aider_edits and effective_author and (not attribute_co_authored_by or author_explicit)
and effective_author
and (not attribute_co_authored_by or author_explicit)
) )
# Committer modification applies regardless of aider_edits (based on tests). # Committer modification applies regardless of aider_edits (based on tests).
@ -258,7 +255,6 @@ class GitRepo:
not (aider_edits and attribute_co_authored_by) or committer_explicit not (aider_edits and attribute_co_authored_by) or committer_explicit
) )
if not commit_message: if not commit_message:
commit_message = "(no commit message provided)" commit_message = "(no commit message provided)"
@ -291,7 +287,9 @@ class GitRepo:
with contextlib.ExitStack() as stack: with contextlib.ExitStack() as stack:
if use_attribute_committer: if use_attribute_committer:
stack.enter_context( stack.enter_context(
set_git_env("GIT_COMMITTER_NAME", committer_name, original_committer_name_env) set_git_env(
"GIT_COMMITTER_NAME", committer_name, original_committer_name_env
)
) )
if use_attribute_author: if use_attribute_author:
stack.enter_context( stack.enter_context(

View file

@ -206,7 +206,9 @@ class TestRepo(unittest.TestCase):
self.assertEqual(commit.committer.name, "Test User (aider)") self.assertEqual(commit.committer.name, "Test User (aider)")
# Now test with explicit False # Now test with explicit False
git_repo_explicit_false = GitRepo(io, None, None, attribute_author=False, attribute_committer=False) git_repo_explicit_false = GitRepo(
io, None, None, attribute_author=False, attribute_committer=False
)
fname.write_text("explicit false content") fname.write_text("explicit false content")
commit_result = 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) self.assertIsNotNone(commit_result)
@ -223,11 +225,21 @@ class TestRepo(unittest.TestCase):
# Test user commit with explicit no-committer attribution # Test user commit with explicit no-committer attribution
git_repo_user_no_committer = GitRepo(io, None, None, attribute_committer=False) git_repo_user_no_committer = GitRepo(io, None, None, attribute_committer=False)
fname.write_text("user no committer content") fname.write_text("user no committer content")
commit_result = git_repo_user_no_committer.commit(fnames=[str(fname)], aider_edits=False) commit_result = git_repo_user_no_committer.commit(
fnames=[str(fname)], aider_edits=False
)
self.assertIsNotNone(commit_result) self.assertIsNotNone(commit_result)
commit = raw_repo.head.commit commit = raw_repo.head.commit
self.assertEqual(commit.author.name, "Test User", msg="Author name should not be modified for user commits") self.assertEqual(
self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified when attribute_committer=False") 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") @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
def test_commit_with_co_authored_by(self): def test_commit_with_co_authored_by(self):
@ -254,13 +266,14 @@ class TestRepo(unittest.TestCase):
mock_coder.main_model = MagicMock() mock_coder.main_model = MagicMock()
mock_coder.main_model.name = "gpt-test" mock_coder.main_model.name = "gpt-test"
io = InputOutput() io = InputOutput()
git_repo = GitRepo(io, None, None) git_repo = GitRepo(io, None, None)
# commit a change with aider_edits=True and co-authored-by flag # commit a change with aider_edits=True and co-authored-by flag
fname.write_text("new content") fname.write_text("new content")
commit_result = 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) self.assertIsNotNone(commit_result)
# check the commit message and author/committer # check the commit message and author/committer
@ -268,8 +281,16 @@ class TestRepo(unittest.TestCase):
self.assertIn("Co-authored-by: aider (gpt-test) <noreply@aider.chat>", commit.message) self.assertIn("Co-authored-by: aider (gpt-test) <noreply@aider.chat>", commit.message)
self.assertEqual(commit.message.splitlines()[0], "Aider edit") self.assertEqual(commit.message.splitlines()[0], "Aider edit")
# With default (None), co-authored-by takes precedence # 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") self.assertEqual(
self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified when co-authored-by takes precedence") 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") @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
def test_commit_co_authored_by_with_explicit_name_modification(self): def test_commit_co_authored_by_with_explicit_name_modification(self):
@ -297,22 +318,33 @@ class TestRepo(unittest.TestCase):
mock_coder.main_model = MagicMock() mock_coder.main_model = MagicMock()
mock_coder.main_model.name = "gpt-test-combo" mock_coder.main_model.name = "gpt-test-combo"
io = InputOutput() io = InputOutput()
git_repo = GitRepo(io, None, None) git_repo = GitRepo(io, None, None)
# commit a change with aider_edits=True and combo flags # commit a change with aider_edits=True and combo flags
fname.write_text("new content combo") fname.write_text("new content combo")
commit_result = 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) self.assertIsNotNone(commit_result)
# check the commit message and author/committer # check the commit message and author/committer
commit = raw_repo.head.commit commit = raw_repo.head.commit
self.assertIn("Co-authored-by: aider (gpt-test-combo) <noreply@aider.chat>", commit.message) self.assertIn(
"Co-authored-by: aider (gpt-test-combo) <noreply@aider.chat>", commit.message
)
self.assertEqual(commit.message.splitlines()[0], "Aider combo edit") self.assertEqual(commit.message.splitlines()[0], "Aider combo edit")
# When co-authored-by is true BUT author/committer are explicit True, modification SHOULD happen # 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") self.assertEqual(
self.assertEqual(commit.committer.name, "Test User (aider)", msg="Committer name should be modified when explicitly True, even with co-author") 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") @unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
def test_commit_ai_edits_no_coauthor_explicit_false(self): def test_commit_ai_edits_no_coauthor_explicit_false(self):
@ -342,7 +374,12 @@ class TestRepo(unittest.TestCase):
git_repo_no_author = GitRepo(io, None, None) git_repo_no_author = GitRepo(io, None, None)
fname.write_text("no author content") 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") 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) self.assertIsNotNone(commit_result)
commit = raw_repo.head.commit commit = raw_repo.head.commit
self.assertNotIn("Co-authored-by:", commit.message) self.assertNotIn("Co-authored-by:", commit.message)
@ -361,12 +398,25 @@ class TestRepo(unittest.TestCase):
git_repo_no_committer = GitRepo(io, None, None) git_repo_no_committer = GitRepo(io, None, None)
fname.write_text("no committer content") 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") 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) self.assertIsNotNone(commit_result)
commit = raw_repo.head.commit commit = raw_repo.head.commit
self.assertNotIn("Co-authored-by:", commit.message) self.assertNotIn("Co-authored-by:", commit.message)
self.assertEqual(commit.author.name, "Test User (aider)", msg="Author name should be modified (default True) when co-author=False") self.assertEqual(
self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified (explicit False) when co-author=False") 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): def test_get_tracked_files(self):
# Create a temporary directory # Create a temporary directory

View file

@ -1,7 +1,9 @@
import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from aider.scrape import install_playwright, Scraper import pytest
from aider.scrape import Scraper, install_playwright
class DummyIO: class DummyIO:
def __init__(self): def __init__(self):
@ -25,13 +27,16 @@ def test_scraper_disable_playwright_flag(monkeypatch):
scraper = Scraper(print_error=io.tool_error, playwright_available=False) scraper = Scraper(print_error=io.tool_error, playwright_available=False)
# Patch scrape_with_httpx to check it is called # Patch scrape_with_httpx to check it is called
called = {} called = {}
def fake_httpx(url): def fake_httpx(url):
called['called'] = True called["called"] = True
return "plain text", "text/plain" return "plain text", "text/plain"
scraper.scrape_with_httpx = fake_httpx scraper.scrape_with_httpx = fake_httpx
content = scraper.scrape("http://example.com") content = scraper.scrape("http://example.com")
assert content == "plain text" assert content == "plain text"
assert called['called'] assert called["called"]
def test_scraper_enable_playwright(monkeypatch): def test_scraper_enable_playwright(monkeypatch):
io = DummyIO() io = DummyIO()
@ -39,13 +44,16 @@ def test_scraper_enable_playwright(monkeypatch):
scraper = Scraper(print_error=io.tool_error, playwright_available=True) scraper = Scraper(print_error=io.tool_error, playwright_available=True)
# Patch scrape_with_playwright to check it is called # Patch scrape_with_playwright to check it is called
called = {} called = {}
def fake_playwright(url): def fake_playwright(url):
called['called'] = True called["called"] = True
return "<html>hi</html>", "text/html" return "<html>hi</html>", "text/html"
scraper.scrape_with_playwright = fake_playwright scraper.scrape_with_playwright = fake_playwright
content = scraper.scrape("http://example.com") content = scraper.scrape("http://example.com")
assert content.startswith("hi") or "<html>" in content assert content.startswith("hi") or "<html>" in content
assert called['called'] assert called["called"]
def test_commands_web_disable_playwright(monkeypatch): def test_commands_web_disable_playwright(monkeypatch):
""" """
@ -59,16 +67,22 @@ def test_commands_web_disable_playwright(monkeypatch):
self.outputs = [] self.outputs = []
self.warnings = [] self.warnings = []
self.errors = [] self.errors = []
def tool_output(self, msg, *a, **k): def tool_output(self, msg, *a, **k):
self.outputs.append(msg) self.outputs.append(msg)
def tool_warning(self, msg, *a, **k): def tool_warning(self, msg, *a, **k):
self.warnings.append(msg) self.warnings.append(msg)
def tool_error(self, msg, *a, **k): def tool_error(self, msg, *a, **k):
self.errors.append(msg) self.errors.append(msg)
def read_text(self, filename, silent=False): def read_text(self, filename, silent=False):
return "" return ""
def confirm_ask(self, *a, **k): def confirm_ask(self, *a, **k):
return True return True
def print(self, *a, **k): def print(self, *a, **k):
pass pass
@ -77,18 +91,25 @@ def test_commands_web_disable_playwright(monkeypatch):
def __init__(self): def __init__(self):
self.cur_messages = [] self.cur_messages = []
self.main_model = type("M", (), {"edit_format": "code", "name": "dummy", "info": {}}) self.main_model = type("M", (), {"edit_format": "code", "name": "dummy", "info": {}})
def get_rel_fname(self, fname): def get_rel_fname(self, fname):
return fname return fname
def get_inchat_relative_files(self): def get_inchat_relative_files(self):
return [] return []
def abs_root_path(self, fname): def abs_root_path(self, fname):
return fname return fname
def get_all_abs_files(self): def get_all_abs_files(self):
return [] return []
def get_announcements(self): def get_announcements(self):
return [] return []
def format_chat_chunks(self): def format_chat_chunks(self):
return type("Chunks", (), {"repo": [], "readonly_files": [], "chat_files": []})() return type("Chunks", (), {"repo": [], "readonly_files": [], "chat_files": []})()
def event(self, *a, **k): def event(self, *a, **k):
pass pass
@ -99,6 +120,7 @@ def test_commands_web_disable_playwright(monkeypatch):
class DummyScraper: class DummyScraper:
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.called = False self.called = False
def scrape(self, url): def scrape(self, url):
self.called = True self.called = True
return "dummy content" return "dummy content"