diff --git a/src/codeguard/git/hooks.py b/src/codeguard/git/hooks.py new file mode 100644 index 0000000..aff3928 --- /dev/null +++ b/src/codeguard/git/hooks.py @@ -0,0 +1,76 @@ +"""Git hooks management for CodeGuard.""" + +import os +import stat +from pathlib import Path + + +class HookManager: + HOOK_SCRIPT = """#!/bin/bash +# CodeGuard pre-commit hook +# This hook runs CodeGuard security analysis before commit + +set -e + +# Get the root directory of the git repository +GIT_DIR=$(git rev-parse --git-dir) +REPO_ROOT=$(git rev-parse --show-toplevel) + +# Change to repository root +cd "$REPO_ROOT" + +# Run CodeGuard scan +# Exit with error code if scan fails or finds critical issues +exec codeguard scan --fail-level critical +""" + + def __init__(self, repo_path: str = "."): + self.repo_path = Path(repo_path).resolve() + self.git_dir = self.repo_path / ".git" + self.hooks_dir = self.git_dir / "hooks" + + def install(self, force: bool = False) -> bool: + if not self.git_dir.exists(): + raise NotAGitRepository(f"Not a git repository: {self.repo_path}") + + self.hooks_dir.mkdir(parents=True, exist_ok=True) + + hook_path = self.hooks_dir / "pre-commit" + + if hook_path.exists() and not force: + existing_content = hook_path.read_text() + if "codeguard" in existing_content.lower(): + return False + + hook_content = self._generate_hook_script() + hook_path.write_text(hook_content) + + os.chmod(hook_path, hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + return True + + def uninstall(self) -> None: + hook_path = self.hooks_dir / "pre-commit" + if hook_path.exists(): + hook_path.unlink() + + def _generate_hook_script(self) -> str: + return self.HOOK_SCRIPT + + def check_installed(self) -> bool: + hook_path = self.hooks_dir / "pre-commit" + if not hook_path.exists(): + return False + + content = hook_path.read_text() + return "codeguard" in content.lower() + + def get_hook_path(self) -> Path: + return self.hooks_dir / "pre-commit" + + +class NotAGitRepository(Exception): + pass + + +class HookAlreadyInstalled(Exception): + pass