mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-20 12:24:59 +00:00
666 lines
27 KiB
Python
666 lines
27 KiB
Python
import os
|
|
import platform
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import git
|
|
|
|
from aider.dump import dump # noqa: F401
|
|
from aider.io import InputOutput
|
|
from aider.models import Model
|
|
from aider.repo import GitRepo
|
|
from aider.utils import GitTemporaryDirectory
|
|
|
|
|
|
class TestRepo(unittest.TestCase):
|
|
def setUp(self):
|
|
self.GPT35 = Model("gpt-3.5-turbo")
|
|
|
|
def test_diffs_empty_repo(self):
|
|
with GitTemporaryDirectory():
|
|
repo = git.Repo()
|
|
|
|
# Add a change to the index
|
|
fname = Path("foo.txt")
|
|
fname.write_text("index\n")
|
|
repo.git.add(str(fname))
|
|
|
|
# Make a change in the working dir
|
|
fname.write_text("workingdir\n")
|
|
|
|
git_repo = GitRepo(InputOutput(), None, ".")
|
|
diffs = git_repo.get_diffs()
|
|
self.assertIn("index", diffs)
|
|
self.assertIn("workingdir", diffs)
|
|
|
|
def test_diffs_nonempty_repo(self):
|
|
with GitTemporaryDirectory():
|
|
repo = git.Repo()
|
|
fname = Path("foo.txt")
|
|
fname.touch()
|
|
repo.git.add(str(fname))
|
|
|
|
fname2 = Path("bar.txt")
|
|
fname2.touch()
|
|
repo.git.add(str(fname2))
|
|
|
|
repo.git.commit("-m", "initial")
|
|
|
|
fname.write_text("index\n")
|
|
repo.git.add(str(fname))
|
|
|
|
fname2.write_text("workingdir\n")
|
|
|
|
git_repo = GitRepo(InputOutput(), None, ".")
|
|
diffs = git_repo.get_diffs()
|
|
self.assertIn("index", diffs)
|
|
self.assertIn("workingdir", diffs)
|
|
|
|
def test_diffs_detached_head(self):
|
|
with GitTemporaryDirectory():
|
|
repo = git.Repo()
|
|
fname = Path("foo.txt")
|
|
fname.touch()
|
|
repo.git.add(str(fname))
|
|
repo.git.commit("-m", "foo")
|
|
|
|
fname2 = Path("bar.txt")
|
|
fname2.touch()
|
|
repo.git.add(str(fname2))
|
|
repo.git.commit("-m", "bar")
|
|
|
|
fname3 = Path("baz.txt")
|
|
fname3.touch()
|
|
repo.git.add(str(fname3))
|
|
repo.git.commit("-m", "baz")
|
|
|
|
repo.git.checkout("HEAD^")
|
|
|
|
fname.write_text("index\n")
|
|
repo.git.add(str(fname))
|
|
|
|
fname2.write_text("workingdir\n")
|
|
|
|
git_repo = GitRepo(InputOutput(), None, ".")
|
|
diffs = git_repo.get_diffs()
|
|
self.assertIn("index", diffs)
|
|
self.assertIn("workingdir", diffs)
|
|
|
|
def test_diffs_between_commits(self):
|
|
with GitTemporaryDirectory():
|
|
repo = git.Repo()
|
|
fname = Path("foo.txt")
|
|
|
|
fname.write_text("one\n")
|
|
repo.git.add(str(fname))
|
|
repo.git.commit("-m", "initial")
|
|
|
|
fname.write_text("two\n")
|
|
repo.git.add(str(fname))
|
|
repo.git.commit("-m", "second")
|
|
|
|
git_repo = GitRepo(InputOutput(), None, ".")
|
|
diffs = git_repo.diff_commits(False, "HEAD~1", "HEAD")
|
|
self.assertIn("two", diffs)
|
|
|
|
@patch("aider.models.Model.simple_send_with_retries")
|
|
def test_get_commit_message(self, mock_send):
|
|
mock_send.side_effect = ["", "a good commit message"]
|
|
|
|
model1 = Model("gpt-3.5-turbo")
|
|
model2 = Model("gpt-4")
|
|
dump(model1)
|
|
dump(model2)
|
|
repo = GitRepo(InputOutput(), None, None, models=[model1, model2])
|
|
|
|
# Call the get_commit_message method with dummy diff and context
|
|
result = repo.get_commit_message("dummy diff", "dummy context")
|
|
|
|
# Assert that the returned message is the expected one from the second model
|
|
self.assertEqual(result, "a good commit message")
|
|
|
|
# Check that simple_send_with_retries was called twice
|
|
self.assertEqual(mock_send.call_count, 2)
|
|
|
|
# Check that both calls were made with the same messages
|
|
first_call_messages = mock_send.call_args_list[0][0][0] # Get messages from first call
|
|
second_call_messages = mock_send.call_args_list[1][0][0] # Get messages from second call
|
|
self.assertEqual(first_call_messages, second_call_messages)
|
|
|
|
@patch("aider.models.Model.simple_send_with_retries")
|
|
def test_get_commit_message_strip_quotes(self, mock_send):
|
|
mock_send.return_value = '"a good commit message"'
|
|
|
|
repo = GitRepo(InputOutput(), None, None, models=[self.GPT35])
|
|
# Call the get_commit_message method with dummy diff and context
|
|
result = repo.get_commit_message("dummy diff", "dummy context")
|
|
|
|
# Assert that the returned message is the expected one
|
|
self.assertEqual(result, "a good commit message")
|
|
|
|
@patch("aider.models.Model.simple_send_with_retries")
|
|
def test_get_commit_message_no_strip_unmatched_quotes(self, mock_send):
|
|
mock_send.return_value = 'a good "commit message"'
|
|
|
|
repo = GitRepo(InputOutput(), None, None, models=[self.GPT35])
|
|
# Call the get_commit_message method with dummy diff and context
|
|
result = repo.get_commit_message("dummy diff", "dummy context")
|
|
|
|
# Assert that the returned message is the expected one
|
|
self.assertEqual(result, 'a good "commit message"')
|
|
|
|
@patch("aider.models.Model.simple_send_with_retries")
|
|
def test_get_commit_message_with_custom_prompt(self, mock_send):
|
|
mock_send.return_value = "Custom commit message"
|
|
custom_prompt = "Generate a commit message in the style of Shakespeare"
|
|
|
|
repo = GitRepo(InputOutput(), None, None, models=[self.GPT35], commit_prompt=custom_prompt)
|
|
result = repo.get_commit_message("dummy diff", "dummy context")
|
|
|
|
self.assertEqual(result, "Custom commit message")
|
|
mock_send.assert_called_once()
|
|
args = mock_send.call_args[0] # Get positional args
|
|
self.assertEqual(args[0][0]["content"], custom_prompt) # Check first message content
|
|
|
|
@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()
|
|
raw_repo.config_writer().set_value("user", "name", "Test User").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")
|
|
|
|
io = InputOutput()
|
|
# Initialize GitRepo with default None values for attributes
|
|
git_repo = GitRepo(io, None, None, attribute_author=None, attribute_committer=None)
|
|
|
|
# commit a change with aider_edits=True (using default attributes)
|
|
fname.write_text("new content")
|
|
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
|
|
self.assertEqual(commit.author.name, "Test User (aider)")
|
|
self.assertEqual(commit.committer.name, "Test User (aider)")
|
|
|
|
# commit a change without aider_edits (using default attributes)
|
|
fname.write_text("new content again!")
|
|
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
|
|
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")
|
|
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
|
|
|
|
# check that the original committer name is restored
|
|
original_committer_name = os.environ.get("GIT_COMMITTER_NAME")
|
|
self.assertIsNone(original_committer_name)
|
|
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":
|
|
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
|
|
# 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()
|
|
git_repo = GitRepo(io, None, None)
|
|
|
|
# commit a change with aider_edits=True and co-authored-by flag
|
|
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
|
|
commit = raw_repo.head.commit
|
|
self.assertIn("Co-authored-by: aider (gpt-test) <noreply@aider.dev>", 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_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()
|
|
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 explicitly enabled
|
|
mock_coder = MagicMock()
|
|
mock_coder.args.attribute_co_authored_by = True
|
|
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
|
|
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")
|
|
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
|
|
self.assertIn("Co-authored-by: aider (gpt-test-combo) <noreply@aider.dev>", 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
|
|
|
|
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) <noreply@aider.dev>", 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_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_get_tracked_files(self):
|
|
# Create a temporary directory
|
|
tempdir = Path(tempfile.mkdtemp())
|
|
|
|
# Initialize a git repository in the temporary directory and set user name and email
|
|
repo = git.Repo.init(tempdir)
|
|
repo.config_writer().set_value("user", "name", "Test User").release()
|
|
repo.config_writer().set_value("user", "email", "testuser@example.com").release()
|
|
|
|
# Create three empty files and add them to the git repository
|
|
filenames = ["README.md", "subdir/fänny.md", "systemüber/blick.md", 'file"with"quotes.txt']
|
|
created_files = []
|
|
for filename in filenames:
|
|
file_path = tempdir / filename
|
|
try:
|
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
file_path.touch()
|
|
repo.git.add(str(file_path))
|
|
created_files.append(Path(filename))
|
|
except OSError:
|
|
# windows won't allow files with quotes, that's ok
|
|
self.assertIn('"', filename)
|
|
self.assertEqual(os.name, "nt")
|
|
|
|
self.assertTrue(len(created_files) >= 3)
|
|
|
|
repo.git.commit("-m", "added")
|
|
|
|
tracked_files = GitRepo(InputOutput(), [tempdir], None).get_tracked_files()
|
|
|
|
# On windows, paths will come back \like\this, so normalize them back to Paths
|
|
tracked_files = [Path(fn) for fn in tracked_files]
|
|
|
|
# Assert that coder.get_tracked_files() returns the three filenames
|
|
self.assertEqual(set(tracked_files), set(created_files))
|
|
|
|
def test_get_tracked_files_with_new_staged_file(self):
|
|
with GitTemporaryDirectory():
|
|
# new repo
|
|
raw_repo = git.Repo()
|
|
|
|
# add it, but no commits at all in the raw_repo yet
|
|
fname = Path("new.txt")
|
|
fname.touch()
|
|
raw_repo.git.add(str(fname))
|
|
|
|
git_repo = GitRepo(InputOutput(), None, None)
|
|
|
|
# better be there
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
|
|
# commit it, better still be there
|
|
raw_repo.git.commit("-m", "new")
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
|
|
# new file, added but not committed
|
|
fname2 = Path("new2.txt")
|
|
fname2.touch()
|
|
raw_repo.git.add(str(fname2))
|
|
|
|
# both should be there
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
self.assertIn(str(fname2), fnames)
|
|
|
|
def test_get_tracked_files_with_aiderignore(self):
|
|
with GitTemporaryDirectory():
|
|
# new repo
|
|
raw_repo = git.Repo()
|
|
|
|
# add it, but no commits at all in the raw_repo yet
|
|
fname = Path("new.txt")
|
|
fname.touch()
|
|
raw_repo.git.add(str(fname))
|
|
|
|
aiderignore = Path(".aiderignore")
|
|
git_repo = GitRepo(InputOutput(), None, None, str(aiderignore))
|
|
|
|
# better be there
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
|
|
# commit it, better still be there
|
|
raw_repo.git.commit("-m", "new")
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
|
|
# new file, added but not committed
|
|
fname2 = Path("new2.txt")
|
|
fname2.touch()
|
|
raw_repo.git.add(str(fname2))
|
|
|
|
# both should be there
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
self.assertIn(str(fname2), fnames)
|
|
|
|
aiderignore.write_text("new.txt\n")
|
|
time.sleep(2)
|
|
|
|
# new.txt should be gone!
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertNotIn(str(fname), fnames)
|
|
self.assertIn(str(fname2), fnames)
|
|
|
|
# This does not work in github actions?!
|
|
# The mtime doesn't change, even if I time.sleep(1)
|
|
# Before doing this write_text()!?
|
|
#
|
|
# aiderignore.write_text("new2.txt\n")
|
|
# new2.txt should be gone!
|
|
# fnames = git_repo.get_tracked_files()
|
|
# self.assertIn(str(fname), fnames)
|
|
# self.assertNotIn(str(fname2), fnames)
|
|
|
|
def test_get_tracked_files_from_subdir(self):
|
|
with GitTemporaryDirectory():
|
|
# new repo
|
|
raw_repo = git.Repo()
|
|
|
|
# add it, but no commits at all in the raw_repo yet
|
|
fname = Path("subdir/new.txt")
|
|
fname.parent.mkdir()
|
|
fname.touch()
|
|
raw_repo.git.add(str(fname))
|
|
|
|
os.chdir(fname.parent)
|
|
|
|
git_repo = GitRepo(InputOutput(), None, None)
|
|
|
|
# better be there
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
|
|
# commit it, better still be there
|
|
raw_repo.git.commit("-m", "new")
|
|
fnames = git_repo.get_tracked_files()
|
|
self.assertIn(str(fname), fnames)
|
|
|
|
def test_subtree_only(self):
|
|
with GitTemporaryDirectory():
|
|
# Create a new repo
|
|
raw_repo = git.Repo()
|
|
|
|
# Create files in different directories
|
|
root_file = Path("root.txt")
|
|
subdir_file = Path("subdir/subdir_file.txt")
|
|
another_subdir_file = Path("another_subdir/another_file.txt")
|
|
|
|
root_file.touch()
|
|
subdir_file.parent.mkdir()
|
|
subdir_file.touch()
|
|
another_subdir_file.parent.mkdir()
|
|
another_subdir_file.touch()
|
|
|
|
raw_repo.git.add(str(root_file), str(subdir_file), str(another_subdir_file))
|
|
raw_repo.git.commit("-m", "Initial commit")
|
|
|
|
# Change to the subdir
|
|
os.chdir(subdir_file.parent)
|
|
|
|
# Create GitRepo instance with subtree_only=True
|
|
git_repo = GitRepo(InputOutput(), None, None, subtree_only=True)
|
|
|
|
# Test ignored_file method
|
|
self.assertFalse(git_repo.ignored_file(str(subdir_file)))
|
|
self.assertTrue(git_repo.ignored_file(str(root_file)))
|
|
self.assertTrue(git_repo.ignored_file(str(another_subdir_file)))
|
|
|
|
# Test get_tracked_files method
|
|
tracked_files = git_repo.get_tracked_files()
|
|
self.assertIn(str(subdir_file), tracked_files)
|
|
self.assertNotIn(str(root_file), tracked_files)
|
|
self.assertNotIn(str(another_subdir_file), tracked_files)
|
|
|
|
@patch("aider.models.Model.simple_send_with_retries")
|
|
def test_noop_commit(self, mock_send):
|
|
mock_send.return_value = '"a good commit message"'
|
|
|
|
with GitTemporaryDirectory():
|
|
# new repo
|
|
raw_repo = git.Repo()
|
|
|
|
# add it, but no commits at all in the raw_repo yet
|
|
fname = Path("file.txt")
|
|
fname.touch()
|
|
raw_repo.git.add(str(fname))
|
|
raw_repo.git.commit("-m", "new")
|
|
|
|
git_repo = GitRepo(InputOutput(), None, None)
|
|
|
|
commit_result = git_repo.commit(fnames=[str(fname)])
|
|
self.assertIsNone(commit_result)
|
|
|
|
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()
|
|
|
|
# Create a file to commit
|
|
fname = Path("test_file.txt")
|
|
fname.write_text("initial content")
|
|
raw_repo.git.add(str(fname))
|
|
|
|
# Do the initial commit
|
|
raw_repo.git.commit("-m", "Initial commit")
|
|
|
|
# Now create a pre-commit hook that always fails
|
|
hooks_dir = Path(raw_repo.git_dir) / "hooks"
|
|
hooks_dir.mkdir(exist_ok=True)
|
|
|
|
pre_commit_hook = hooks_dir / "pre-commit"
|
|
pre_commit_hook.write_text("#!/bin/sh\nexit 1\n") # Always fail
|
|
pre_commit_hook.chmod(0o755) # Make executable
|
|
|
|
# Modify the file
|
|
fname.write_text("modified content")
|
|
|
|
# Create GitRepo with verify=True (default)
|
|
io = InputOutput()
|
|
git_repo_verify = GitRepo(io, None, None, git_commit_verify=True)
|
|
|
|
# Attempt to commit - should fail due to pre-commit hook
|
|
commit_result = git_repo_verify.commit(fnames=[str(fname)], message="Should fail")
|
|
self.assertIsNone(commit_result)
|
|
|
|
# Create GitRepo with verify=False
|
|
git_repo_no_verify = GitRepo(io, None, None, git_commit_verify=False)
|
|
|
|
# Attempt to commit - should succeed by bypassing the hook
|
|
commit_result = git_repo_no_verify.commit(fnames=[str(fname)], message="Should succeed")
|
|
self.assertIsNotNone(commit_result)
|
|
|
|
# Verify the commit was actually made
|
|
latest_commit_msg = raw_repo.head.commit.message
|
|
self.assertEqual(latest_commit_msg.strip(), "Should succeed")
|