@minonl/dia 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bash
2
- # adopt / doctor / gate 回归测试(2026-11-05)
3
- # 覆盖:dry-run 计划、apply 幂等、check 通过、主 checkout 守卫、doctor、revert。
2
+ # adopt / doctor / gate 回归测试(2026-11-06:本机全局钩子模型)
3
+ # 覆盖:dry-run、apply(全局钩子)、check/doctor、主 checkout 守卫、链式调用仓库钩子、
4
+ # 非 adopt 仓透传、敏感扫描、adopt --all、gate、revert。
4
5
  set -uo pipefail
5
6
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
7
  WT="$HERE/../bin/dia-wt"
@@ -8,6 +9,13 @@ TD="$(mktemp -d)"; trap 'rm -rf "$TD"' EXIT
8
9
  PASS=0; FAIL=0
9
10
  ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }; bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
10
11
 
12
+ # 隔离:HOME / 全局 git 配置 / 数据根,避免污染真实机器
13
+ export HOME="$TD/home"; mkdir -p "$HOME"
14
+ export GIT_CONFIG_GLOBAL="$TD/gitconfig"; : > "$GIT_CONFIG_GLOBAL"
15
+ export GIT_CONFIG_NOSYSTEM=1
16
+ export DIA_WT_DATA_ROOT="$TD/data"
17
+ GH="$TD/data/hooks"
18
+
11
19
  R="$TD/repo"; mkdir -p "$R"; git -C "$R" init -q -b main
12
20
  git -C "$R" config user.email t@example.com; git -C "$R" config user.name t
13
21
  echo a > "$R/a.txt"; git -C "$R" add -A; git -C "$R" commit -qm init
@@ -18,16 +26,14 @@ printf '%s' "$out" | grep -q '"defaultBranch":"main"' && ok "dry-run 识别默
18
26
 
19
27
  bash "$WT" adopt "$R" --apply >/dev/null 2>&1 || bad "apply 失败"
20
28
  [ -f "$R/.git/dia/config.json" ] && ok "config 已写" || bad "缺 config"
