A tiny, Git-native hook runner driven by fishook.json. Installs stubs into .git/hooks and runs hook commands via bash -lc for any language/tooling.
Project description
fishook 🐟🪝
Tiny git hook runner driven by fishook.json
Why?
- protect yourself from committing secrets accidentally
- run tests before committing
- run linting and style checks before pushing
- run CI/CD pipelines before merging PRs
- auto-generate commit messages
- auto-generate changelogs
- etc.
Install (coming soon)
With node
npm install --save-dev fishook # (runs fishhook install during postinstall)
With pip/pipx
pipx install fishook # or pip install fishook
fishhook install # install hooks
Usage
For the most part, you don't even need to use the CLI, just edit the fishook.json file in your repo, and those commands will be run automatically.
Multiple Configs
Fishook supports multiple config files by searching for all *fishook*.json files in your repo:
fishook.json- Team-wide rules (tracked in git).fishook.local.json- git-ignored rules (maybe the rules are checking for secrets and have the secrets hardcoded? maybe individual users have specific checks they want to do)src/fishook.json- Directory-scoped rules (only applies tosrc/and below)
All matching configs are run in alphabetical order. Directory-scoped configs automatically only apply to files at their level or below. Searches up to 4 directory levels deep.
Example structure:
repo/
├── .gitignore
├── fishook.json # Team rules - applies to all files
├── .fishook.local.json # Your personal rules (gitignored)
└── frontend/
└── fishook.json # Only applies to frontend/ files
.gitignore:
.fishook.local.json
Now each developer can have their own .fishook.local.json for personal preferences!
Basic
{
"pre-commit": "npm test",
"post-checkout": "echo Checked out: $FISHOOK_REF"
}
Complex
{
"setup": "export PATH=$HOME/.local/bin:$PATH",
"source": "$FISHOOK_REPO_ROOT/.venv/bin/activate",
"pre-commit": [
{
"run": ["npm test", "npm run lint"],
"applyTo": ["*.js", "*.ts"],
"skipList": ["vendor/**", "*.min.js", "dist/**"],
"onChange": [
"$FISHOOK_COMMON/forbid-pattern.sh 'console\\.log' 'Remove console.log before committing'",
"$FISHOOK_COMMON/forbid-pattern.sh 'debugger' 'Remove debugger statements'"
]
},
{
"applyTo": ["src/**/*.{js,ts,jsx,tsx}"],
"onAdd": [
"test $(new | wc -c) -lt 100000 || raise 'File too large (>100KB)'"
],
"onChange": [
"diff | grep -q '+.*TODO' && echo 'Warning: New TODO added in $FISHOOK_PATH'"
]
},
{
"skipList": ["*.md", "docs/**"],
"onFileEvent": [
"$FISHOOK_COMMON/forbid-pattern.sh '(password|secret|api[_-]?key)\\s*=\\s*[\"'\\'''][^\"'\\''']' 'Potential secret detected' || true"
]
}
],
"commit-msg": [
"grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\\(.+\\))?!?:' $1 || raise 'Commit message must follow conventional commits format'"
],
"pre-push": {
"onRefUpdate": [
"test \"$FISHOOK_REF\" != 'refs/heads/main' || raise 'Direct push to main blocked. Create a PR instead.'"
]
}
}
Available Hooks supported by git
Client-side (commit workflow)
pre-commitProbably the most useful hook - Runs before a commit is created; commonly lint/tests/format checks; can reject.pre-merge-commitRuns before creating a merge commit (when merge is clean); can reject.prepare-commit-msgRuns before commit message editor opens; can prefill/edit message.commit-msgRuns after message is written; validate commit message; can reject.post-commitRuns after commit is created; notification only.
Client-side (branch / history changes)
pre-rebaseRuns before rebase starts; can reject.post-checkoutRuns after checkout/switch; args old/new/flag.post-mergeRuns after merge; arg is squash flag.post-rewriteRuns after commit rewriting; arg is rewrite command; stdin has old/new oids.
Client-side (push / maintenance)
pre-pushRuns before pushing; args remote_name/remote_url; stdin lists ref updates.pre-auto-gcRuns before git gc --auto; can abort.
Client-side (patch / email workflows; a bit outdated)
applypatch-msgRuns during git am after extracting a patch commit message; validate/edit the message.pre-applypatchRuns during git am before committing the applied patch; can reject.post-applypatchRuns during git am after committing; notification only.sendemail-validateRuns during git send-email to validate outgoing patch email; can reject.
Server-side (bare repo / self-hosted only; not GitHub/GitLab.com)
pre-receiveServer-side: before accepting pushed refs; stdin old/new/ref triples. Not run on GitHub.updateServer-side: per-ref update check; args ref/old/new. Not run on GitHub.post-receiveServer-side: after refs updated; stdin old/new/ref triples. Not run on GitHub.post-updateServer-side: after refs updated; args are ref names. Not run on GitHub.push-to-checkoutServer-side: when pushing to checked-out branch with updateInstead. Not run on GitHub.proc-receiveServer-side: advanced receive-pack protocol hook. Not run on GitHub.
Performance
fsmonitor-watchmanUsed by core.fsmonitor to speed status; reports changed files.
CLI
The following commands are available:
fishook # general help
fishook install # install hooks
fishook list # show all available hooks
fishook explain pre-commit # explain a hook
fishook pre-commit --dry-run # test manually
fishook pre-commit # run the hook manually
fishook uninstall # remove
Formats
fishhook.json supports a variety of formats ranging from
- simple (just a string command or a list of commands) ...
- to complex (multiple actions per hook, each ignoring or applying to only certain files)
Hopefully this explains the various options:
type SingleRunCmd = string; // e.g. "pre-commit": "echo foo",
type RunCmdList = string[]; // e.g. "pre-commit": ["echo foo", "echo bar"],
type RunCmd = SingleRunCmd | RunCmdList;
type Setup = SingleRunCmd | RunCmdList; // runs BEFORE every command (as-is)
type Source = SingleRunCmd | RunCmdList; // runs BEFORE every command (auto-prepends "source")
type FileGlobFilter = string | string[]; // glob or array of globs applied to filepaths
type SingleActionSpec = {
run: RunCmd, // execute once per hook
onAdd?: RunCmdList, // called once per file added
onChange?: RunCmdList, // called once per file changed
onDelete?: RunCmdList, // called once per file deleted
onMove?: RunCmdList, // called once per file moved
onCopy?: RunCmdList, // called once per file copied
onFileEvent?: RunCmdList, // called per file event
onRefEvent?: RunCmdList, // called per ref event
onRefCreate?: RunCmdList, // called per ref create event
onRefUpdate?: RunCmdList, // called per ref update event
onRefDelete?: RunCmdList, // called per ref delete event
onEvent?: RunCmdList, // called per event
applyTo?: FileGlobFilter, // file-event filter (defaults to all)
skipList?: FileGlobFilter, // file-event filter (defaults to none)
}
type SingleAction = RunCmd | SingleActionSpec;
type Action = SingleAction | SingleAction[];
type Key = |
'applypatch-msg' |
'pre-applypatch' |
'post-applypatch' |
'sendemail-validate' |
'pre-commit' |
'prepare-commit-msg' |
'commit-msg' |
'post-commit' |
'pre-rebase' |
'post-checkout' |
'post-merge' |
'post-rewrite' |
'pre-push' |
'pre-auto-gc' |
'pre-receive' |
'update' |
'post-receive' |
'post-update' |
'push-to-checkout' |
'proc-receive' |
'fsmonitor-watchman';
type Spec = {
setup?: Setup, // top-level: runs before every command (e.g. export PATH)
source?: Source, // top-level: auto-sources files before every command (e.g. ".venv/bin/activate")
[k: Key]: Action
}
Common Utilities
Fishook includes reusable scripts in $FISHOOK_COMMON/:
forbid-pattern <pattern> <message>- Fail if pattern found in file contentforbid-file-pattern <pattern> <message>- Fail if file path matches patternensure-executable- Make the current file executable (use withapplyTofilter)modify_commit_messageiter_source <folder>- Iterate all the bash files in a folder and source them (e.g.iter_source scripts)pcsed <pattern> <replacement> [--index-only] [--local-only]- Apply sed commands to file content, replacing either in both worktree and staged (default), staged only (--index-only), or local only (--local-only).
Examples:
{
"pre-commit": [
{
"applyTo": ["*.js"],
"onChange": [
"forbid_pattern 'console\\.log' 'Remove console.log'"
]
},
{
"onFileEvent": [
"forbid_file_pattern '\\.env$' 'Do not commit .env files'",
"forbid_file_pattern 'secret|credential' 'File name contains secret/credential'"
]
},
{
"applyTo": ["*.sh", "scripts/*"],
"onAdd": ["$FISHOOK_COMMON/ensure-executable.sh"],
"onChange": ["$FISHOOK_COMMON/ensure-executable.sh"]
}
]
}
Scope
Available in all commands (including setup and source):
Functions
old()- get old file contentnew()- get new file contentdiff()- show diffraise "msg"- fail hook with message
Common Utilities
Fishook includes reusable scripts in $FISHOOK_COMMON/:
forbid_pattern <pattern> <message>- Fail if pattern found in file contentforbid_file_pattern <pattern> <message>- Fail if file path matches patternensure_executable- Make the current file executable (use withapplyTofilter)sedder- Apply sed commands to file content, replacing either in both worktree and staged (default), staged only (--index-only), or local only (--local-only).
Environment Variables
Available in hook commands:
FISHOOK_COMMON- path to fishook's common/ scripts directory (use for shared utilities)FISHOOK_CONFIG_DIR- directory containing the current config file (for scoped configs)FISHOOK_REPO_ROOT- absolute path to repo root (use this for paths!)FISHOOK_REPO_NAME- repo directory nameFISHOOK_HOOK- hook name (pre-commit, commit-msg, etc)FISHOOK_EVENT- event type (add, change, delete, move, copy)FISHOOK_PATH- file path (add/change/delete)FISHOOK_SRC,FISHOOK_DST- source/dest (move/copy)FISHOOK_REF- ref name (post-checkout: new HEAD; ref events in pre-push/update/etc)FISHOOK_OLD_OID,FISHOOK_NEW_OID- commit oidsFISHOOK_REMOTE_NAME,FISHOOK_REMOTE_URL- remote info (pre-push only)
Configuration options:
FISHOOK_INSTALL_CHOICE- bypass interactive prompt during install (set to1=overwrite,2=chain,3=backup). Useful for CI/CD and automated scenarios.
Positional Arguments
Git hook arguments are available as $1, $2, $3, etc. if you need them:
commit-msg:$1= path to commit message filepost-checkout:$1= previous HEAD,$2= new HEAD,$3= branch checkout flagpre-push:$1= remote name,$2= remote URL- Most commands use environment variables instead
Requirements
git,bash,jq
License
UnLicense
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fishook-1.0.0.tar.gz.
File metadata
- Download URL: fishook-1.0.0.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8f94eff79ca297ce8ca30e81574d00db94073a346f74b5b3783f66cb27b6284
|
|
| MD5 |
1f77f04b9e95f5dc5b2a26672ff0ec0a
|
|
| BLAKE2b-256 |
52d3362a9c7710e5f4f5b96cb070f0a8a0c59f11b0b73fece5e035617515be68
|
Provenance
The following attestation bundles were made for fishook-1.0.0.tar.gz:
Publisher:
publish-to-pip.yml on modularizer/fishook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fishook-1.0.0.tar.gz -
Subject digest:
e8f94eff79ca297ce8ca30e81574d00db94073a346f74b5b3783f66cb27b6284 - Sigstore transparency entry: 814125075
- Sigstore integration time:
-
Permalink:
modularizer/fishook@2601231bee7d389bc00147bf3978859e0fd110c1 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/modularizer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pip.yml@2601231bee7d389bc00147bf3978859e0fd110c1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fishook-1.0.0-py3-none-any.whl.
File metadata
- Download URL: fishook-1.0.0-py3-none-any.whl
- Upload date:
- Size: 23.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e87128e2c918462700f75bff6e93b4d8efb407bffe91855b847d40650b5a4c6
|
|
| MD5 |
162b9b4f1fe35ff60dce0d41210adde2
|
|
| BLAKE2b-256 |
5303c7be7500d40dec4d2655b9312b422a311d77654d10cbc2ff8e8cc17fc76b
|
Provenance
The following attestation bundles were made for fishook-1.0.0-py3-none-any.whl:
Publisher:
publish-to-pip.yml on modularizer/fishook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fishook-1.0.0-py3-none-any.whl -
Subject digest:
9e87128e2c918462700f75bff6e93b4d8efb407bffe91855b847d40650b5a4c6 - Sigstore transparency entry: 814125078
- Sigstore integration time:
-
Permalink:
modularizer/fishook@2601231bee7d389bc00147bf3978859e0fd110c1 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/modularizer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pip.yml@2601231bee7d389bc00147bf3978859e0fd110c1 -
Trigger Event:
release
-
Statement type: