from pathlib import Path
import shutil
from typing import Final
from autocommit.config import Config
from autocommit.utils import get_api_key
autocommit_storage_dir: Final[Path] = Path(".autocommit_storage_dir")
prepare_commit_msg_hook: Final[str] = "exec autocommit git_prepare_commit_msg \"$@\""
post_commit_hook: Final[str] = "exec autocommit git_post_commit"
[docs]
def add_storage_dir_to_exclude(gitdir: Path):
"""Add .autocommit_storage_dir/ to the .git/info/exclude file
unless it is already there. This serves to avoid tracking the storage directory,
which contains the api key.
"""
pattern = f"{autocommit_storage_dir}/"
exclude_path = gitdir / "info" / "exclude"
pattern_already_there = pattern in [line.strip() for line
in exclude_path.read_text().splitlines()]
if pattern_already_there:
return
with exclude_path.open("a") as f:
f.write(pattern + "\n")
[docs]
def add_key_to_tree(key, worktree):
"""Adds the api key to the worktree"""
storage_dir = worktree / autocommit_storage_dir
storage_dir.mkdir(exist_ok=True)
storage_dir.chmod(0o700)
key_file = storage_dir / "api_key"
key_file.write_text(key)
key_file.chmod(0o600)
return key_file
[docs]
def check_repo_bare(repo: Path):
"""Checks if the repo is bare"""
return not (repo / ".git").is_dir()
[docs]
def add_commit_hook(repo: Path, *, hook_name="prepare-commit-msg", hook_content):
"""Adds the commit hook to the repository"""
hook_path = repo / "hooks" / hook_name
if not hook_path.exists():
hook_path.write_text(f"#!/bin/sh\n{hook_content}\n")
hook_path.chmod(0o755)
else:
lines = hook_path.read_text().splitlines()
if hook_content in lines:
return
with hook_path.open("a") as f:
f.write("\n" + hook_content + "\n")
[docs]
def remove_commit_hook(repo: Path, *, hook_name="prepare-commit-msg", hook_content):
"""Removes the commit hook from the repository"""
hook_path = repo / "hooks" / hook_name
import pdb; pdb.set_trace()
if not hook_path.exists():
return
lines = hook_path.read_text().splitlines()
if hook_content not in lines:
return
lines.remove(hook_content)
hook_path.write_text("\n".join(lines) + "\n")
[docs]
def get_repo_worktree(repo: Path, worktree: Path|None = None):
"""Gets the repo and worktree paths"""
if worktree is not None and not check_repo_bare(repo):
raise ValueError("Cannot specify a worktree for a non-bare repository")
if worktree is None:
if check_repo_bare(repo):
raise ValueError("Cannot run setup on a bare repository"
" without specifying a worktree")
worktree = repo
repo = repo / ".git"
assert worktree is not None
return repo, worktree
[docs]
def run_setup(repo, isolation: bool, key=None, worktree: Path|None =None,
enable_rag: bool = True, enable_function_calls: bool = False):
"""Runs the setup for the repository"""
config = Config(
enable_rag=enable_rag,
isolation=isolation,
enable_function_calls=enable_function_calls,
debug=False, # must be manually enabled by editing the config file
)
key = get_api_key(key)
if isolation:
raise NotImplementedError("Isolation is not implemented yet")
repo, worktree = get_repo_worktree(repo, worktree)
dir = worktree / autocommit_storage_dir
dir.mkdir(exist_ok=True)
config.to_json_file(dir / "config.json")
add_storage_dir_to_exclude(repo)
_= add_key_to_tree(key, worktree)
# git hooks
autocommit_line = 'exec autocommit git_prepare_commit_msg \"$@\"'
add_commit_hook(repo, hook_name="prepare-commit-msg",
hook_content=autocommit_line)
# build ragdb
if enable_rag:
from autocommit.build_ragdb import build_ragdb
add_commit_hook(repo, hook_name="post-commit",
hook_content=post_commit_hook)
build_ragdb(key, str(worktree), update=False)
[docs]
def run_cleanup(repo: Path, worktree: Path|None = None):
"""Cleans up the autocommit setup"""
repo, worktree = get_repo_worktree(repo, worktree)
dir = worktree / autocommit_storage_dir
if dir.exists():
shutil.rmtree(dir)
remove_commit_hook(repo, hook_name="prepare-commit-msg",
hook_content=prepare_commit_msg_hook)
remove_commit_hook(repo, hook_name="post-commit",
hook_content=post_commit_hook)