From ad722845ba6ead915fe58166eae06eb626d13ab3 Mon Sep 17 00:00:00 2001 From: Guido Dinello Date: Tue, 17 Dec 2024 22:43:37 -0300 Subject: [PATCH 1/3] feat(dev): add hooks setup and pre-push hook with black formatter --- pyproject.toml | 4 + .../cli/hook_templates/pre_commit.py | 77 +++++++++++++++++++ src/claudesync/cli/hooks.py | 75 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 src/claudesync/cli/hook_templates/pre_commit.py create mode 100644 src/claudesync/cli/hooks.py diff --git a/pyproject.toml b/pyproject.toml index f2b9a8f..1000789 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,9 @@ test = [ "pytest>=8.2.2", "pytest-cov>=5.0.0", ] +format = [ + "black>=24.10.0", +] [project.urls] "Homepage" = "https://github.com/jahwag/claudesync" @@ -59,6 +62,7 @@ test = [ [project.scripts] claudesync = "claudesync.cli.main:cli" +install-hooks = "claudesync.cli.hooks:install_hooks" [build-system] requires = ["setuptools>=42", "wheel"] diff --git a/src/claudesync/cli/hook_templates/pre_commit.py b/src/claudesync/cli/hook_templates/pre_commit.py new file mode 100644 index 0000000..6afdf4c --- /dev/null +++ b/src/claudesync/cli/hook_templates/pre_commit.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import os +import subprocess +import sys + + +def get_changed_files() -> list[str]: + """Get list of Python files that are staged for commit.""" + try: + cmd = ["git", "diff", "--cached", "--name-only", "--diff-filter=ACMR"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + files = result.stdout.strip().split("\n") + return [f for f in files if f.endswith(".py") and os.path.exists(f)] + except subprocess.CalledProcessError: + print("Failed to get changed files") + return [] + + +def format_files(files: list[str]) -> tuple[bool, list[str]]: + """Format the given files using black.""" + if not files: + return True, [] + + formatted_files = [] + try: + subprocess.run(["black", "--version"], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + print( + "Error: black is not installed. Please install it with: pip install black" + ) + return False, [] + + for file_path in files: + try: + subprocess.run( + ["black", "--quiet", file_path], capture_output=True, check=True + ) + formatted_files.append(file_path) + except subprocess.CalledProcessError as e: + print(f"Failed to format {file_path}: {e}") + return False, formatted_files + + return True, formatted_files + + +def main(): + files = get_changed_files() + if not files: + print("No Python files to format") + sys.exit(0) + + print("Formatting Python files with black...") + success, formatted_files = format_files(files) + + if formatted_files: + print("\nFormatted files:") + for file in formatted_files: + print(f" - {file}") + + # Re-stage formatted files + try: + subprocess.run(["git", "add"] + formatted_files, check=True) + print("\nFormatted files have been re-staged") + except subprocess.CalledProcessError: + print("Failed to re-stage formatted files") + sys.exit(1) + + if not success: + print("\nSome files could not be formatted") + sys.exit(1) + + print("\nAll files formatted successfully") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/src/claudesync/cli/hooks.py b/src/claudesync/cli/hooks.py new file mode 100644 index 0000000..3015a9f --- /dev/null +++ b/src/claudesync/cli/hooks.py @@ -0,0 +1,75 @@ +import os +import shutil +import stat +import subprocess +from pathlib import Path + +import click + +SUPPORTED_GIT_HOOKS = [ + "pre-commit", +] + + +def copy_hook(hooks_dir, hook_name): + """ + Copy a specific hook from templates to git hooks directory. + + Args: + hooks_dir: Path to the .git/hooks directory + hook_name: Name of the hook to install + """ + hook_path = hooks_dir / hook_name + module_dir = Path(__file__).parent + template_name = f"{hook_name.replace('-', '_')}.py" + hook_template = module_dir / "hook_templates" / template_name + + if not hook_template.exists(): + click.echo(f"Warning: Template for {hook_name} not found at {hook_template}") + return + + try: + shutil.copy2(hook_template, hook_path) + + st = os.stat(hook_path) + os.chmod(hook_path, st.st_mode | stat.S_IEXEC) + + click.echo(f"Successfully installed {hook_name} hook") + except Exception as e: + click.echo(f"Error installing {hook_name} hook: {e}") + + +def install_hooks(): + """Install all supported Git hooks for the project.""" + project_root = find_git_root() + if not project_root: + click.echo("Error: Not a git repository (or any of the parent directories)") + return + + hooks_dir = project_root / ".git" / "hooks" + if not hooks_dir.exists(): + click.echo(f"Creating hooks directory: {hooks_dir}") + hooks_dir.mkdir(parents=True, exist_ok=True) + + for hook in SUPPORTED_GIT_HOOKS: + copy_hook(hooks_dir, hook) + + click.echo("\nHook installation complete!") + + +def find_git_root(): + """Find the root directory of the Git repository""" + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=True, + ) + return Path(result.stdout.strip()) + except subprocess.CalledProcessError: + return None + + +if __name__ == "__main__": + install_hooks() From 0295a74d726810b3423a519a5a07b92cc29d24fa Mon Sep 17 00:00:00 2001 From: Guido Dinello Date: Thu, 19 Dec 2024 23:29:41 -0300 Subject: [PATCH 2/3] feat: add hooks command group as a subcommand of claudesync --- src/claudesync/cli/hooks.py | 53 ++++++++++++++++++++++++------------- src/claudesync/cli/main.py | 2 ++ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/claudesync/cli/hooks.py b/src/claudesync/cli/hooks.py index 3015a9f..68e5a3d 100644 --- a/src/claudesync/cli/hooks.py +++ b/src/claudesync/cli/hooks.py @@ -11,6 +11,39 @@ ] +@click.group() +def hooks(): + """Manage Git hooks for the project.""" + pass + + +@hooks.command() +def install(): + """Install Git hooks for automatic code formatting.""" + project_root = find_git_root() + if not project_root: + click.echo("Error: Not a git repository (or any of the parent directories)") + return + + hooks_dir = project_root / ".git" / "hooks" + if not hooks_dir.exists(): + click.echo(f"Creating hooks directory: {hooks_dir}") + hooks_dir.mkdir(parents=True, exist_ok=True) + + for hook in SUPPORTED_GIT_HOOKS: + copy_hook(hooks_dir, hook) + + click.echo("\nHook installation complete!") + + +@hooks.command() +def list(): + """List available Git hooks.""" + click.echo("Available hooks:") + for hook in SUPPORTED_GIT_HOOKS: + click.echo(f" - {hook}") + + def copy_hook(hooks_dir, hook_name): """ Copy a specific hook from templates to git hooks directory. @@ -39,24 +72,6 @@ def copy_hook(hooks_dir, hook_name): click.echo(f"Error installing {hook_name} hook: {e}") -def install_hooks(): - """Install all supported Git hooks for the project.""" - project_root = find_git_root() - if not project_root: - click.echo("Error: Not a git repository (or any of the parent directories)") - return - - hooks_dir = project_root / ".git" / "hooks" - if not hooks_dir.exists(): - click.echo(f"Creating hooks directory: {hooks_dir}") - hooks_dir.mkdir(parents=True, exist_ok=True) - - for hook in SUPPORTED_GIT_HOOKS: - copy_hook(hooks_dir, hook) - - click.echo("\nHook installation complete!") - - def find_git_root(): """Find the root directory of the Git repository""" try: @@ -72,4 +87,4 @@ def find_git_root(): if __name__ == "__main__": - install_hooks() + install() diff --git a/src/claudesync/cli/main.py b/src/claudesync/cli/main.py index ac3a477..65f5134 100644 --- a/src/claudesync/cli/main.py +++ b/src/claudesync/cli/main.py @@ -21,6 +21,7 @@ from .project import project from .sync import schedule from .config import config +from .hooks import hooks import logging logging.basicConfig( @@ -213,6 +214,7 @@ def sync_submodule(provider, config, submodule, category): cli.add_command(schedule) cli.add_command(config) cli.add_command(chat) +cli.add_command(hooks) if __name__ == "__main__": cli() From 835078e5bd5a6c554ee597b8becfa482acdcaef7 Mon Sep 17 00:00:00 2001 From: Guido Dinello Date: Sat, 21 Dec 2024 13:06:25 -0300 Subject: [PATCH 3/3] refactor: remove standalone install-hooks script --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1000789..bde9e7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,6 @@ format = [ [project.scripts] claudesync = "claudesync.cli.main:cli" -install-hooks = "claudesync.cli.hooks:install_hooks" [build-system] requires = ["setuptools>=42", "wheel"]