-
-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(dev): add hooks setup and pre-push hook with black formatter #101
Open
guidodinello
wants to merge
3
commits into
jahwag:master
Choose a base branch
from
guidodinello:feature/pre-push-hook
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,90 @@ | ||
import os | ||
import shutil | ||
import stat | ||
import subprocess | ||
from pathlib import Path | ||
|
||
import click | ||
|
||
SUPPORTED_GIT_HOOKS = [ | ||
"pre-commit", | ||
] | ||
|
||
|
||
@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. | ||
|
||
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 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() |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach didn’t work on Windows because only the
python
alias is mapped, leading to the error:/usr/bin/env: 'python3': No such file or directory
.Since this issue could affect a number of users, it would be more robust to use
#!/usr/bin/env python
in the shebang for better cross-platform compatibility. To ensure the script runs with Python 3, you can add a runtime version check like this:This ensures that the script will fail gracefully if the system default
python
points to Python 2. It also avoids requiring users to set up thepython3
alias, which may not be available by default on Windows.