Usage guide

git-hooks-ext

0.4.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 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

*-created is a creation candidate, not proof that the ref did not already exist. Git uses an all-zero old value both for ref creation and for an update that does not require a particular previous value. This bridge maps every zero → value record to *-created, which is usually a creation. Events run only after commit, so the ref already has its new value and git rev-parse cannot distinguish the two cases.

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. When Git reports an all-zero old or new value, the previous or next attachment state is unknown, so only head-updated is emitted. Git can report a zero old value even for an explicit git update-ref --no-deref HEAD detach.

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.

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

Measured on 2026-09-19. The detailed CI matrix covers Git 2.27–2.55, Apple Git 2.39.3, and the files and reftable backends. 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 topic2.28 ≤ Git ≤ 2.30 ²
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 origin❌ ²
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 v12.28 ≤ Git ≤ 2.30 ²
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 push❌
stash-updatedgit update-ref refs/stashGit ≥ 2.28
stash-deletedgit stash clearGit ≥ 2.28
note-createdgit notes addGit ≥ 2.28
note-updatedgit notes append❌
note-updatedgit notes remove❌
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 origin❌
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 --detach❌
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.

git remote prune removes the tracking ref without a semantic deletion. In the tested versions, git notes append, git notes remove, and a second git stash push emit a created event instead of the expected updated event.

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.