21
- hp="$(git -C "$R" config --get core.hooksPath)"
22
- case "$hp" in /*) ok "hooksPath 是绝对路径" ;; *) bad "hooksPath=$hp" ;; esac
23
- [ -x "$R/.git/dia/hooks/pre-commit" ] && ok "pre-commit 可执行" || bad "缺 pre-commit"
24
-
25
- md5_before="$(md5 -q "$R/.git/dia/config.json" 2>/dev/null || md5sum "$R/.git/dia/config.json" | cut -d' ' -f1)"
26
- sleep 1
27
- bash "$WT" adopt "$R" --apply >/dev/null 2>&1
28
- md5_after="$(md5 -q "$R/.git/dia/config.json" 2>/dev/null || md5sum "$R/.git/dia/config.json" | cut -d' ' -f1)"
29
- [ "$md5_before" != "$md5_after" ] && ok "重复 apply 会刷新时间戳(幂等语义:不重复建目录/钩子)" || bad "重复 apply 无变化"
30
-
29
+ [ "$(git config --global --get core.hooksPath)" = "$GH" ] && ok "全局 hooksPath 指向 $GH" || bad "全局 hooksPath 未设置"
30
+ for h in pre-commit pre-push post-checkout; do [ -x "$GH/$h" ] || bad "缺全局钩子 $h"; done
31
+ ok "全局三钩子可执行"
32
+ [ -z "$(git -C "$R" config --local --get core.hooksPath || true)" ] && ok "不再设置仓库本地 hooksPath" || bad "仍设了本地 hooksPath"
33
+ python3 -c 'import json,sys;d=json.load(open(sys.argv[1]));sys.exit(0 if d.get("hooksManager")=="global" else 1)' "$R/.git/dia/config.json" \
34
+ && ok "config 记录 hooksManager=global" || bad "hooksManager 未记录"
35
+
36
+ bash "$WT" adopt "$R" --apply >/dev/null 2>&1 && ok "重复 apply 幂等" || bad "重复 apply 失败"
31
37
  bash "$WT" adopt "$R" --check >/dev/null 2>&1 && ok "check 通过" || bad "check 失败"
32
38
  bash "$WT" doctor "$R" >/dev/null 2>&1 && ok "doctor 通过" || bad "doctor 失败"
33
39
 
@@ -35,119 +41,45 @@ echo b > "$R/b.txt"; git -C "$R" add b.txt
35
41
  if git -C "$R" commit -qm should-block 2>/dev/null; then bad "主 checkout main 提交未被拦截"; else ok "主 checkout main 提交被拦截"; fi
36
42
  git -C "$R" reset -q
37
43
 
38
- # ── 推送前敏感扫描(PII/凭据形态)──
39
- BR="$TD/remote.git"; git init -q --bare "$BR"
40
- git -C "$R" remote add origin "$BR" 2>/dev/null || true
41
- git --git-dir="$BR" fetch -q "$R" main:main 2>/dev/null || true
42
- git -C "$R" fetch -q origin 2>/dev/null || true
43
- git -C "$R" worktree add -q "$TD/wt" -b feat/scan 2>/dev/null || true
44
- BAD_HOME="/Users"; echo "$BAD_HOME/alice/secret" > "$TD/wt/bad.txt"
45
- git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm bad
46
- if git -C "$TD/wt" push -q origin feat/scan 2>/dev/null; then bad "敏感推送未被拦截"; else ok "敏感推送被拦截"; fi
47
- echo ok > "$TD/wt/bad.txt" && git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm fix
48
- git -C "$TD/wt" push -q origin feat/scan 2>/dev/null && ok "干净推送放行" || bad "干净推送被拒"
49
-
50
- # 误报回归:`$TD/home/kits/...` 这种相对片段不该被判成 /home/<user>
51
- printf 'path=$TD/home/kits/dia-msg/dia.json\n' > "$TD/wt/vars.txt"
52
- git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm vars
53
- git -C "$TD/wt" push -q origin feat/scan 2>/dev/null && ok "相对片段 home/ 不误报" || bad "相对片段被误判为敏感路径"
54
-
55
- bash "$WT" adopt "$R" --revert >/dev/null 2>&1 || bad "revert 失败"
56
- [ -z "$(git -C "$R" config --get core.hooksPath || true)" ] && ok "revert 还原 hooksPath" || bad "revert 未还原"
57
- [ ! -d "$R/.git/dia" ] && ok "revert 删除 dia 目录" || bad "dia 目录残留"
58
-
59
- # ── 原 hooksPath 保留 + 二次 apply 不改写 hooksPrevious(review P2-7)──────────
60
- R2="$TD/repo2"; mkdir -p "$R2" "$TD/orighooks"; git -C "$R2" init -q -b main
44
+ # 链式:仓库自己的 .git/hooks/pre-commit 仍要执行(simple-git-hooks 等管理器就写这里)
45
+ printf '#!/bin/sh\necho chained >> "$(git rev-parse --show-toplevel)/chain.log"\n' > "$R/.git/hooks/pre-commit"
46
+ chmod +x "$R/.git/hooks/pre-commit"
47
+ git -C "$R" worktree add -q -b feat/y "$TD/repo-worktree--y" main
48
+ echo c > "$TD/repo-worktree--y/c.txt"; git -C "$TD/repo-worktree--y" add c.txt
49
+ git -C "$TD/repo-worktree--y" commit -qm feat-y >/dev/null 2>&1
50
+ [ -f "$TD/repo-worktree--y/chain.log" ] && ok "仓库自己的钩子被链式执行" || bad "仓库钩子未执行"
51
+ rm -f "$TD/repo-worktree--y/chain.log"
52
+
53
+ # 本地 hooksPath(husky/.githooks)优先于全局 → 守卫不生效,但 adopt 不得破坏它
54
+ R2="$TD/repo2"; mkdir -p "$R2/.githooks"; git -C "$R2" init -q -b main
61
55
  git -C "$R2" config user.email t@example.com; git -C "$R2" config user.name t
62
56
  echo a > "$R2/a.txt"; git -C "$R2" add -A; git -C "$R2" commit -qm init
63
- printf '#!/bin/sh\nexit 0\n' > "$TD/orighooks/pre-commit"; chmod +x "$TD/orighooks/pre-commit"
64
- git -C "$R2" config --local core.hooksPath "$TD/orighooks"
65
- bash "$WT" adopt "$R2" --apply >/dev/null 2>&1 || bad "R2 apply 失败"
66
- hp1="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$R2/.git/dia/config.json")"
67
- [ "$hp1" = "$TD/orighooks" ] && ok "首次 apply 记录 hooksPrevious" || bad "hooksPrevious=$hp1"
68
- bash "$WT" adopt "$R2" --apply >/dev/null 2>&1
69
- hp2="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$R2/.git/dia/config.json")"
70
- [ "$hp2" = "$TD/orighooks" ] && ok "二次 apply 不覆盖 hooksPrevious" || bad "hooksPrevious 被改成 $hp2"
71
- bash "$WT" adopt "$R2" --revert >/dev/null 2>&1 || bad "R2 revert 失败"
72
- [ "$(git -C "$R2" config --get core.hooksPath)" = "$TD/orighooks" ] && ok "revert 还原原 hooksPath" || bad "revert 未还原原 hooksPath"
73
- # hooksPath 被第三方改动 → revert 拒绝(不抢配置)
57
+ git -C "$R2" config --local core.hooksPath .githooks
74
58
  bash "$WT" adopt "$R2" --apply >/dev/null 2>&1
75
- git -C "$R2" config --local core.hooksPath "$TD/orighooks"
76
- if bash "$WT" adopt "$R2" --revert >/dev/null 2>&1; then bad "hooksPath 被改动后 revert 仍执行"; else ok "hooksPath 被改动后 revert 拒绝"; fi
77
- [ -f "$R2/.git/dia/config.json" ] && ok "拒绝 revert 时保留 config 备份" || bad "拒绝 revert 却删了 config"
78
- git -C "$R2" config --local core.hooksPath "$R2/.git/dia/hooks"; bash "$WT" adopt "$R2" --revert >/dev/null 2>&1
59
+ [ "$(git -C "$R2" config --get core.hooksPath)" = ".githooks" ] && ok "保留仓库本地 hooksPath(husky 等)" || bad "破坏了本地 hooksPath"
60
+ bash "$WT" doctor "$R2" 2>&1 | grep -q "本地 hooksPath" && ok "doctor 提示本地 hooksPath 覆盖全局" || bad "doctor 未提示覆盖"
79
61
 
80
- # ── 自定义默认分支受保护(review P1-1 + P1-2)────────────────────────────────
81
- R3="$TD/repo3"; mkdir -p "$R3"; git -C "$R3" init -q -b trunk
62
+ # 未 adopt 的仓:全局钩子纯透传(主 checkout 可正常提交)
63
+ R3="$TD/repo3"; mkdir -p "$R3"; git -C "$R3" init -q -b main
82
64
  git -C "$R3" config user.email t@example.com; git -C "$R3" config user.name t
83
- echo a > "$R3/a.txt"; git -C "$R3" add -A; git -C "$R3" commit -qm init
84
- bash "$WT" adopt "$R3" --apply --default-branch trunk >/dev/null 2>&1 || bad "自定义分支 apply 失败"
85
- bash "$WT" adopt "$R3" --check >/dev/null 2>&1 && ok "自定义默认分支 check 通过" || bad "自定义默认分支 check 失败"
86
- bash "$WT" doctor "$R3" >/dev/null 2>&1 && ok "自定义默认分支 doctor 通过" || bad "自定义默认分支 doctor 失败"
87
- echo b > "$R3/b.txt"; git -C "$R3" add b.txt
88
- if git -C "$R3" commit -qm should-block 2>/dev/null; then bad "trunk 提交未被拦截"; else ok "自定义默认分支 trunk 提交被拦截"; fi
89
- git -C "$R3" reset -q
65
+ echo a > "$R3/a.txt"; git -C "$R3" add -A
66
+ git -C "$R3" commit -qm init >/dev/null 2>&1 && ok "未 adopt 仓不受守卫影响" || bad "未 adopt 仓被误拦"
90
67
 
91
- # ── doctor:未 adopt / 无参数(review P1-4)──────────────────────────────────
92
- bash "$WT" doctor "$R3" >/dev/null 2>&1 || true
93
- R4="$TD/repo4"; mkdir -p "$R4"; git -C "$R4" init -q -b main
94
- git -C "$R4" config user.email t@example.com; git -C "$R4" config user.name t
95
- echo a > "$R4/a.txt"; git -C "$R4" add -A; git -C "$R4" commit -qm init
96
- if bash "$WT" doctor "$R4" >/dev/null 2>&1; then bad "未 adopt 的仓 doctor 误报通过"; else ok "未 adopt 的仓 doctor 失败"; fi
97
- bash "$WT" doctor >/dev/null 2>&1; [ $? = 2 ] && ok "doctor 无参数 exit 2" || bad "doctor 无参数未 exit 2"
98
-
99
- # ── pre-push 守卫(主 checkout 拒绝 / worktree 放行)──────────────────────────
100
- R5="$TD/repo5"; ORIGIN5="$TD/origin5.git"; git init -q --bare "$ORIGIN5"
101
- mkdir -p "$R5"; git -C "$R5" init -q -b main
102
- git -C "$R5" config user.email t@example.com; git -C "$R5" config user.name t
103
- echo a > "$R5/a.txt"; git -C "$R5" add -A; git -C "$R5" commit -qm init
104
- git -C "$R5" remote add origin "$ORIGIN5"; git -C "$R5" push -q origin main
105
- git -C "$R5" fetch -q origin
106
- git -C "$R5" symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
107
- bash "$WT" adopt "$R5" --apply >/dev/null 2>&1 || bad "R5 apply 失败"
108
- # 主 checkout 的 pre-push 拒绝的是「从这里发起的任何 push」(含 feature 分支),不需要脏提交
109
- git -C "$R5" branch feat/push-probe origin/main
110
- if git -C "$R5" push origin feat/push-probe >/dev/null 2>&1; then bad "主 checkout push 未被拦截"; else ok "主 checkout push 被拦截"; fi
111
- git -C "$R5" branch -D feat/push-probe >/dev/null 2>&1 || true
112
- git -C "$R5" worktree add -q -b feat/x "$TD/repo5-worktree--x" main
113
- echo b > "$TD/repo5-worktree--x/b.txt"; git -C "$TD/repo5-worktree--x" add b.txt
114
- git -C "$TD/repo5-worktree--x" commit -qm feat-x 2>/dev/null
115
- if git -C "$TD/repo5-worktree--x" push -q origin feat/x >/dev/null 2>&1; then ok "worktree push 放行"; else bad "worktree push 被误拦"; fi
116
-
117
- # ── template 生效(P2-6):adopt 后 create 按 config.template 推导路径 ─────────
118
- out="$(bash "$WT" create "$R5" t1 --dry-run 2>&1 || true)"
119
- printf '%s' "$out" | grep -q "repo5-worktree--t1" && ok "默认模板 create 路径" || bad "默认模板 create: $out"
120
- bash "$WT" adopt "$R5" --apply --template '{{parent}}/{{repo}}.wt.{{feature}}' >/dev/null 2>&1 || bad "template apply 失败"
121
- out="$(bash "$WT" create "$R5" t1 --dry-run 2>&1 || true)"
122
- printf '%s' "$out" | grep -q "repo5.wt.t1" && ok "自定义模板 create 路径生效" || bad "自定义模板: $out"
123
- bash "$WT" adopt "$R5" --apply >/dev/null 2>&1
124
-
125
- # ── 过期 origin/HEAD(远端分支已删)不误判默认分支 ──────────────────────────
126
- R6="$TD/repo6"; mkdir -p "$R6"; git -C "$R6" init -q -b main
127
- git -C "$R6" config user.email t@example.com; git -C "$R6" config user.name t
128
- echo a > "$R6/a.txt"; git -C "$R6" add -A; git -C "$R6" commit -qm init
129
- git -C "$R6" symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/gone
130
- out="$(bash "$WT" adopt "$R6" --json)"
131
- printf '%s' "$out" | grep -q '"defaultBranch":"main"' && ok "过期 origin/HEAD 回落到 main" || bad "过期 origin/HEAD: $out"
132
-
133
- # ── 钩子链:原 hooksPath(.githooks / husky)不被吞掉 ─────────────────────────
134
- R7="$TD/repo7"; mkdir -p "$R7/.githooks"; git -C "$R7" init -q -b main
135
- git -C "$R7" config user.email t@example.com; git -C "$R7" config user.name t
136
- printf '#!/bin/sh\necho chained >> "$(git rev-parse --show-toplevel)/chain.log"\n' > "$R7/.githooks/pre-commit"
137
- chmod +x "$R7/.githooks/pre-commit"
138
- echo a > "$R7/a.txt"; git -C "$R7" add -A; git -C "$R7" commit -qm init
139
- git -C "$R7" config --local core.hooksPath .githooks
140
- bash "$WT" adopt "$R7" --apply >/dev/null 2>&1 || bad "R7 apply 失败"
141
- python3 -c 'import json,sys;d=json.load(open(sys.argv[1]));sys.exit(0 if d.get("hooksChain")==".githooks" else 1)' "$R7/.git/dia/config.json" \
142
- && ok "config 记录 hooksChain" || bad "hooksChain 未记录"
143
- git -C "$R7" worktree add -q -b feat/y "$TD/repo7-worktree--y" main
144
- echo b > "$TD/repo7-worktree--y/b.txt"; git -C "$TD/repo7-worktree--y" add b.txt
145
- git -C "$TD/repo7-worktree--y" commit -qm feat-y >/dev/null 2>&1
146
- [ -f "$TD/repo7-worktree--y/chain.log" ] && ok "原 pre-commit 被链式执行" || bad "原 pre-commit 未执行(钩子被吞)"
147
- bash "$WT" adopt "$R7" --revert >/dev/null 2>&1
148
- [ "$(git -C "$R7" config --get core.hooksPath)" = ".githooks" ] && ok "revert 还原原 hooksPath(相对)" || bad "revert 未还原 .githooks"
149
-
150
- # ── adopt --all:批量主 checkout,跳过 worktree 目录 ─────────────────────────
68
+ # 敏感扫描:adopted 仓的 worktree 推送带绝对路径 → 阻断;相对片段不误报
69
+ BR="$TD/remote.git"; git init -q --bare "$BR"
70
+ git -C "$R" remote add origin "$BR" 2>/dev/null || true
71
+ git --git-dir="$BR" fetch -q "$R" main:main 2>/dev/null || true
72
+ git -C "$R" fetch -q origin 2>/dev/null || true
73
+ BAD="$(printf '/%s/alice/secret' Users)"; echo "$BAD" > "$TD/repo-worktree--y/bad.txt"
74
+ git -C "$TD/repo-worktree--y" add bad.txt
75
+ git -C "$TD/repo-worktree--y" commit -qm bad >/dev/null 2>&1
76
+ if git -C "$TD/repo-worktree--y" push -q origin feat/y 2>/dev/null; then bad "敏感推送未被拦截"; else ok "敏感推送被拦截"; fi
77
+ git -C "$TD/repo-worktree--y" reset -q --hard HEAD~1 # 丢掉含敏感内容的提交
78
+ printf 'path=$TD/home/kits/dia.json\n' > "$TD/repo-worktree--y/vars.txt"
79
+ git -C "$TD/repo-worktree--y" add vars.txt && git -C "$TD/repo-worktree--y" commit -qm vars >/dev/null 2>&1
80
+ git -C "$TD/repo-worktree--y" push -q origin feat/y 2>/dev/null && ok "相对片段 home/ 不误报" || bad "相对片段被误判"
81
+
82
+ # adopt --all:只处理主 checkout,跳过 *-worktree--*
151
83
  AR="$TD/allroot"; mkdir -p "$AR/a" "$AR/b"
152
84
  for r in a b; do
153
85
  git -C "$AR/$r" init -q -b main
@@ -155,35 +87,27 @@ for r in a b; do
155
87
  echo x > "$AR/$r/x.txt"; git -C "$AR/$r" add -A; git -C "$AR/$r" commit -qm init
156
88
  done
157
89
  git -C "$AR/a" worktree add -q -b feat/w "$AR/a-worktree--w" main
158
- out="$(DIA_WT_REPOS_ROOT="$AR" DIA_WT_DATA_ROOT="$TD/dr2" bash "$WT" adopt --all --apply --json 2>&1)"
90
+ out="$(DIA_WT_REPOS_ROOT="$AR" bash "$WT" adopt --all --apply --json 2>&1)"
159
91
  printf '%s' "$out" | grep -q '"repos":2,"ok":2,"fail":0' && ok "adopt --all 覆盖 2 个主 checkout" || bad "adopt --all: $out"
160
- for r in a b; do
161
- [ -n "$(git -C "$AR/$r" config --get core.hooksPath || true)" ] && ok "adopt --all: $r 已配 hooksPath" || bad "adopt --all: $r 未配置"
162
- done
163
92
  [ ! -d "$AR/a-worktree--w/.git/dia" ] && ok "adopt --all 跳过 worktree 目录" || bad "adopt --all 误改 worktree"
164
- out="$(DIA_WT_REPOS_ROOT="$AR" DIA_WT_DATA_ROOT="$TD/dr4" bash "$WT" doctor --all --json 2>/dev/null)"
165
- printf '%s' "$out" | grep -q "$AR/a" && ok "doctor --all 覆盖 DIA_WT_REPOS_ROOT" || bad "doctor --all 未覆盖自定义仓根"
166
- DIA_WT_REPOS_ROOT="$TD/empty-root" DIA_WT_DATA_ROOT="$TD/dr3" bash "$WT" adopt --all >/dev/null 2>&1
93
+ out="$(DIA_WT_REPOS_ROOT="$AR" bash "$WT" doctor --all --json 2>/dev/null)"
94
+ printf '%s' "$out" | grep -q "$AR/a" && ok "doctor --all 覆盖自定义仓根" || bad "doctor --all 未覆盖"
95
+ DIA_WT_REPOS_ROOT="$TD/empty-root" bash "$WT" adopt --all >/dev/null 2>&1
167
96
  [ $? = 2 ] && ok "adopt --all 空结果 exit 2" || bad "adopt --all 空结果未 exit 2"
168
97
 
169
- # ── doctor --fix:钩子被覆盖后能无损重建 ────────────────────────────────────
170
- R8="$TD/repo8"; mkdir -p "$R8"; git -C "$R8" init -q -b main
171
- git -C "$R8" config user.email t@example.com; git -C "$R8" config user.name t
172
- echo a > "$R8/a.txt"; git -C "$R8" add -A; git -C "$R8" commit -qm init
173
- bash "$WT" adopt "$R8" --apply >/dev/null 2>&1
174
- rm -f "$R8/.git/dia/hooks/post-checkout"
175
- printf '#!/bin/sh\nexit 0\n' > "$R8/.git/dia/hooks/pre-commit"; chmod +x "$R8/.git/dia/hooks/pre-commit"
176
- if bash "$WT" doctor "$R8" >/dev/null 2>&1; then bad "钩子被覆盖后 doctor 仍通过"; else ok "钩子被覆盖后 doctor 报失败"; fi
177
- bash "$WT" doctor "$R8" --fix >/dev/null 2>&1 || bad "doctor --fix 失败"
178
- bash "$WT" doctor "$R8" >/dev/null 2>&1 && ok "doctor --fix 后恢复 ok" || bad "doctor --fix 后仍失败"
179
- [ -x "$R8/.git/dia/hooks/post-checkout" ] && ok "doctor --fix 补回 post-checkout" || bad "post-checkout 未补回"
98
+ # gate
99
+ rm -f "$TD/repo-worktree--y/chain.log"
100
+ if bash "$WT" gate "$R" y >/tmp/gate.log 2>&1; then ok "gate 成功"; else bad "gate 失败: $(tail -2 /tmp/gate.log | tr "\n" " ")"; fi
101
+ if bash "$WT" gate "$R" yb >/dev/null 2>&1; then bad "gate 子串误匹配"; else ok "gate 精确匹配 feature"; fi
102
+ echo dirty >> "$TD/repo-worktree--y/c.txt"
103
+ if bash "$WT" gate "$R" y >/dev/null 2>&1; then bad "worktree 脏时 gate 通过"; else ok "worktree 脏时 gate 失败"; fi
104
+ git -C "$TD/repo-worktree--y" checkout -q -- c.txt
180
105
 
181
- # ── gate:成功 / 失败(P1-3 精确匹配)────────────────────────────────────────
182
- bash "$WT" gate "$R5" x >/dev/null 2>&1 && ok "gate 成功" || bad "gate 失败"
183
- if bash "$WT" gate "$R5" xb >/dev/null 2>&1; then bad "gate 子串误匹配(x 命中 xb)"; else ok "gate 精确匹配 feature"; fi
184
- echo dirty >> "$TD/repo5-worktree--x/b.txt"
185
- if bash "$WT" gate "$R5" x >/dev/null 2>&1; then bad "worktree 脏时 gate 通过"; else ok "worktree 脏时 gate 失败"; fi
186
- git -C "$TD/repo5-worktree--x" checkout -q -- b.txt
106
+ # revert:撤本仓配置,保留全局钩子
107
+ bash "$WT" adopt "$R" --revert >/dev/null 2>&1 || bad "revert 失败"
108
+ [ ! -f "$R/.git/dia/config.json" ] && ok "revert 删除本仓配置" || bad "配置残留"
109
+ [ -x "$GH/pre-commit" ] && ok "revert 不影响全局钩子" || bad "revert 误删全局钩子"
110
+ git -C "$R" worktree remove --force "$TD/repo-worktree--y" >/dev/null 2>&1 || true
187
111
 
188
112
  echo; echo "── PASS $PASS · FAIL $FAIL ──"
189
113
  [ "$FAIL" = 0 ]