Git subcommands for curating the index by hand, so your commits stay focused enough to review, bisect, and blame.
| Script | Purpose |
|---|---|
bin/git-addgrep |
Stage every change matching a regex, per-hunk when grepdiff is available. |
bin/git-restage |
Refresh the staged copy of files you have already staged and then edited. |
bin/git-post-fmt |
Stage only the files whose changes are formatting-only, so chore(fmt) lands separately. |
The reasoning behind each one is in Commit like a librarian.. That article's code blocks are generated from this repository, so these files are the version to trust.
Anything on your $PATH named git-foo is callable as git foo. Clone and
symlink:
git clone https://github.com/photostructure/git-tools.git
ln -s "$PWD/git-tools/bin/git-addgrep" ~/.local/bin/git-addgrep
ln -s "$PWD/git-tools/bin/git-restage" ~/.local/bin/git-restage
ln -s "$PWD/git-tools/bin/git-post-fmt" ~/.local/bin/git-post-fmtCopying the scripts works just as well. Each carries a # Source: header
pointing back here, so a stray copy can always find the maintained one.
git addgrep oldFunctionNameStages every hunk whose diff mentions the pattern. git diff -U0 emits
zero-context hunks, grepdiff keeps the matching ones, and git apply --cached
applies that filtered patch to the index.
Per-hunk staging needs grepdiff, from
patchutils. The package is named
patchutils for brew, apt, dnf, pacman, and apk alike. Without it,
the script says so and falls back to staging whole files that match, via
git diff -S/-G.
git restageRe-stages the files that are already staged, and only those. Useful after a formatter, a deleted debug line, or one more test lands in files you had already staged.
It stages the whole file, so a partially-staged file loses its hunk selection.
git post-fmtStages the files whose changes are formatting-only, so you can commit
chore(fmt): apply prettier first and keep the next commit semantic.
For a file type with a mapped formatter, the test is exact: reformat both the
HEAD version and the working copy, then compare. Matching canonical forms mean
every difference between the two is something the formatter rewrites. Files with
no mapped formatter fall back to a normalization heuristic that strips
whitespace and shuffled punctuation, which can mask a precedence edit like
(a+b)*c versus a+b*c — skim git diff --cached before committing those.
Add your own languages to the formatter_for table. Any formatter that reads
stdin and writes stdout works; point it at the same binary your pre-commit hook
and CI use, or "canonical" means something different in each place. The defaults
cover Prettier, gofmt, rustfmt, black, shfmt, and clang-format.
The exact test costs two formatter runs per file, so checks fan out across cores
with xargs -P and the survivors are staged in one batched git add. For a
large Prettier repo, point the table at a resident daemon like prettierd to
skip Node's startup cost on every file.
make install-tools # shellcheck and shfmt, via brew, apt, dnf, pacman, or apk
make precommit # shellcheck + shfmt --diff, same as CI
make fmt # shfmt -w -i 2photostructure/coding-skills
is the plugin marketplace with the gitplan and stage skills, which teach a
coding agent the same discipline.
MIT