Added DirectoryRepo as a GitRepo substitute, so /ls, /map etc. can be used on a dir with other version control systems (--no-git).

This commit is contained in:
Luke Rissacher 2025-03-03 08:48:54 -08:00
parent 0c5b51d2ac
commit 69d53c9783
4 changed files with 133 additions and 16 deletions

View file

@ -815,7 +815,7 @@ class Coder:
self.shell_commands = []
self.message_cost = 0
if self.repo:
if isinstance(self.repo, GitRepo):
self.commit_before_message.append(self.repo.get_head_commit_sha())
def run(self, with_message=None, preproc=True):
@ -1935,7 +1935,7 @@ class Coder:
return all_files - inchat_files - read_only_files
def check_for_dirty_commit(self, path):
if not self.repo:
if not isinstance(self.repo, GitRepo):
return
if not self.dirty_commits:
return
@ -2135,7 +2135,7 @@ class Coder:
return context
def auto_commit(self, edited, context=None):
if not self.repo or not self.auto_commits or self.dry_run:
if not isinstance(self.repo, GitRepo) or not self.auto_commits or self.dry_run:
return
if not context:
@ -2175,7 +2175,7 @@ class Coder:
return
if not self.dirty_commits:
return
if not self.repo:
if not isinstance(self.repo, GitRepo):
return
self.repo.commit(fnames=self.need_commit_before_edits)

View file

@ -18,7 +18,7 @@ from aider.editor import pipe_editor
from aider.format_settings import format_settings
from aider.help import Help, install_help_extra
from aider.llm import litellm
from aider.repo import ANY_GIT_ERROR
from aider.repo import ANY_GIT_ERROR, GitRepo
from aider.run_cmd import run_cmd
from aider.scrape import Scraper, install_playwright
from aider.utils import is_image_file
@ -282,7 +282,7 @@ class Commands:
self.io.tool_error(f"Unable to complete commit: {err}")
def raw_cmd_commit(self, args=None):
if not self.coder.repo:
if not isinstance(self.coder.repo, GitRepo):
self.io.tool_error("No git repository found.")
return
@ -296,7 +296,7 @@ class Commands:
def cmd_lint(self, args="", fnames=None):
"Lint and fix in-chat files or all dirty files if none in chat"
if not self.coder.repo:
if not isinstance(self.coder.repo, GitRepo):
self.io.tool_error("No git repository found.")
return
@ -483,7 +483,7 @@ class Commands:
self.io.tool_error(f"Unable to complete undo: {err}")
def raw_cmd_undo(self, args):
if not self.coder.repo:
if not isinstance(self.coder.repo, GitRepo):
self.io.tool_error("No git repository found.")
return
@ -585,7 +585,7 @@ class Commands:
self.io.tool_error(f"Unable to complete diff: {err}")
def raw_cmd_diff(self, args=""):
if not self.coder.repo:
if not isinstance(self.coder.repo, GitRepo):
self.io.tool_error("No git repository found.")
return

View file

@ -31,6 +31,7 @@ from aider.io import InputOutput
from aider.llm import litellm # noqa: F401; properly init litellm on launch
from aider.models import ModelSettings
from aider.repo import ANY_GIT_ERROR, GitRepo
from aider.repo_directory import DirectoryRepo
from aider.report import report_uncaught_exceptions
from aider.versioncheck import check_version, install_from_main_branch, install_upgrade
from aider.watch import FileWatcher
@ -686,13 +687,8 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
git_dname = None
if len(all_files) == 1:
if Path(all_files[0]).is_dir():
if args.git:
git_dname = str(Path(all_files[0]).resolve())
fnames = []
else:
io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.")
analytics.event("exit", reason="Directory with --no-git")
return 1
git_dname = str(Path(all_files[0]).resolve())
fnames = []
# We can't know the git repo for sure until after parsing the args.
# If we guessed wrong, reparse because that changes things like
@ -861,6 +857,12 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
else:
analytics.event("no-repo")
if not repo:
repo = DirectoryRepo(
git_dname,
args.aiderignore,
)
commands = Commands(
io,
None,

115
aider/repo_directory.py Normal file
View file

@ -0,0 +1,115 @@
import os
import time
import pathspec
from pathlib import Path, PurePosixPath
from aider import utils
class DirectoryRepo:
"""
A substitute for GitRepo; lets Aider run mostly full-featured without a git repo
(for instance, with another version-control tool like fossil).
"""
aider_ignore_file = None
aider_ignore_spec = None
aider_ignore_ts = 0
aider_ignore_last_check = 0
def __init__(
self,
git_dname,
aider_ignore_file=None,
):
self.normalized_path_cache = {}
self.ignore_file_cache = {}
self.root = utils.safe_abs_path(Path(git_dname or ".").resolve())
if aider_ignore_file:
self.aider_ignore_file = Path(aider_ignore_file)
def commit(self, fnames=None, context=None, message=None, aider_edits=False):
return
def get_rel_repo_dir(self):
return "non-git directory"
def get_tracked_files(self):
files = []
for dirpath, _, filenames in os.walk(self.root):
for filename in filenames:
file_path = self.normalize_path(os.path.join(dirpath, filename))
if self.ignored_file(file_path):
continue
files.append(file_path)
return files
def normalize_path(self, path):
orig_path = path
res = self.normalized_path_cache.get(orig_path)
if res:
return res
path = str(Path(PurePosixPath((Path(self.root) / path).relative_to(self.root))))
self.normalized_path_cache[orig_path] = path
return path
def refresh_aider_ignore(self):
if not self.aider_ignore_file:
return
current_time = time.time()
if current_time - self.aider_ignore_last_check < 1:
return
self.aider_ignore_last_check = current_time
if not self.aider_ignore_file.is_file():
return
mtime = self.aider_ignore_file.stat().st_mtime
if mtime != self.aider_ignore_ts:
self.aider_ignore_ts = mtime
self.ignore_file_cache = {}
lines = self.aider_ignore_file.read_text().splitlines()
self.aider_ignore_spec = pathspec.PathSpec.from_lines(
pathspec.patterns.GitWildMatchPattern,
lines,
)
def git_ignored_file(self, path):
return False
def ignored_file(self, fname):
self.refresh_aider_ignore()
if fname in self.ignore_file_cache:
return self.ignore_file_cache[fname]
is_ignored = self.ignored_file_raw(fname)
self.ignore_file_cache[fname] = is_ignored
return is_ignored
def ignored_file_raw(self, fname):
try:
fname = self.normalize_path(fname)
except ValueError:
return True
if '.aider' in str(fname):
return True
if not self.aider_ignore_file or not self.aider_ignore_file.is_file():
return False
return self.aider_ignore_spec.match_file(fname)
def path_in_repo(self, path):
if not path:
return
tracked_files = set(self.get_tracked_files())
return self.normalize_path(path) in tracked_files
def get_dirty_files(self):
return []
def is_dirty(self, path=None):
return False