Usage guide

git-hooks-ext

0.6.0

Name

git-hooks-ext — semantic events for Git reference transactions

Synopsis

git-hooks-ext install [--local|--global|--system]
# or: ghe install [--local|--global|--system]
ghe uninstall [--local|--global|--system]
ghe add [--local|--global|--system] <event> <name> <command> [args...]
ghe list [--local|--global|--system]
ghe show [--local|--global|--system] <name>
ghe remove [--local|--global|--system] <name>
ghe verbose [--local|--global|--system] <on|off>
ghe events
ghe doctor
ghe worktree <command> [args...]
ghe reference-transaction <state> [--dry-run]

Description

Git's reference-transaction hook reports old and new values together with a ref name. git-hooks-ext translates that input into events such as branch-created, tag-deleted, and branch-renamed.

ghe is a shorter, equivalent command installed alongside git-hooks-ext.

Run ghe doctor in a repository for its detected Git compatibility and bridge status.

It also provides a frontend for semantic worktree lifecycle events that Git does not expose as native hooks.

Git input 0000… a84f… refs/heads/topic
Creation candidate branch-created topic

Event hooks run after the transaction is committed. They receive the short ref name, full ref name, old value, and new value. Symbolic values use Git's ref: representation.

Events

Event names are identical in hook files, Git config, and dry-run output.

Everyday refs

RefCreatedDeletedUpdatedRenamed
Branchbranch-createdbranch-deletedbranch-updatedbranch-renamed
Remote branchremote-branch-createdremote-branch-deletedremote-branch-updatedremote-branch-renamed
Tagtag-createdtag-deletedtag-updatedtag-renamed
Notenote-creatednote-deletednote-updatednote-renamed
Stashstash-createdstash-deletedstash-updated—

Specialized refs

RefCreatedDeletedUpdated
Replacereplace-createdreplace-deletedreplace-updated
Prefetchprefetch-createdprefetch-deletedprefetch-updated
Bisect refbisect-ref-createdbisect-ref-deletedbisect-ref-updated
Rewritten refrewritten-ref-createdrewritten-ref-deletedrewritten-ref-updated
Per-worktree refworktree-ref-createdworktree-ref-deletedworktree-ref-updated

Git may report an all-zero old value even for an existing ref. The bridge reads its previous value during prepared and restores it at committed, distinguishing creation from updates. Without a usable snapshot, *-created remains a creation candidate. Dry-run output uses only its input.

Fallback refs

