mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-24 22:34:59 +00:00
Merge branch 'refactor-repo' into chat-history
This commit is contained in:
commit
844194c268
9 changed files with 194 additions and 41 deletions
|
@ -1,8 +1,9 @@
|
|||
# Release history
|
||||
|
||||
### main branch on GitHub
|
||||
### v0.10.1
|
||||
|
||||
- /add and /drop always use paths relative to the git root
|
||||
- Encourage GPT to use language like "add files to the chat" to ask users for permission to edit them.
|
||||
|
||||
### v0.10.0
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.10.1-dev"
|
||||
__version__ = "0.10.2-dev"
|
||||
|
|
|
@ -99,6 +99,7 @@ class Coder:
|
|||
main_model,
|
||||
io,
|
||||
fnames=None,
|
||||
git_dname=None,
|
||||
pretty=True,
|
||||
show_diffs=False,
|
||||
auto_commits=True,
|
||||
|
@ -156,11 +157,14 @@ class Coder:
|
|||
fname.parent.mkdir(parents=True, exist_ok=True)
|
||||
fname.touch()
|
||||
|
||||
if not fname.is_file():
|
||||
raise ValueError(f"{fname} is not a file")
|
||||
|
||||
self.abs_fnames.add(str(fname.resolve()))
|
||||
|
||||
if use_git:
|
||||
try:
|
||||
self.repo = GitRepo(self.io, fnames)
|
||||
self.repo = GitRepo(self.io, fnames, git_dname)
|
||||
self.root = self.repo.root
|
||||
except FileNotFoundError:
|
||||
self.repo = None
|
||||
|
@ -198,7 +202,7 @@ class Coder:
|
|||
self.io.tool_output(f"Added {fname} to the chat.")
|
||||
|
||||
if self.repo:
|
||||
self.repo.add_new_files(fnames)
|
||||
self.repo.add_new_files(fname for fname in fnames if not Path(fname).is_dir())
|
||||
|
||||
self.summarizer = ChatSummary(self.main_model.name)
|
||||
|
||||
|
|
|
@ -9,10 +9,14 @@ import openai
|
|||
from aider import __version__, models
|
||||
from aider.coders import Coder
|
||||
from aider.io import InputOutput
|
||||
from aider.repo import GitRepo
|
||||
from aider.versioncheck import check_version
|
||||
|
||||
from .dump import dump # noqa: F402
|
||||
|
||||
|
||||
def get_git_root():
|
||||
"""Try and guess the git repo, since the conf.yml can be at the repo root"""
|
||||
try:
|
||||
repo = git.Repo(search_parent_directories=True)
|
||||
return repo.working_tree_dir
|
||||
|
@ -20,6 +24,33 @@ def get_git_root():
|
|||
return None
|
||||
|
||||
|
||||
def guessed_wrong_repo(io, git_root, fnames, git_dname):
|
||||
"""After we parse the args, we can determine the real repo. Did we guess wrong?"""
|
||||
|
||||
dump(git_root)
|
||||
|
||||
try:
|
||||
check_repo = Path(GitRepo(io, fnames, git_dname).root).resolve()
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
dump(check_repo)
|
||||
|
||||
# we had no guess, rely on the "true" repo result
|
||||
if not git_root:
|
||||
return str(check_repo)
|
||||
|
||||
git_root = Path(git_root).resolve()
|
||||
if check_repo == git_root:
|
||||
return
|
||||
|
||||
print("guessed the wrong repo")
|
||||
dump(check_repo)
|
||||
dump(git_root)
|
||||
|
||||
return str(check_repo)
|
||||
|
||||
|
||||
def setup_git(git_root, io):
|
||||
if git_root:
|
||||
return git_root
|
||||
|
@ -71,10 +102,13 @@ def check_gitignore(git_root, io, ask=True):
|
|||
io.tool_output(f"Added {pat} to .gitignore")
|
||||
|
||||
|
||||
def main(args=None, input=None, output=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
def main(argv=None, input=None, output=None, force_git_root=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
if force_git_root:
|
||||
git_root = force_git_root
|
||||
else:
|
||||
git_root = get_git_root()
|
||||
|
||||
conf_fname = Path(".aider.conf.yml")
|
||||
|
@ -101,7 +135,7 @@ def main(args=None, input=None, output=None):
|
|||
"files",
|
||||
metavar="FILE",
|
||||
nargs="*",
|
||||
help="a list of source code files to edit with GPT (optional)",
|
||||
help="the directory of a git repo, or a list of files to edit with GPT (optional)",
|
||||
)
|
||||
core_group.add_argument(
|
||||
"--openai-api-key",
|
||||
|
@ -344,7 +378,7 @@ def main(args=None, input=None, output=None):
|
|||
),
|
||||
)
|
||||
|
||||
args = parser.parse_args(args)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.dark_mode:
|
||||
args.user_input_color = "#32FF32"
|
||||
|
@ -371,6 +405,38 @@ def main(args=None, input=None, output=None):
|
|||
dry_run=args.dry_run,
|
||||
)
|
||||
|
||||
fnames = [str(Path(fn).resolve()) for fn in args.files]
|
||||
if len(args.files) > 1:
|
||||
good = True
|
||||
for fname in args.files:
|
||||
if Path(fname).is_dir():
|
||||
io.tool_error(f"{fname} is a directory, not provided alone.")
|
||||
good = False
|
||||
if not good:
|
||||
io.tool_error(
|
||||
"Provide either a single directory of a git repo, or a list of one or more files."
|
||||
)
|
||||
return 1
|
||||
|
||||
git_dname = None
|
||||
if len(args.files) == 1:
|
||||
if Path(args.files[0]).is_dir():
|
||||
if args.git:
|
||||
git_dname = str(Path(args.files[0]).resolve())
|
||||
fnames = []
|
||||
else:
|
||||
io.tool_error(f"{args.files[0]} is a directory, but --no-git selected.")
|
||||
return 1
|
||||
|
||||
# We can't know the git repo for sure until after parsing the args.
|
||||
# If we guessed wrong, reparse because that changes things like
|
||||
# the location of the config.yml and history files.
|
||||
if args.git and not force_git_root:
|
||||
right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname)
|
||||
if right_repo_root:
|
||||
print("guessed wrong")
|
||||
return main(argv, input, output, right_repo_root)
|
||||
|
||||
io.tool_output(f"Aider v{__version__}")
|
||||
|
||||
check_version(io.tool_error)
|
||||
|
@ -418,12 +484,14 @@ def main(args=None, input=None, output=None):
|
|||
setattr(openai, mod_key, val)
|
||||
io.tool_output(f"Setting openai.{mod_key}={val}")
|
||||
|
||||
try:
|
||||
coder = Coder.create(
|
||||
main_model,
|
||||
args.edit_format,
|
||||
io,
|
||||
##
|
||||
fnames=args.files,
|
||||
fnames=fnames,
|
||||
git_dname=git_dname,
|
||||
pretty=args.pretty,
|
||||
show_diffs=args.show_diffs,
|
||||
auto_commits=args.auto_commits,
|
||||
|
@ -436,6 +504,9 @@ def main(args=None, input=None, output=None):
|
|||
stream=args.stream,
|
||||
use_git=args.git,
|
||||
)
|
||||
except ValueError as err:
|
||||
io.tool_error(str(err))
|
||||
return 1
|
||||
|
||||
if args.show_repo_map:
|
||||
repo_map = coder.get_repo_map()
|
||||
|
|
|
@ -12,10 +12,12 @@ from .dump import dump # noqa: F401
|
|||
class GitRepo:
|
||||
repo = None
|
||||
|
||||
def __init__(self, io, fnames):
|
||||
def __init__(self, io, fnames, git_dname):
|
||||
self.io = io
|
||||
|
||||
if fnames:
|
||||
if git_dname:
|
||||
check_fnames = [git_dname]
|
||||
elif fnames:
|
||||
check_fnames = fnames
|
||||
else:
|
||||
check_fnames = ["."]
|
||||
|
@ -25,6 +27,9 @@ class GitRepo:
|
|||
fname = Path(fname)
|
||||
fname = fname.resolve()
|
||||
|
||||
if not fname.exists() and fname.parent.exists():
|
||||
fname = fname.parent
|
||||
|
||||
try:
|
||||
repo_path = git.Repo(fname, search_parent_directories=True).working_dir
|
||||
repo_path = utils.safe_abs_path(repo_path)
|
||||
|
@ -49,6 +54,8 @@ class GitRepo:
|
|||
for fname in fnames:
|
||||
if Path(fname).resolve() in cur_files:
|
||||
continue
|
||||
if not Path(fname).exists():
|
||||
continue
|
||||
self.io.tool_output(f"Adding {fname} to git")
|
||||
self.repo.git.add(fname)
|
||||
|
||||
|
@ -147,13 +154,20 @@ class GitRepo:
|
|||
try:
|
||||
commit = self.repo.head.commit
|
||||
except ValueError:
|
||||
return set()
|
||||
commit = None
|
||||
|
||||
files = []
|
||||
if commit:
|
||||
for blob in commit.tree.traverse():
|
||||
if blob.type == "blob": # blob is a file
|
||||
files.append(blob.path)
|
||||
|
||||
# Add staged files
|
||||
index = self.repo.index
|
||||
staged_files = [path for path, _ in index.entries.keys()]
|
||||
|
||||
files.extend(staged_files)
|
||||
|
||||
# convert to appropriate os.sep, since git always normalizes to /
|
||||
res = set(str(Path(PurePosixPath(path))) for path in files)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ def main():
|
|||
with open("aider/__init__.py", "r") as f:
|
||||
content = f.read()
|
||||
|
||||
current_version = re.search(r'__version__ = "(.+?)"', content).group(1).split("-dev")[0]
|
||||
current_version = re.search(r'__version__ = "(.+?)"', content).group(1)
|
||||
if new_version <= version.parse(current_version):
|
||||
raise ValueError(
|
||||
f"New version {new_version} must be greater than the current version {current_version}"
|
||||
|
|
|
@ -44,6 +44,33 @@ class TestMain(TestCase):
|
|||
main(["--yes", "foo.txt"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertTrue(os.path.exists("foo.txt"))
|
||||
|
||||
@patch("aider.repo.GitRepo.get_commit_message", return_value="mock commit message")
|
||||
def test_main_with_empty_git_dir_new_files(self, _):
|
||||
make_repo()
|
||||
main(["--yes", "foo.txt", "bar.txt"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertTrue(os.path.exists("foo.txt"))
|
||||
self.assertTrue(os.path.exists("bar.txt"))
|
||||
|
||||
def test_main_with_dname_and_fname(self):
|
||||
subdir = Path("subdir")
|
||||
subdir.mkdir()
|
||||
make_repo(str(subdir))
|
||||
res = main(["subdir", "foo.txt"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertNotEqual(res, None)
|
||||
|
||||
@patch("aider.repo.GitRepo.get_commit_message", return_value="mock commit message")
|
||||
def test_main_with_subdir_repo_fnames(self, _):
|
||||
subdir = Path("subdir")
|
||||
subdir.mkdir()
|
||||
make_repo(str(subdir))
|
||||
main(
|
||||
["--yes", str(subdir / "foo.txt"), str(subdir / "bar.txt")],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
)
|
||||
self.assertTrue((subdir / "foo.txt").exists())
|
||||
self.assertTrue((subdir / "bar.txt").exists())
|
||||
|
||||
def test_main_with_git_config_yml(self):
|
||||
make_repo()
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import git
|
|||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.repo import GitRepo
|
||||
from tests.utils import GitTemporaryDirectory
|
||||
|
||||
|
||||
class TestRepo(unittest.TestCase):
|
||||
|
@ -16,7 +17,7 @@ class TestRepo(unittest.TestCase):
|
|||
def test_get_commit_message(self, mock_send):
|
||||
mock_send.return_value = "a good commit message"
|
||||
|
||||
repo = GitRepo(InputOutput(), None)
|
||||
repo = GitRepo(InputOutput(), None, None)
|
||||
# Call the get_commit_message method with dummy diff and context
|
||||
result = repo.get_commit_message("dummy diff", "dummy context")
|
||||
|
||||
|
@ -27,7 +28,7 @@ class TestRepo(unittest.TestCase):
|
|||
def test_get_commit_message_strip_quotes(self, mock_send):
|
||||
mock_send.return_value = '"a good commit message"'
|
||||
|
||||
repo = GitRepo(InputOutput(), None)
|
||||
repo = GitRepo(InputOutput(), None, None)
|
||||
# Call the get_commit_message method with dummy diff and context
|
||||
result = repo.get_commit_message("dummy diff", "dummy context")
|
||||
|
||||
|
@ -38,7 +39,7 @@ class TestRepo(unittest.TestCase):
|
|||
def test_get_commit_message_no_strip_unmatched_quotes(self, mock_send):
|
||||
mock_send.return_value = 'a good "commit message"'
|
||||
|
||||
repo = GitRepo(InputOutput(), None)
|
||||
repo = GitRepo(InputOutput(), None, None)
|
||||
# Call the get_commit_message method with dummy diff and context
|
||||
result = repo.get_commit_message("dummy diff", "dummy context")
|
||||
|
||||
|
@ -73,10 +74,41 @@ class TestRepo(unittest.TestCase):
|
|||
|
||||
repo.git.commit("-m", "added")
|
||||
|
||||
tracked_files = GitRepo(InputOutput(), [tempdir]).get_tracked_files()
|
||||
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)
|
||||
|
|
|
@ -42,7 +42,11 @@ class GitTemporaryDirectory(ChdirTemporaryDirectory):
|
|||
return res
|
||||
|
||||
|
||||
def make_repo():
|
||||
repo = git.Repo.init()
|
||||
def make_repo(path=None):
|
||||
if not path:
|
||||
path = "."
|
||||
repo = git.Repo.init(path)
|
||||
repo.config_writer().set_value("user", "name", "Test User").release()
|
||||
repo.config_writer().set_value("user", "email", "testuser@example.com").release()
|
||||
|
||||
return repo
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue