diff options
| author | 2026-05-19 16:50:52 +0100 | |
|---|---|---|
| committer | 2026-05-19 16:50:52 +0100 | |
| commit | 0fc39faa90f97db24043017a845f1754b4bb8b84 (patch) | |
| tree | 066acfd99d41d2f4ebf0d3716ae5f28a06801aa5 /dot_config | |
| parent | 67868f51bbab5bc3ef5c8ba15433ba401a297f1a (diff) | |
| download | dotfiles-0fc39faa90f97db24043017a845f1754b4bb8b84.tar.gz dotfiles-0fc39faa90f97db24043017a845f1754b4bb8b84.tar.bz2 dotfiles-0fc39faa90f97db24043017a845f1754b4bb8b84.zip | |
feat(git): per-clone hook override at .git/hooks-local/
Adds an untracked per-clone override layer to the hook dispatcher.
Lookup order is now:
1. <git-dir>/hooks-local/<name> — untracked, per-clone, ignored by git
2. <repo-top>/.githooks/<name> — tracked, shared with teammates
Use case: a shared repo ships a .githooks/pre-commit you want to
locally replace without modifying the tracked file. Drop your hook in
.git/hooks-local/<name> (chmod +x) and the dispatcher will run it
instead — the global commit-msg trailer-strip and pre-push gate still
run on top.
If neither override exists, only the global user-level logic runs.
Diffstat (limited to 'dot_config')
| -rw-r--r-- | dot_config/git/hooks/_dispatch.sh | 44 |
1 files changed, 29 insertions, 15 deletions
diff --git a/dot_config/git/hooks/_dispatch.sh b/dot_config/git/hooks/_dispatch.sh index 7dcd89c..0b1135d 100644 --- a/dot_config/git/hooks/_dispatch.sh +++ b/dot_config/git/hooks/_dispatch.sh @@ -1,16 +1,26 @@ #!/bin/sh -# Sourced by every hook in this directory. Runs the per-repo hook of the -# same name from `<repo-top>/.githooks/` if it exists, then returns -# control so the calling user-level hook can do its own work after. +# Sourced by every hook in this directory. Runs the per-repo hook of +# the same name and then returns control so the calling user-level +# hook can do its own work after. # -# Repos opt in by just dropping `.githooks/<hookname>` (executable) in -# the working tree — no per-repo `core.hooksPath` setting, no stubs. -# If the per-repo hook exits non-zero we abort with that status so git -# sees the failure. +# Lookup order (first executable file wins): +# 1. `<git-dir>/hooks-local/<hookname>` — untracked per-clone override, +# lives inside .git/ so it stays personal. Use this when you want +# to replace a tracked .githooks/<name> on a shared repo without +# affecting teammates. Create with e.g.: +# $ mkdir -p "$(git rev-parse --git-dir)/hooks-local" +# $ $EDITOR "$(git rev-parse --git-dir)/hooks-local/pre-commit" +# $ chmod +x "$(git rev-parse --git-dir)/hooks-local/pre-commit" +# 2. `<repo-top>/.githooks/<hookname>` — tracked, the project's +# shared hook. The intended default for opting a repo in. +# +# Either an empty file with exit 0 or no hook at all means "skip the +# project layer entirely"; the user-level hook still runs its own +# global logic afterwards. # # GIT_HOOK_DISPATCHED guards against re-entry: if some legacy repo has # its own `.githooks/<hook>` that ends with `exec "$HOME/.config/..."` -# (the old pattern), we won't dispatch back into it a second time. +# (the old stub pattern), we won't dispatch back into it a second time. # shellcheck shell=sh dispatch_repo_hook() { @@ -19,13 +29,17 @@ dispatch_repo_hook() { [ -n "${GIT_HOOK_DISPATCHED:-}" ] && return 0 + gitdir=$(git rev-parse --git-dir 2>/dev/null) || return 0 root=$(git rev-parse --show-toplevel 2>/dev/null) || return 0 - repo_hook="$root/.githooks/$hookname" - [ -x "$repo_hook" ] || return 0 - GIT_HOOK_DISPATCHED=1 "$repo_hook" "$@" - rc=$? - if [ "$rc" -ne 0 ]; then - exit "$rc" - fi + for candidate in "$gitdir/hooks-local/$hookname" "$root/.githooks/$hookname"; do + if [ -x "$candidate" ]; then + GIT_HOOK_DISPATCHED=1 "$candidate" "$@" + rc=$? + if [ "$rc" -ne 0 ]; then + exit "$rc" + fi + return 0 + fi + done } |
