mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-24 22:34:59 +00:00
wip
This commit is contained in:
parent
6d044485f3
commit
e34e6ff897
2 changed files with 217 additions and 211 deletions
|
@ -206,12 +206,6 @@ class Coder:
|
||||||
|
|
||||||
self.root = utils.safe_abs_path(self.root)
|
self.root = utils.safe_abs_path(self.root)
|
||||||
|
|
||||||
def get_rel_repo_dir(self):
|
|
||||||
try:
|
|
||||||
return os.path.relpath(self.repo.git_dir, os.getcwd())
|
|
||||||
except ValueError:
|
|
||||||
return self.repo.git_dir
|
|
||||||
|
|
||||||
def add_rel_fname(self, rel_fname):
|
def add_rel_fname(self, rel_fname):
|
||||||
self.abs_fnames.add(self.abs_root_path(rel_fname))
|
self.abs_fnames.add(self.abs_root_path(rel_fname))
|
||||||
|
|
||||||
|
@ -219,72 +213,6 @@ class Coder:
|
||||||
res = Path(self.root) / path
|
res = Path(self.root) / path
|
||||||
return utils.safe_abs_path(res)
|
return utils.safe_abs_path(res)
|
||||||
|
|
||||||
def set_repo(self, cmd_line_fnames):
|
|
||||||
if not cmd_line_fnames:
|
|
||||||
cmd_line_fnames = ["."]
|
|
||||||
|
|
||||||
repo_paths = []
|
|
||||||
for fname in cmd_line_fnames:
|
|
||||||
fname = Path(fname)
|
|
||||||
if not fname.exists():
|
|
||||||
self.io.tool_output(f"Creating empty file {fname}")
|
|
||||||
fname.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
fname.touch()
|
|
||||||
|
|
||||||
fname = fname.resolve()
|
|
||||||
|
|
||||||
try:
|
|
||||||
repo_path = git.Repo(fname, search_parent_directories=True).working_dir
|
|
||||||
repo_path = utils.safe_abs_path(repo_path)
|
|
||||||
repo_paths.append(repo_path)
|
|
||||||
except git.exc.InvalidGitRepositoryError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if fname.is_dir():
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.abs_fnames.add(str(fname))
|
|
||||||
|
|
||||||
num_repos = len(set(repo_paths))
|
|
||||||
|
|
||||||
if num_repos == 0:
|
|
||||||
return
|
|
||||||
if num_repos > 1:
|
|
||||||
self.io.tool_error("Files are in different git repos.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# https://github.com/gitpython-developers/GitPython/issues/427
|
|
||||||
self.repo = git.Repo(repo_paths.pop(), odbt=git.GitDB)
|
|
||||||
|
|
||||||
self.root = utils.safe_abs_path(self.repo.working_tree_dir)
|
|
||||||
|
|
||||||
new_files = []
|
|
||||||
for fname in self.abs_fnames:
|
|
||||||
relative_fname = self.get_rel_fname(fname)
|
|
||||||
|
|
||||||
tracked_files = set(self.get_tracked_files())
|
|
||||||
if relative_fname not in tracked_files:
|
|
||||||
new_files.append(relative_fname)
|
|
||||||
|
|
||||||
if new_files:
|
|
||||||
rel_repo_dir = self.get_rel_repo_dir()
|
|
||||||
|
|
||||||
self.io.tool_output(f"Files not tracked in {rel_repo_dir}:")
|
|
||||||
for fn in new_files:
|
|
||||||
self.io.tool_output(f" - {fn}")
|
|
||||||
if self.io.confirm_ask("Add them?"):
|
|
||||||
for relative_fname in new_files:
|
|
||||||
self.repo.git.add(relative_fname)
|
|
||||||
self.io.tool_output(f"Added {relative_fname} to the git repo")
|
|
||||||
show_files = ", ".join(new_files)
|
|
||||||
commit_message = f"Added new files to the git repo: {show_files}"
|
|
||||||
self.repo.git.commit("-m", commit_message, "--no-verify")
|
|
||||||
commit_hash = self.repo.head.commit.hexsha[:7]
|
|
||||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
|
||||||
else:
|
|
||||||
self.io.tool_error("Skipped adding new files to the git repo.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# fences are obfuscated so aider can modify this file!
|
# fences are obfuscated so aider can modify this file!
|
||||||
fences = [
|
fences = [
|
||||||
("``" + "`", "``" + "`"),
|
("``" + "`", "``" + "`"),
|
||||||
|
@ -797,145 +725,6 @@ class Coder:
|
||||||
def render_incremental_response(self, final):
|
def render_incremental_response(self, final):
|
||||||
return self.partial_response_content
|
return self.partial_response_content
|
||||||
|
|
||||||
def get_context_from_history(self, history):
|
|
||||||
context = ""
|
|
||||||
if history:
|
|
||||||
for msg in history:
|
|
||||||
context += "\n" + msg["role"].upper() + ": " + msg["content"] + "\n"
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_commit_message(self, diffs, context):
|
|
||||||
if len(diffs) >= 4 * 1024 * 4:
|
|
||||||
self.io.tool_error(
|
|
||||||
f"Diff is too large for {models.GPT35.name} to generate a commit message."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
diffs = "# Diffs:\n" + diffs
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
dict(role="system", content=prompts.commit_system),
|
|
||||||
dict(role="user", content=context + diffs),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
interrupted = self.send(
|
|
||||||
messages,
|
|
||||||
model=models.GPT35.name,
|
|
||||||
silent=True,
|
|
||||||
)
|
|
||||||
except openai.error.InvalidRequestError:
|
|
||||||
self.io.tool_error(
|
|
||||||
f"Failed to generate commit message using {models.GPT35.name} due to an invalid"
|
|
||||||
" request."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
commit_message = self.partial_response_content
|
|
||||||
commit_message = commit_message.strip()
|
|
||||||
if commit_message and commit_message[0] == '"' and commit_message[-1] == '"':
|
|
||||||
commit_message = commit_message[1:-1].strip()
|
|
||||||
|
|
||||||
if interrupted:
|
|
||||||
self.io.tool_error(
|
|
||||||
f"Unable to get commit message from {models.GPT35.name}. Use /commit to try again."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
return commit_message
|
|
||||||
|
|
||||||
def get_diffs(self, *args):
|
|
||||||
if self.pretty:
|
|
||||||
args = ["--color"] + list(args)
|
|
||||||
|
|
||||||
diffs = self.repo.git.diff(*args)
|
|
||||||
return diffs
|
|
||||||
|
|
||||||
def commit(self, history=None, prefix=None, ask=False, message=None, which="chat_files"):
|
|
||||||
repo = self.repo
|
|
||||||
if not repo:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not repo.is_dirty():
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_dirty_files_and_diffs(file_list):
|
|
||||||
diffs = ""
|
|
||||||
relative_dirty_files = []
|
|
||||||
for fname in file_list:
|
|
||||||
relative_fname = self.get_rel_fname(fname)
|
|
||||||
relative_dirty_files.append(relative_fname)
|
|
||||||
|
|
||||||
try:
|
|
||||||
current_branch_commit_count = len(
|
|
||||||
list(self.repo.iter_commits(self.repo.active_branch))
|
|
||||||
)
|
|
||||||
except git.exc.GitCommandError:
|
|
||||||
current_branch_commit_count = None
|
|
||||||
|
|
||||||
if not current_branch_commit_count:
|
|
||||||
continue
|
|
||||||
|
|
||||||
these_diffs = self.get_diffs("HEAD", "--", relative_fname)
|
|
||||||
|
|
||||||
if these_diffs:
|
|
||||||
diffs += these_diffs + "\n"
|
|
||||||
|
|
||||||
return relative_dirty_files, diffs
|
|
||||||
|
|
||||||
if which == "repo_files":
|
|
||||||
all_files = [os.path.join(self.root, f) for f in self.get_all_relative_files()]
|
|
||||||
relative_dirty_fnames, diffs = get_dirty_files_and_diffs(all_files)
|
|
||||||
elif which == "chat_files":
|
|
||||||
relative_dirty_fnames, diffs = get_dirty_files_and_diffs(self.abs_fnames)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Invalid value for 'which': {which}")
|
|
||||||
|
|
||||||
if self.show_diffs or ask:
|
|
||||||
# don't use io.tool_output() because we don't want to log or further colorize
|
|
||||||
print(diffs)
|
|
||||||
|
|
||||||
context = self.get_context_from_history(history)
|
|
||||||
if message:
|
|
||||||
commit_message = message
|
|
||||||
else:
|
|
||||||
commit_message = self.get_commit_message(diffs, context)
|
|
||||||
|
|
||||||
if not commit_message:
|
|
||||||
commit_message = "work in progress"
|
|
||||||
|
|
||||||
if prefix:
|
|
||||||
commit_message = prefix + commit_message
|
|
||||||
|
|
||||||
if ask:
|
|
||||||
if which == "repo_files":
|
|
||||||
self.io.tool_output("Git repo has uncommitted changes.")
|
|
||||||
else:
|
|
||||||
self.io.tool_output("Files have uncommitted changes.")
|
|
||||||
|
|
||||||
res = self.io.prompt_ask(
|
|
||||||
"Commit before the chat proceeds [y/n/commit message]?",
|
|
||||||
default=commit_message,
|
|
||||||
).strip()
|
|
||||||
self.last_asked_for_commit_time = self.get_last_modified()
|
|
||||||
|
|
||||||
self.io.tool_output()
|
|
||||||
|
|
||||||
if res.lower() in ["n", "no"]:
|
|
||||||
self.io.tool_error("Skipped commmit.")
|
|
||||||
return
|
|
||||||
if res.lower() not in ["y", "yes"] and res:
|
|
||||||
commit_message = res
|
|
||||||
|
|
||||||
repo.git.add(*relative_dirty_fnames)
|
|
||||||
|
|
||||||
full_commit_message = commit_message + "\n\n# Aider chat conversation:\n\n" + context
|
|
||||||
repo.git.commit("-m", full_commit_message, "--no-verify")
|
|
||||||
commit_hash = repo.head.commit.hexsha[:7]
|
|
||||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
|
||||||
|
|
||||||
return commit_hash, commit_message
|
|
||||||
|
|
||||||
def get_rel_fname(self, fname):
|
def get_rel_fname(self, fname):
|
||||||
return os.path.relpath(fname, self.root)
|
return os.path.relpath(fname, self.root)
|
||||||
|
|
||||||
|
|
217
aider/repo.py
Normal file
217
aider/repo.py
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
import git
|
||||||
|
|
||||||
|
|
||||||
|
class AiderRepo:
|
||||||
|
repo = None
|
||||||
|
|
||||||
|
def __init__(self, io, cmd_line_fnames):
|
||||||
|
self.io = io
|
||||||
|
|
||||||
|
if not cmd_line_fnames:
|
||||||
|
cmd_line_fnames = ["."]
|
||||||
|
|
||||||
|
repo_paths = []
|
||||||
|
for fname in cmd_line_fnames:
|
||||||
|
fname = Path(fname)
|
||||||
|
if not fname.exists():
|
||||||
|
self.io.tool_output(f"Creating empty file {fname}")
|
||||||
|
fname.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
fname.touch()
|
||||||
|
|
||||||
|
fname = fname.resolve()
|
||||||
|
|
||||||
|
try:
|
||||||
|
repo_path = git.Repo(fname, search_parent_directories=True).working_dir
|
||||||
|
repo_path = utils.safe_abs_path(repo_path)
|
||||||
|
repo_paths.append(repo_path)
|
||||||
|
except git.exc.InvalidGitRepositoryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if fname.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.abs_fnames.add(str(fname))
|
||||||
|
|
||||||
|
num_repos = len(set(repo_paths))
|
||||||
|
|
||||||
|
if num_repos == 0:
|
||||||
|
return
|
||||||
|
if num_repos > 1:
|
||||||
|
self.io.tool_error("Files are in different git repos.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# https://github.com/gitpython-developers/GitPython/issues/427
|
||||||
|
self.repo = git.Repo(repo_paths.pop(), odbt=git.GitDB)
|
||||||
|
|
||||||
|
self.root = utils.safe_abs_path(self.repo.working_tree_dir)
|
||||||
|
|
||||||
|
new_files = []
|
||||||
|
for fname in self.abs_fnames:
|
||||||
|
relative_fname = self.get_rel_fname(fname)
|
||||||
|
|
||||||
|
tracked_files = set(self.get_tracked_files())
|
||||||
|
if relative_fname not in tracked_files:
|
||||||
|
new_files.append(relative_fname)
|
||||||
|
|
||||||
|
if new_files:
|
||||||
|
rel_repo_dir = self.get_rel_repo_dir()
|
||||||
|
|
||||||
|
self.io.tool_output(f"Files not tracked in {rel_repo_dir}:")
|
||||||
|
for fn in new_files:
|
||||||
|
self.io.tool_output(f" - {fn}")
|
||||||
|
if self.io.confirm_ask("Add them?"):
|
||||||
|
for relative_fname in new_files:
|
||||||
|
self.repo.git.add(relative_fname)
|
||||||
|
self.io.tool_output(f"Added {relative_fname} to the git repo")
|
||||||
|
show_files = ", ".join(new_files)
|
||||||
|
commit_message = f"Added new files to the git repo: {show_files}"
|
||||||
|
self.repo.git.commit("-m", commit_message, "--no-verify")
|
||||||
|
commit_hash = self.repo.head.commit.hexsha[:7]
|
||||||
|
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
||||||
|
else:
|
||||||
|
self.io.tool_error("Skipped adding new files to the git repo.")
|
||||||
|
|
||||||
|
def commit(self, history=None, prefix=None, ask=False, message=None, which="chat_files"):
|
||||||
|
repo = self.repo
|
||||||
|
if not repo:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not repo.is_dirty():
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_dirty_files_and_diffs(file_list):
|
||||||
|
diffs = ""
|
||||||
|
relative_dirty_files = []
|
||||||
|
for fname in file_list:
|
||||||
|
relative_fname = self.get_rel_fname(fname)
|
||||||
|
relative_dirty_files.append(relative_fname)
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_branch_commit_count = len(
|
||||||
|
list(self.repo.iter_commits(self.repo.active_branch))
|
||||||
|
)
|
||||||
|
except git.exc.GitCommandError:
|
||||||
|
current_branch_commit_count = None
|
||||||
|
|
||||||
|
if not current_branch_commit_count:
|
||||||
|
continue
|
||||||
|
|
||||||
|
these_diffs = self.get_diffs("HEAD", "--", relative_fname)
|
||||||
|
|
||||||
|
if these_diffs:
|
||||||
|
diffs += these_diffs + "\n"
|
||||||
|
|
||||||
|
return relative_dirty_files, diffs
|
||||||
|
|
||||||
|
if which == "repo_files":
|
||||||
|
all_files = [os.path.join(self.root, f) for f in self.get_all_relative_files()]
|
||||||
|
relative_dirty_fnames, diffs = get_dirty_files_and_diffs(all_files)
|
||||||
|
elif which == "chat_files":
|
||||||
|
relative_dirty_fnames, diffs = get_dirty_files_and_diffs(self.abs_fnames)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid value for 'which': {which}")
|
||||||
|
|
||||||
|
if self.show_diffs or ask:
|
||||||
|
# don't use io.tool_output() because we don't want to log or further colorize
|
||||||
|
print(diffs)
|
||||||
|
|
||||||
|
context = self.get_context_from_history(history)
|
||||||
|
if message:
|
||||||
|
commit_message = message
|
||||||
|
else:
|
||||||
|
commit_message = self.get_commit_message(diffs, context)
|
||||||
|
|
||||||
|
if not commit_message:
|
||||||
|
commit_message = "work in progress"
|
||||||
|
|
||||||
|
if prefix:
|
||||||
|
commit_message = prefix + commit_message
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
if which == "repo_files":
|
||||||
|
self.io.tool_output("Git repo has uncommitted changes.")
|
||||||
|
else:
|
||||||
|
self.io.tool_output("Files have uncommitted changes.")
|
||||||
|
|
||||||
|
res = self.io.prompt_ask(
|
||||||
|
"Commit before the chat proceeds [y/n/commit message]?",
|
||||||
|
default=commit_message,
|
||||||
|
).strip()
|
||||||
|
self.last_asked_for_commit_time = self.get_last_modified()
|
||||||
|
|
||||||
|
self.io.tool_output()
|
||||||
|
|
||||||
|
if res.lower() in ["n", "no"]:
|
||||||
|
self.io.tool_error("Skipped commmit.")
|
||||||
|
return
|
||||||
|
if res.lower() not in ["y", "yes"] and res:
|
||||||
|
commit_message = res
|
||||||
|
|
||||||
|
repo.git.add(*relative_dirty_fnames)
|
||||||
|
|
||||||
|
full_commit_message = commit_message + "\n\n# Aider chat conversation:\n\n" + context
|
||||||
|
repo.git.commit("-m", full_commit_message, "--no-verify")
|
||||||
|
commit_hash = repo.head.commit.hexsha[:7]
|
||||||
|
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
||||||
|
|
||||||
|
return commit_hash, commit_message
|
||||||
|
|
||||||
|
def get_rel_repo_dir(self):
|
||||||
|
try:
|
||||||
|
return os.path.relpath(self.repo.git_dir, os.getcwd())
|
||||||
|
except ValueError:
|
||||||
|
return self.repo.git_dir
|
||||||
|
|
||||||
|
def get_context_from_history(self, history):
|
||||||
|
context = ""
|
||||||
|
if history:
|
||||||
|
for msg in history:
|
||||||
|
context += "\n" + msg["role"].upper() + ": " + msg["content"] + "\n"
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_commit_message(self, diffs, context):
|
||||||
|
if len(diffs) >= 4 * 1024 * 4:
|
||||||
|
self.io.tool_error(
|
||||||
|
f"Diff is too large for {models.GPT35.name} to generate a commit message."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
diffs = "# Diffs:\n" + diffs
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
dict(role="system", content=prompts.commit_system),
|
||||||
|
dict(role="user", content=context + diffs),
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
interrupted = self.send(
|
||||||
|
messages,
|
||||||
|
model=models.GPT35.name,
|
||||||
|
silent=True,
|
||||||
|
)
|
||||||
|
except openai.error.InvalidRequestError:
|
||||||
|
self.io.tool_error(
|
||||||
|
f"Failed to generate commit message using {models.GPT35.name} due to an invalid"
|
||||||
|
" request."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
commit_message = self.partial_response_content
|
||||||
|
commit_message = commit_message.strip()
|
||||||
|
if commit_message and commit_message[0] == '"' and commit_message[-1] == '"':
|
||||||
|
commit_message = commit_message[1:-1].strip()
|
||||||
|
|
||||||
|
if interrupted:
|
||||||
|
self.io.tool_error(
|
||||||
|
f"Unable to get commit message from {models.GPT35.name}. Use /commit to try again."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
return commit_message
|
||||||
|
|
||||||
|
def get_diffs(self, *args):
|
||||||
|
if self.pretty:
|
||||||
|
args = ["--color"] + list(args)
|
||||||
|
|
||||||
|
diffs = self.repo.git.diff(*args)
|
||||||
|
return diffs
|
Loading…
Add table
Add a link
Reference in a new issue