RefCreatedDeletedUpdated
Other refs/*ref-createdref-deletedref-updated
Root refroot-ref-createdroot-ref-deletedroot-ref-updated

Remote HEAD

RefCreatedDeletedUpdated
Remote HEADremote-head-createdremote-head-deletedremote-head-updated

HEAD

HEADUpdatedAttachedDetachedSwitched
HEADhead-updatedhead-attachedhead-detachedhead-switched

The worktree-ref-* events describe updates under refs/worktree/*. They are separate from the worktree-* lifecycle events below.

remote-head-* treats the first path component after refs/remotes/ as the remote name. Names containing / are ambiguous with remote branches ending in /HEAD and are classified as remote branches.

Ref names outside refs/* that reach the ref backend, including custom names, emit root-ref-*. Git's main-worktree/ and worktrees/<name>/ aliases use the class of the underlying per-worktree ref while retaining the full alias-qualified name in hook arguments. Alias-qualified FETCH_HEAD and MERGE_HEAD can reach the ref backend and emit root-ref-*, unlike their direct pseudoref names.

Every changed HEAD value emits head-updated. Changes between direct and symbolic values additionally emit head-attached or head-detached; changes between symbolic targets emit head-switched. A snapshot preserves the previous symbolic target or direct OID when Git supplies a zero old value, including for git update-ref --no-deref HEAD. Without a snapshot, an unknown attachment state produces only head-updated.

Worktree lifecycle

Git has no hook for observing worktree lifecycle operations, so these events are available only through the ghe worktree wrapper.

Wrapper commandEvent
ghe worktree addworktree-created
ghe worktree removeworktree-removed
ghe worktree moveworktree-moved
ghe worktree lockworktree-locked
ghe worktree unlockworktree-unlocked
ghe worktree pruneworktree-pruned
ghe worktree repairworktree-repaired

The wrapper forwards all arguments to git worktree, compares the worktree state before and after a successful mutating command, and emits the observed lifecycle events. Calling git worktree directly bypasses it and cannot emit these events.

Quick start

1. Enable the hook bridge

git-hooks-ext install
# or: ghe install

The command automatically uses config-based hooks with Git 2.54+ or installs the legacy bridge on older Git versions. After upgrading Git, remove the legacy reference-transaction hook and run git-hooks-ext install again (or ghe install).

2. Add an event hook

mkdir -p scripts
cat > scripts/announce-branch <<'SH'
#!/bin/sh
echo "created branch: $1"
SH
chmod +x scripts/announce-branch
ghe add branch-created announce-branch ./scripts/announce-branch

3. Trigger it

git branch topic
created branch: topic

Manage configured hooks

For config-based hooks added with ghe add, inspect and manage the named entries:

ghe list
ghe show announce-branch
ghe remove announce-branch

list prints each hook name, event and command. The commands use local configuration by default and accept --local, --global or --system. The technical bridge is not listed; use install and uninstall to manage it.

Log hook dispatches

ghe verbose on
# disable it again
ghe verbose off

verbose is disabled by default. When enabled, it writes each hook dispatch to the same standard-error stream as Git's messages:

[git-hooks-ext] branch-created ➠ create-branch-env

Diagnose compatibility

ghe doctor

Run this inside a repository to see the detected Git version and ref backend, the bridge state, and whether each event is supported, requires a newer Git version, or is affected by a known Git bug.

Worktree events

Use the worktree frontend when lifecycle events are needed:

ghe worktree add -b feature ../feature
ghe worktree lock --reason "offline disk" ../feature
ghe worktree move ../feature ../feature-renamed
ghe worktree remove ../feature-renamed

Arguments are forwarded to git worktree. Events are emitted after Git succeeds and the worktree state changes.

Compatibility

Raw Git results were measured on 2026-09-19. Snapshot recovery was validated on 2026-10-01 with Apple Git 2.39.3 and Git 2.55 using files and reftable. The CI matrix covers Git 2.27–2.55. Git versions < 2.28 do not provide the required reference-transaction hook.

Commands and emitted events

This table lists each tested command, its expected event and the Git versions that emit it. Direct update-ref commands show where an event remains reachable when a higher-level command omits usable transaction data. The worktree rows use the extension's frontend.

HookCommandSupported Git versions
branch-createdgit branch topicGit ≥ 2.28
branch-updatedgit commitGit ≥ 2.28
branch-deletedgit branch -D topicGit ≥ 2.28
branch-deletedgit update-ref -d refs/heads/topicGit ≥ 2.28
branch-renamedgit branch -m old new❌ ¹
branch-renamedgit update-ref --stdin (heads)Git ≥ 2.28
remote-branch-createdgit fetch originGit ≥ 2.28
remote-branch-updatedgit fetch originGit ≥ 2.28
remote-branch-deletedgit remote prune originGit ≥ 2.28
remote-branch-deletedgit update-ref -d refs/remotes/origin/topicGit ≥ 2.28
remote-branch-renamedgit remote rename origin upstreamGit ≥ 2.55
remote-branch-renamedgit update-ref --stdin (remotes)Git ≥ 2.28
tag-createdgit tag v1Git ≥ 2.28
tag-updatedgit tag -f v1Git ≥ 2.28
tag-deletedgit tag -d v1Git ≥ 2.28
tag-deletedgit update-ref -d refs/tags/topicGit ≥ 2.28
tag-renamedgit update-ref --stdin (tags)Git ≥ 2.28
stash-createdfirst git stash pushGit ≥ 2.28
stash-updatedsecond git stash pushGit ≥ 2.28
stash-updatedgit update-ref refs/stashGit ≥ 2.28
stash-deletedgit stash clearGit ≥ 2.28
note-createdgit notes addGit ≥ 2.28
note-updatedgit notes appendGit ≥ 2.28
note-updatedgit notes removeGit ≥ 2.28
note-updatedgit update-ref refs/notes/topicGit ≥ 2.28
note-deletedgit update-ref -d refs/notes/topicGit ≥ 2.28
note-renamedgit update-ref --stdin (notes)Git ≥ 2.28
remote-head-createdgit remote set-head origin mainGit ≥ 2.54
remote-head-createdgit remote set-head origin topic after fetchGit ≥ 2.54
remote-head-updatedgit update-ref --stdin (symref-update)Git ≥ 2.54
remote-head-deletedgit remote set-head -d originGit ≥ 2.54
remote-head-deletedgit update-ref --stdin (symref-delete)Git ≥ 2.54
replace-createdgit replace <old> <new>Git ≥ 2.28
replace-updatedgit replace -f <old> <new>Git ≥ 2.28
replace-deletedgit replace -d <old>Git ≥ 2.28
prefetch-createdgit fetch --prefetch originGit ≥ 2.32
prefetch-updatedsecond git fetch --prefetch originGit ≥ 2.32
bisect-ref-createdgit bisect start <bad> <good>Git ≥ 2.28
remote-head-*direct git update-refGit ≥ 2.28
replace-*direct git update-refGit ≥ 2.28
prefetch-*direct git update-refGit ≥ 2.28
bisect-ref-*direct git update-refGit ≥ 2.28
rewritten-ref-*direct git update-refGit ≥ 2.28
worktree-ref-*direct git update-refGit ≥ 2.28
ref-*direct git update-refGit ≥ 2.28
head-updatedsymbolic/ref-backend transactionGit ≥ 2.54
head-attachedsymbolic/ref-backend transactionGit ≥ 2.54
head-switchedsymbolic/ref-backend transactionGit ≥ 2.54
remote-head-createdsymbolic/ref-backend transactionGit ≥ 2.54
root-ref-*symbolic/ref-backend transactionGit ≥ 2.54
head-detachedgit checkout --detachGit ≥ 2.28
worktree-createdghe worktree addGit ≥ 2.39.3
worktree-removedghe worktree removeGit ≥ 2.39.3
worktree-movedghe worktree moveGit ≥ 2.39.3
worktree-lockedghe worktree lockGit ≥ 2.39.3
worktree-unlockedghe worktree unlockGit ≥ 2.39.3
worktree-prunedghe worktree pruneGit ≥ 2.39.3
worktree-repairedghe worktree repairGit ≥ 2.39.3

Git bugs

Some compatibility gaps are caused by Git bugs, rather than limitations in git-hooks-ext. I filed these reports upstream with proposed fixes:

  1. ¹ git branch -m omits the destination ref from the reference-transaction hook.
  2. ² git branch -D and git tag -d report zero OIDs from Git 2.31 onward.

Snapshots recover branch and tag deletions, tracking ref deletions from git remote prune, updates from git notes append, git notes remove and subsequent git stash push commands. On Git 2.54+, they also recover git remote set-head -d and symbolic HEAD transitions. Re-run ghe install after upgrading the extension to update a config-based bridge.

See the compatibility notes for the exact tested versions, method, and raw transaction results.

Notes

Rename detection is best-effort. A rename is emitted only when a deletion and creation have one unique object-ID match within the same ref namespace.

Git 2.28 introduces the required hook, but not every Git command supplies a usable payload. See the command compatibility matrix for measured event availability and command behavior through Git 2.55.

See Install for native packages and the README for complete hook arguments.