Skip to main content

git-a-grip

Two things, from one package:

  1. A set of pre-commit hooks you point your .pre-commit-config.yaml at.
  2. gag, a CLI for managing pre-commit across all your repos — see what every project pins, and pin them all to the same rev.

1. The hooks

repos:
  - repo: https://github.com/dannybrown37/git-a-grip
    rev: v0.5.0
    hooks:
      - id: commitizen-early
      - id: ruff-check
      - id: ruff-format
      - id: readme-tree
      - id: pytest
        args: [tests/, -q]

Nothing to install. Your project needs no ruff, cz or uv on PATH — pre-commit builds the env. (pytest, eslint and tsc are the exceptions: they must run inside your project's environment, so they shell out to uv run / your package manager.)

Hook What it does for you
commitizen-early Rejects a bad commit message in ~0.3s, instead of after the whole hook suite has run. Pair with the upstream commitizen hook for the cases it can't see (editor, merge, rebase).
ruff-check ruff check --fix, with fixes re-staged — no dirty tree to git add and amend.
ruff-format ruff format, likewise re-staged.
pytest Your test suite, from the repo root, in your project's env. args: ['--runner=uv run --extra api pytest', ...] to change the runner.
readme-tree Keeps a file tree in your README true. Drop <!-- tree:start --> / <!-- tree:end --> in, and it regenerates and re-stages on every commit. Contents come from git ls-files, so it's exactly what's committed.
eslint eslint --fix, re-staged, --max-warnings=0 by default so warnings can't pile up forever.
tsc Type-checks the project, never bare filenames — given filenames, tsc silently ignores your tsconfig.json.

Anything in args is passed through to the underlying tool. Pin your own tool version with additional_dependencies: [ruff==0.16.1].

Full details on any hook, including the paste-ready config block:

gag hooks           # one line per hook
gag hooks pytest    # that one, in full
gag hooks --all

2. The gag CLI

uv tool install git-a-grip    # `gag` on PATH
uvx --from git-a-grip gag    # or one-off

gag --help lists everything; gag <command> --help its options.

gag audit — what does every repo pin?

Walks your project directories, stops at each git working tree, and reports which hooks each one uses and at what rev — labelled (behind), (ahead), (unpinned) against the version you have installed. Third-party hooks and repo: local one-offs are listed too.

gag audit
gag audit ~/projects ~/work
gag audit --json | jq '.repos[] | select(.hooks == [])'

gag sync — pin them all to one rev

The other half. Dry run by default, one line per repo (old -> new).

gag sync                    # dry run against the installed version
gag sync --write            # apply it
gag sync --to v0.5.0
gag sync --latest --write
gag sync --repo https://github.com/gitleaks/gitleaks --latest

The rewrite is textual and touches only the rev: line, so your comments and formatting survive. Nothing is committed — changes land in each repo's working tree for you to review.

gag release — bump, tag, push

For repos that release from a laptop rather than from CI. On main it bumps and pushes; on any other branch it just pushes, so it can replace git push. Refuses to run on a dirty tree. Configure the bump via [tool.commitizen] in the target repo.

alias release='uvx --from git-a-grip gag release'

(A pre-push hook cannot do this: git picks the sha to push before hooks run, so a commit made during the hook is either cancelled or rejected as non-fast-forward. As a command, the bump simply happens first.)

gag hooks — the hook reference

The table above, in full, from your installed version. Colorized for the terminal, plain when piped.


Running a hook by hand

The hooks aren't scripts on your PATH — gag is the only thing this package installs. To run one outside pre-commit (debugging):

uv tool install 'git-a-grip[hooks]'
python -m git_a_grip.hooks ruff-check path/to/file.py

Development

uv sync
uv run pytest

Releases are cut by CI: merge to main, and if lint, tests and the install proofs pass, cz bump tags it and publish.yml uploads to PyPI. Nothing is tagged before the checks pass.

This repo eats its own dog food — its hooks run on itself, and the tree below is maintained by readme-tree.

git-a-grip/
|-- .github/
|   `-- workflows/
|       |-- ci.yml
|       `-- publish.yml
|-- src/
|   `-- git_a_grip/
|       |-- __init__.py
|       |-- audit.py
|       |-- cli.py
|       |-- commitizen_early.py
|       |-- cz.py
|       |-- hook_docs.py
|       |-- hooks.py
|       |-- node_hooks.py
|       |-- pytest_hook.py
|       |-- readme_tree.py
|       |-- release.py
|       |-- restage.py
|       |-- ruff_hooks.py
|       |-- sync.py
|       `-- version.py
|-- tests/
|   |-- test_audit.py
|   |-- test_cli.py
|   |-- test_commitizen_early.py
|   |-- test_hook_docs.py
|   |-- test_hooks.py
|   |-- test_node_hooks.py
|   |-- test_packaging.py
|   |-- test_pytest_hook.py
|   |-- test_readme_tree.py
|   |-- test_release.py
|   |-- test_restage.py
|   |-- test_ruff_hooks.py
|   |-- test_sync.py
|   `-- test_version.py
|-- .gitignore
|-- .pre-commit-config.yaml
|-- .pre-commit-hooks.yaml
|-- .ruff.toml
|-- CHANGELOG.md
|-- LICENSE
|-- pyproject.toml
|-- README.md
`-- uv.lock

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

git_a_grip-0.5.1.tar.gz (71.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

git_a_grip-0.5.1-py3-none-any.whl (32.4 kB view details)

Uploaded Python 3

File details

Details for the file git_a_grip-0.5.1.tar.gz.

File metadata

  • Download URL: git_a_grip-0.5.1.tar.gz
  • Upload date:
  • Size: 71.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for git_a_grip-0.5.1.tar.gz
Algorithm Hash digest
SHA256 6b2a9715988614464106a75700679398adef636516461093f4c68b380dd7622d
MD5 bae801fc36227ba8a4b3ae43f0bc29b9
BLAKE2b-256 dbbefb37d2d9530b5d5cc156c2d5d99e6d051deb7dc12b12eeab8df7b760c8c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_a_grip-0.5.1.tar.gz:

Publisher: publish.yml on dannybrown37/git-a-grip

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file git_a_grip-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: git_a_grip-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 32.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for git_a_grip-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cf31743e890b779aa75b42a732d625d40477fb1fd1e1f8a71d5f5062ce6a1ff0
MD5 79c090e24810b9f57835a0935c26e9b1
BLAKE2b-256 113994a9ce7d69e63b80df943592bbcfd627b69d3e799fd05ca2e3e3e0be69be

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_a_grip-0.5.1-py3-none-any.whl:

Publisher: publish.yml on dannybrown37/git-a-grip

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page