-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dev): add hooks setup and pre-push hook with black formatter
- Loading branch information
1 parent
0006331
commit a39cabc
Showing
3 changed files
with
156 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |