fix(dev-infra): skip hook setup in CI
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md
|
||||
2026-07-27-worktree-local-lefthook.md: 2e15f2d242cbcdd56452f3c8ac59bc1e330bc7c0
|
||||
2026-07-27-worktree-local-lefthook.zh.md: 742d48511f6fd46202ac9c0ead476105d9b97370
|
||||
2026-07-27-worktree-local-lefthook.md: 8bfa6da3de33baf247acb9cb58c8d28abd3c501e
|
||||
2026-07-27-worktree-local-lefthook.zh.md: 19f5163ddd855a9a9ca9940da01b93b6ee533e6a
|
||||
|
||||
@@ -12,13 +12,13 @@ Lefthook-generated hooks prefer an absolute binary path captured from the instal
|
||||
|
||||
## Decision
|
||||
|
||||
Hook installation is worktree-scoped. The installer requires Git 2.26 or newer for configuration-scope provenance, upgrades a format-0 repository to format 1, enables `extensions.worktreeConfig`, and assigns the current worktree an absolute `core.hooksPath` at `$GIT_DIR/dsh-hooks`. Before first enabling the repository-wide extension, it inspects the dormant `config.worktree` file for the main worktree and every registered linked worktree, then refuses any settings whose activation would change the current or a sibling worktree. The main worktree receives `$GIT_COMMON_DIR/dsh-hooks`; each linked worktree receives the corresponding directory under `$GIT_COMMON_DIR/worktrees/<id>`. A repository-scoped lock serializes configuration migration and hook writes, including repeated concurrent installs. Each lock records a process ID and random ownership token; release verifies the same file identity and exact record. A dead or invalid lock is never broken automatically, so the diagnostic requires the contributor to confirm no installer is running and remove the lock manually.
|
||||
Hook installation is worktree-scoped. With `CI=true`, the installer returns before Git discovery or mutation because automated jobs do not consume contributor hooks. Otherwise, it requires Git 2.26 or newer for configuration-scope provenance, upgrades a format-0 repository to format 1, enables `extensions.worktreeConfig`, and assigns the current worktree an absolute `core.hooksPath` at `$GIT_DIR/dsh-hooks`. Before first enabling the repository-wide extension, it inspects the dormant `config.worktree` file for the main worktree and every registered linked worktree, then refuses any settings whose activation would change the current or a sibling worktree. The main worktree receives `$GIT_COMMON_DIR/dsh-hooks`; each linked worktree receives the corresponding directory under `$GIT_COMMON_DIR/worktrees/<id>`. A repository-scoped lock serializes configuration migration and hook writes, including repeated concurrent installs. Each lock records a process ID and random ownership token; release verifies the same file identity and exact record. A dead or invalid lock is never broken automatically, so the diagnostic requires the contributor to confirm no installer is running and remove the lock manually.
|
||||
|
||||
The installer recognizes its hook directory with a private ownership marker and updates it idempotently. It inspects the effective scope, origin, and value of `core.hooksPath`, then refuses an unowned directory, every command-scoped path, and every non-owned worktree-scoped path, including values loaded through `config.worktree` includes. It follows conditional includes with Git's parser and refuses a command- or worktree-scoped include whose target provides, or cannot safely be shown not to provide, a hook path; an inactive condition therefore cannot later hide a user-owned path behind the installer's direct value. The same risk in an inherited system, global, or common-repository include requires `DSH_LEFTHOOK_ALLOW_HOOKS_PATH_OVERRIDE=1`, which explicitly opts only the current worktree into Lefthook while other worktrees retain the inherited path. Unrelated conditional includes remain valid. Command-scoped Git configuration is removed from the Lefthook subprocess environment after validation. This opt-in does not attempt to chain arbitrary hook managers.
|
||||
|
||||
Enabling worktree config removes the standard redundant `core.bare=false` value from the common config because false remains Git's default; an explicit `core.worktree` or `core.bare=true`, whether direct or loaded through an active common-config include, is refused for manual migration. Before enabling the extension, the installer follows common-config conditional includes and refuses a target that provides, or cannot safely be shown not to provide, either migration-sensitive key; unrelated conditional includes remain valid. If Lefthook fails during a first install, the installer removes the new worktree override so the prior inherited or common hooks remain active. Legacy files in `$GIT_COMMON_DIR/hooks` are never removed or rewritten by the worktree-local installer.
|
||||
|
||||
[`install-lefthook.spec.ts`](../../../../scripts/install-lefthook.spec.ts) exercises main and linked worktrees, removal independence, repeated and concurrent installs, stale and replaced lock ownership, the Git version boundary, dormant sibling-config refusal, migration keys loaded through active and conditional common-config includes, scoped custom-path refusal and opt-in, active and inactive worktree includes, inherited conditional paths, command-environment isolation, legacy common-hook preservation, and failed-install rollback.
|
||||
[`install-lefthook.spec.ts`](../../../../scripts/install-lefthook.spec.ts) exercises the CI no-op, main and linked worktrees, removal independence, repeated and concurrent installs, stale and replaced lock ownership, the Git version boundary, dormant sibling-config refusal, migration keys loaded through active and conditional common-config includes, scoped custom-path refusal and opt-in, active and inactive worktree includes, inherited conditional paths, command-environment isolation, legacy common-hook preservation, and failed-install rollback.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -28,6 +28,8 @@ Enabling worktree config removes the standard redundant `core.bare=false` value
|
||||
|
||||
**Build a general hook-manager chaining layer.** Ordering, argument forwarding, failure semantics, and upgrades become repository-owned behavior unrelated to Lefthook isolation. The installer instead refuses worktree-specific custom paths and makes the narrower inherited-path override explicit.
|
||||
|
||||
**Whitelist provider-specific CI credential-include paths.** Contributor hooks are unused in CI, so path exemptions would couple installer safety to provider checkout internals and weaken strict validation for contributor installs. The CI no-op avoids repository mutation without any exemptions.
|
||||
|
||||
**Stop installing hooks automatically.** Manual setup avoids shared writes but makes the repository's cheap commit and push checks optional by accident, especially in short-lived agent worktrees.
|
||||
|
||||
## Consequences
|
||||
|
||||
@@ -12,13 +12,13 @@ Lefthook 生成的钩子会优先使用安装时从对应 worktree 记录的绝
|
||||
|
||||
## 决策
|
||||
|
||||
钩子安装以 worktree 为作用域。为了获取配置作用域的来源信息,安装程序要求 Git 2.26 或更高版本;它会将格式版本为 0 的仓库升级到格式版本 1,启用 `extensions.worktreeConfig`,并将当前 worktree 的 `core.hooksPath` 设为指向 `$GIT_DIR/dsh-hooks` 的绝对路径。首次启用这一仓库级扩展前,安装程序会检查主 worktree 与每个已注册关联 worktree 中尚未生效的 `config.worktree` 文件,并拒绝任何一经激活就会改变当前或其他 worktree 的设置。主 worktree 使用 `$GIT_COMMON_DIR/dsh-hooks`;每个关联 worktree 则使用 `$GIT_COMMON_DIR/worktrees/<id>` 下的对应目录。仓库级锁会串行化配置迁移与钩子写入,包括并发触发的重复安装。每个锁都会记录进程 ID 和随机所有权令牌;释放锁时会验证同一个文件身份与完全一致的记录。安装程序绝不会自动破坏所属进程已结束或内容无效的锁,因此诊断会要求贡献者先确认没有安装程序正在运行,再手动移除该锁。
|
||||
钩子安装以 worktree 为作用域。当 `CI=true` 时,安装程序会在探测 Git 或做出任何变更之前返回,因为自动化任务不会使用贡献者钩子。否则,为了获取配置作用域的来源信息,安装程序要求 Git 2.26 或更高版本;它会将格式版本为 0 的仓库升级到格式版本 1,启用 `extensions.worktreeConfig`,并将当前 worktree 的 `core.hooksPath` 设为指向 `$GIT_DIR/dsh-hooks` 的绝对路径。首次启用这一仓库级扩展前,安装程序会检查主 worktree 与每个已注册关联 worktree 中尚未生效的 `config.worktree` 文件,并拒绝任何一经激活就会改变当前或其他 worktree 的设置。主 worktree 使用 `$GIT_COMMON_DIR/dsh-hooks`;每个关联 worktree 则使用 `$GIT_COMMON_DIR/worktrees/<id>` 下的对应目录。仓库级锁会串行化配置迁移与钩子写入,包括并发触发的重复安装。每个锁都会记录进程 ID 和随机所有权令牌;释放锁时会验证同一个文件身份与完全一致的记录。安装程序绝不会自动破坏所属进程已结束或内容无效的锁,因此诊断会要求贡献者先确认没有安装程序正在运行,再手动移除该锁。
|
||||
|
||||
安装程序通过私有所有权标记识别其钩子目录,并以幂等方式更新该目录。它会检查 `core.hooksPath` 的生效作用域、来源和值,并拒绝没有所有权标记的目录、所有命令作用域路径,以及所有非本安装程序所有的 worktree 作用域路径,包括通过 `config.worktree` 中的 include 加载的值。安装程序会用 Git 的解析器跟踪 `includeIf`;若命令作用域或 worktree 作用域的目标配置提供钩子路径,或者无法安全证明它不会提供钩子路径,安装程序就会拒绝继续。因此,安装时未生效的条件日后也无法在安装程序的直接配置值之前隐藏用户自有路径。系统配置、全局配置或共用仓库配置中存在相同风险时,必须设置 `DSH_LEFTHOOK_ALLOW_HOOKS_PATH_OVERRIDE=1`,从而只让当前 worktree 显式启用 Lefthook,其他 worktree 则继续使用继承路径。与钩子无关的 `includeIf` 仍然有效。完成验证后,Lefthook 子进程的环境会移除命令作用域的 Git 配置。这项显式选择不会尝试串联任意钩子管理器。
|
||||
|
||||
启用 worktree 配置时,安装程序会从共用配置中移除标准但冗余的 `core.bare=false`,因为 false 仍是 Git 的默认值;无论共用配置直接设置了 `core.worktree` 或 `core.bare=true`,还是通过当前生效的 include 加载了这些值,安装程序都会拒绝继续并要求手动迁移。启用扩展之前,安装程序会跟踪共用配置中的 `includeIf`;若目标配置提供任一迁移敏感键,或者无法安全证明它不会提供这些键,安装程序就会拒绝继续。与迁移无关的 `includeIf` 仍然有效。若首次安装期间 Lefthook 失败,安装程序会移除新建的 worktree 覆盖,使原有的继承钩子或共用钩子继续生效。worktree 本地安装程序绝不会移除或改写 `$GIT_COMMON_DIR/hooks` 中的旧文件。
|
||||
|
||||
[`install-lefthook.spec.ts`](../../../../scripts/install-lefthook.spec.ts) 覆盖主 worktree 和关联 worktree、移除后的相互独立性、重复与并发安装、陈旧锁与锁所有权被替换、Git 版本边界、拒绝激活其他 worktree 中尚未生效的配置、通过生效及条件式共用配置 include 加载的迁移键、按作用域拒绝自定义路径与显式覆盖、生效及未生效的 worktree include、继承的条件式路径、命令环境隔离、保留旧公共钩子,以及安装失败时的回滚。
|
||||
[`install-lefthook.spec.ts`](../../../../scripts/install-lefthook.spec.ts) 覆盖 CI 下不执行操作的行为、主 worktree 和关联 worktree、移除后的相互独立性、重复与并发安装、陈旧锁与锁所有权被替换、Git 版本边界、拒绝激活其他 worktree 中尚未生效的配置、通过生效及条件式共用配置 include 加载的迁移键、按作用域拒绝自定义路径与显式覆盖、生效及未生效的 worktree include、继承的条件式路径、命令环境隔离、保留旧公共钩子,以及安装失败时的回滚。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -28,6 +28,8 @@ Lefthook 生成的钩子会优先使用安装时从对应 worktree 记录的绝
|
||||
|
||||
**构建通用的钩子管理器串联层。** 执行顺序、参数转发、失败语义和升级都会成为仓库自行负责的行为,却与 Lefthook 隔离无关。因此,安装程序会拒绝 worktree 专属的自定义路径,只将范围更窄的继承路径覆盖设为显式操作。
|
||||
|
||||
**将特定 CI 提供商的凭据 include 路径加入白名单。** CI 不使用贡献者钩子,因此路径豁免会使安装程序的安全性耦合于提供商的检出目录内部结构,并削弱贡献者安装时的严格验证。CI 无操作方案无需任何豁免即可避免修改仓库。
|
||||
|
||||
**停止自动安装钩子。** 手动设置可以避免共享写入,却会使仓库中低成本的提交与推送检查意外变成可选项,短期存在、由 agent(智能体)使用的 worktree 尤其容易受到影响。
|
||||
|
||||
## 后果
|
||||
|
||||
Reference in New Issue
Block a user