You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Proposal: support config-based Git hook installation
#2633
prek can already be invoked this way manually. Support for running hook-impl without --hook-dir was added in #1989, and the cookbook documents global config-based hooks.
However, prek install and prek uninstall still manage traditional hook files. For each event, install_hook_script writes a single <hooks-dir>/<event> file, while uninstall removes that same file when it recognizes the prek marker.
As a result, repeated installations for the same event replace one another:
prek install frontend/ --hook-type pre-commitprek install backend/ --hook-type pre-commit
Only the second invocation remains installed.
This discussion proposes an explicit, repository-local config-based installation mode for Git 2.54+.
Motivation
Config-based installation would let prek:
Coexist with other hook providers without taking ownership of .git/hooks/<event>.
Avoid moving an existing traditional hook to <event>.legacy.
Work without writing to an external core.hooksPath.
Potentially support more than one prek invocation for the same event, for example different configs, workspace roots, or selectors.
Git's model and constraints
Git identifies a configured hook by its friendly-name:
A friendly name is expected to be unique across the parsed Git configuration.
hook.<name>.command is single-valued and uses last-one-wins behavior.
hook.<name>.event is multi-valued.
Repeating an event declaration for the same name can change its execution order.
Configured hooks run before the traditional hook from the hooks directory.
Git does not understand prek selectors, files, exclude, --config, or --cd. Those are merely arguments embedded in the command.
A friendly name also cannot be shared across several Git events for prek today because each command needs an event-specific --hook-type.
Older Git clients do not execute these configured hooks. This matters when the command-line Git and an IDE or GUI use different Git installations.
Proposed initial behavior
Add an explicit installation mode, with the exact CLI name still open for discussion. The examples below use --config-based:
prek install --config-based --hook-type pre-commit
Initially, config-based installation should preserve the current replacement semantics:
There is one default prek installation per Git hook event.
Repeating the same installation updates its command in place.
Changing selectors, config path, or workspace does not silently append another hook.
The traditional file-based mode remains the default until mixed-Git-version compatibility is better understood.
--git-dir and init-templatedir remain file-based.
This gives prek and other tools native multi-hook coexistence without immediately introducing a new user-facing instance-management system.
Hook identity
The following identity strategies have different consequences:
Strategy
Reinstall behavior
Assessment
Git event only
Replaces the existing prek registration
Recommended default
Hash of command, config, or filters
Every argument change creates another active hook
Reject; opaque and leaves stale hooks
Explicit name plus event
Updates one named installation while allowing multiple
Suitable if named installs are added
Filters should not define identity. Two equivalent selections can have different serialized forms, while changing a filter may mean either "update this installation" or "create another installation." prek cannot infer that intent.
If multiple prek installations for one event are added later, they should require an explicit name:
Their identity would be (install name, hook type). Reusing --name frontend updates the frontend installation. Omitting --name continues to target the default installation.
A single installation can already contain multiple selectors, so separate named installations should primarily serve independently managed configs or workspaces, rather than ordinary per-hook file filtering.
Ownership and uninstall
prek should track config records it created explicitly. A friendly-name prefix or a command containing prek hook-impl is not sufficient because users may already have equivalent manual configuration from the cookbook.
Expected behavior:
prek uninstall removes the default managed registration for the selected events, regardless of whether it uses the file or config backend.
A future prek uninstall --name <name> removes only that named installation.
prek uninstall --all removes all locally managed prek registrations and traditional prek shims.
Global, system, included, and manually created config hooks are never removed.
A friendly-name collision with another config scope must be detected rather than silently overriding or merging with it.
Config updates should not reset hook.<name>.enabled or reorder an existing event unnecessarily.
git hook list --show-scope <event> should remain a useful native way to inspect the resulting execution order.
Migration
Installing a config-based hook while a prek-managed traditional shim remains would run prek twice. Config-based installation therefore needs an explicit migration path:
Detect and remove the existing prek-managed shim.
Restore an existing <event>.legacy file to <event>.
Leave unrelated traditional hooks untouched.
Report that Git runs configured prek hooks before the traditional hook.
That final point is an observable ordering change. The current migration shim invokes the legacy hook before prek, while Git's config-based model always runs the traditional hook last.
Compatibility and safety
Fail clearly when config-based installation is requested with Git older than 2.54.
Prefer a capability check over relying only on the version string.
Warn that other Git clients using an older Git binary will not run the configured hook.
Preserve Git-provided arguments and stdin for all supported hook types.
Preserve the installed executable-path/PATH fallback behavior of the current generated shim.
Never use --force to overwrite an unrelated configured hook with the same friendly name.
Suggested scope
The first implementation should support only the default registration per event.
Explicitly named multiple installations can be considered separately once there is a concrete case that cannot be represented cleanly by one prek invocation. This keeps config-based storage independent from the larger question of introducing install profiles.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
Git 2.54 introduced config-based hooks, allowing multiple named commands to run for the same hook event.
prekcan already be invoked this way manually. Support for runninghook-implwithout--hook-dirwas added in #1989, and the cookbook documents global config-based hooks.However,
prek installandprek uninstallstill manage traditional hook files. For each event,install_hook_scriptwrites a single<hooks-dir>/<event>file, whileuninstallremoves that same file when it recognizes the prek marker.As a result, repeated installations for the same event replace one another:
Only the second invocation remains installed.
This discussion proposes an explicit, repository-local config-based installation mode for Git 2.54+.
Motivation
Config-based installation would let prek:
.git/hooks/<event>.<event>.legacy.core.hooksPath.Git's model and constraints
Git identifies a configured hook by its
friendly-name:There are several important constraints in Git's configuration model:
hook.<name>.commandis single-valued and uses last-one-wins behavior.hook.<name>.eventis multi-valued.files,exclude,--config, or--cd. Those are merely arguments embedded in the command.A friendly name also cannot be shared across several Git events for prek today because each command needs an event-specific
--hook-type.Older Git clients do not execute these configured hooks. This matters when the command-line Git and an IDE or GUI use different Git installations.
Proposed initial behavior
Add an explicit installation mode, with the exact CLI name still open for discussion. The examples below use
--config-based:prek install --config-based --hook-type pre-commitInitially, config-based installation should preserve the current replacement semantics:
--git-dirandinit-templatedirremain file-based.This gives prek and other tools native multi-hook coexistence without immediately introducing a new user-facing instance-management system.
Hook identity
The following identity strategies have different consequences:
Filters should not define identity. Two equivalent selections can have different serialized forms, while changing a filter may mean either "update this installation" or "create another installation." prek cannot infer that intent.
If multiple prek installations for one event are added later, they should require an explicit name:
Their identity would be
(install name, hook type). Reusing--name frontendupdates the frontend installation. Omitting--namecontinues to target the default installation.A single installation can already contain multiple selectors, so separate named installations should primarily serve independently managed configs or workspaces, rather than ordinary per-hook file filtering.
Ownership and uninstall
prek should track config records it created explicitly. A friendly-name prefix or a command containing
prek hook-implis not sufficient because users may already have equivalent manual configuration from the cookbook.Expected behavior:
prek uninstallremoves the default managed registration for the selected events, regardless of whether it uses the file or config backend.prek uninstall --name <name>removes only that named installation.prek uninstall --allremoves all locally managed prek registrations and traditional prek shims.hook.<name>.enabledor reorder an existing event unnecessarily.git hook list --show-scope <event>should remain a useful native way to inspect the resulting execution order.Migration
Installing a config-based hook while a prek-managed traditional shim remains would run prek twice. Config-based installation therefore needs an explicit migration path:
<event>.legacyfile to<event>.That final point is an observable ordering change. The current migration shim invokes the legacy hook before prek, while Git's config-based model always runs the traditional hook last.
Compatibility and safety
--forceto overwrite an unrelated configured hook with the same friendly name.Suggested scope
The first implementation should support only the default registration per event.
Explicitly named multiple installations can be considered separately once there is a concrete case that cannot be represented cleanly by one prek invocation. This keeps config-based storage independent from the larger question of introducing install profiles.
References
hook-impl, #1989All reactions