Skip to main content

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 to src/ 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-commit Probably the most useful hook - Runs before a commit is created; commonly lint/tests/format checks; can reject.
  • pre-merge-commit Runs before creating a merge commit (when merge is clean); can reject.
  • prepare-commit-msg Runs before commit message editor opens; can prefill/edit message.
  • commit-msg Runs after message is written; validate commit message; can reject.
  • post-commit Runs after commit is created; notification only.

Client-side (branch / history changes)

  • pre-rebase Runs before rebase starts; can reject.
  • post-checkout Runs after checkout/switch; args old/new/flag.
  • post-merge Runs after merge; arg is squash flag.
  • post-rewrite Runs after commit rewriting; arg is rewrite command; stdin has old/new oids.

Client-side (push / maintenance)

  • pre-push Runs before pushing; args remote_name/remote_url; stdin lists ref updates.
  • pre-auto-gc Runs before git gc --auto; can abort.

Client-side (patch / email workflows; a bit outdated)

  • applypatch-msg Runs during git am after extracting a patch commit message; validate/edit the message.
  • pre-applypatch Runs during git am before committing the applied patch; can reject.
  • post-applypatch Runs during git am after committing; notification only.
  • sendemail-validate Runs during git send-email to validate outgoing patch email; can reject.

Server-side (bare repo / self-hosted only; not GitHub/GitLab.com)

  • pre-receive Server-side: before accepting pushed refs; stdin old/new/ref triples. Not run on GitHub.
  • update Server-side: per-ref update check; args ref/old/new. Not run on GitHub.
  • post-receive Server-side: after refs updated; stdin old/new/ref triples. Not run on GitHub.
  • post-update Server-side: after refs updated; args are ref names. Not run on GitHub.
  • push-to-checkout Server-side: when pushing to checked-out branch with updateInstead. Not run on GitHub.
  • proc-receive Server-side: advanced receive-pack protocol hook. Not run on GitHub.

Performance

  • fsmonitor-watchman Used 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 content
  • forbid-file-pattern <pattern> <message> - Fail if file path matches pattern
  • ensure-executable - Make the current file executable (use with applyTo filter)
  • modify_commit_message
  • iter_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 content
  • new() - get new file content
  • diff() - show diff
  • raise "msg" - fail hook with message

Common Utilities

Fishook includes reusable scripts in $FISHOOK_COMMON/:

  • forbid_pattern <pattern> <message> - Fail if pattern found in file content
  • forbid_file_pattern <pattern> <message> - Fail if file path matches pattern
  • ensure_executable - Make the current file executable (use with applyTo filter)
  • 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 name
  • FISHOOK_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 oids
  • FISHOOK_REMOTE_NAME, FISHOOK_REMOTE_URL - remote info (pre-push only)

Configuration options:

  • FISHOOK_INSTALL_CHOICE - bypass interactive prompt during install (set to 1=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 file
  • post-checkout: $1 = previous HEAD, $2 = new HEAD, $3 = branch checkout flag
  • pre-push: $1 = remote name, $2 = remote URL
  • Most commands use environment variables instead

Requirements

  • git, bash, jq

License

UnLicense

Project details


Download files

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

Source Distribution

fishook-1.0.0.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

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

fishook-1.0.0-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

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

Hashes for fishook-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e8f94eff79ca297ce8ca30e81574d00db94073a346f74b5b3783f66cb27b6284
MD5 1f77f04b9e95f5dc5b2a26672ff0ec0a
BLAKE2b-256 52d3362a9c7710e5f4f5b96cb070f0a8a0c59f11b0b73fece5e035617515be68

See more details on using hashes here.

Provenance

The following attestation bundles were made for fishook-1.0.0.tar.gz:

Publisher: publish-to-pip.yml on modularizer/fishook

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

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

Hashes for fishook-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9e87128e2c918462700f75bff6e93b4d8efb407bffe91855b847d40650b5a4c6
MD5 162b9b4f1fe35ff60dce0d41210adde2
BLAKE2b-256 5303c7be7500d40dec4d2655b9312b422a311d77654d10cbc2ff8e8cc17fc76b

See more details on using hashes here.

Provenance

The following attestation bundles were made for fishook-1.0.0-py3-none-any.whl:

Publisher: publish-to-pip.yml on modularizer/fishook

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