From e34e6ff897bad7ce352ea71eae85ecc3e9836470 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Fri, 21 Jul 2023 11:14:15 -0300 Subject: [PATCH] wip --- aider/coders/base_coder.py | 211 ------------------------------------ aider/repo.py | 217 +++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 211 deletions(-) create mode 100644 aider/repo.py diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 14e076764..b9d48cd62 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -206,12 +206,6 @@ class Coder: 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): self.abs_fnames.add(self.abs_root_path(rel_fname)) @@ -219,72 +213,6 @@ class Coder: res = Path(self.root) / path 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 = [ ("``" + "`", "``" + "`"), @@ -797,145 +725,6 @@ class Coder: def render_incremental_response(self, final): 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): return os.path.relpath(fname, self.root) diff --git a/aider/repo.py b/aider/repo.py new file mode 100644 index 000000000..ca05a721c --- /dev/null +++ b/aider/repo.py @@ -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