Files
STM32F4-Base/.dsh/skills/task-worktree/reference/merge-dirty-main-tree.md
T
wandering1 f08b12a46c docs(技能渐进展开): 五个技能正文逐字外置到 reference/,主文只留索引与指针
按「主文留头量、深规程外置」重写本仓技能,全部是逐字搬移:
搬移前断言原文每条非空行必须出现在新主文或 reference 文件里,
0 丢失才落盘;步骤编号一律不重排(正文有「第 12 步」交叉引用);
每个切点保留原小标题 + 索引(加粗引导句与步骤标题)+ 指针行。

主文变化(上限口径 = 插件写侧 65536 字节,见上一笔 skill-size 钩子):
- id-family-semantic-migration 33392 -> 10968(17%),reference 3 篇
- stale-doc-count-sweep 19811 -> 6945(11%),reference 2 篇
- mem-budget 19963 -> 13042(19%),reference 3 篇
- keil-build 12917 -> 8879(14%),reference 2 篇
- task-worktree 72601 -> 28139(42%),reference 8 篇

task-worktree 本笔同时收录另一会话已写完的 C2/D/G 三节正文——那
三处手改与我的拆分落在同一份文件改动里,不可分离;其内容同样已
随 reference/ 外置,主文只剩标题、索引与指针。

外置语料合计 122211 字节 / 30 篇:围栏数逐篇为偶,无 BOM/CR/U+FFFD。
draw-diagram 与 serial-boot-test 脚本是另一会话的在飞改动,本笔未含。

skill-size 钩子在本笔首次真跑(前一笔无 .dsh/skills 文件可触发)。

gate: pre-commit 全绿(doc-links、id-check-new、check-yaml、
      skill-size、check-added-large-files);30 篇 reference 手工字节
      体检 clean;skill() 逐个复载确认加载的是拆分版。
2026-10-29 15:03:12 +08:00

7.4 KiB
Raw Blame History

C2. done 被「主工作树不干净」挡住,而那份脏改动是别的并发会话留下的

场景(2026-10-29 同一套流程在同一天跑了两遍,两遍都撞上它):任务树里的文档改完、准备合回 v2-on-ui, 主工作树却压着一个自己从没碰过的未提交改动(两次都是 M .dsh/skills/serial-boot-test/scripts/run_boot_test.py, 另一个并发会话/插件留下的)。它既不能提交(不是我的改动,也不知道写没写完),也不能 stash / checkout / reset / 删除(项目级 Never:丢弃别人的工作)。$TW done <ID> 的前置正好卡在这—— 它要求主工作树干净,否则未提交改动会被卷进合并(见 C 第 6 步),于是合并与回收一起被挡。

正解 = 取证 → merge-tree 预演 → 主树手工 --no-ff 合并 → 自证外部文件没被动过 → 照常收工,六步:

  1. 先取证再动手(别一看见脏就下「谁弄坏的」结论)。记下它的 mtime 与 diff 内容,判断与本合并有无交集, 并把合并前的 blob id 与 diff --stat 存进 ci-artifacts/(第 4 步拿它对账):

@T3@bash MAIN="D:/Code/DTU Code/STM32F4-Base" EXT=".dsh/skills/serial-boot-test/scripts/run_boot_test.py" # 外部脏文件的仓库相对路径 mkdir -p ci-artifacts git -C "$MAIN" status --porcelain # 全量脏清单(本会话只认自己那几条) stat -c "%y %s %n" "$MAIN/$EXT" # mtime + 字节数:定性是谁在什么时候改的 git -C "$MAIN" diff -- "$EXT" | cat # 内容性质;与本次合并有无同文件同区域 git -C "$MAIN" hash-object "$MAIN/$EXT" | tee ci-artifacts/ext-blob-before.txt # ← 存下这个 blob id git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-before.txt # ← 以及这份统计(几加几减) @T3@

实测:mtime 落在另一会话的活跃时段内,diff 是「docstring 引号改写」2+/2−,与要合的文档毫无交集 → 定性为与本合并无交集、不丢弃。顺手 git -C "$MAIN" worktree list 看一眼有没有别人中途新建的任务树 (两次里都撞见第二棵树 data-trust-restructure,正是这份外部改动的来路)——这是并发会话的硬证据。 2. merge-tree 预演:先证明能干净合并,再碰主干。$TW done --dry-run 在这条路上同样被前置挡住, 预演只能自己敲(git 2.38+ 的新语法,打印的是结果树 sha):

@T3@bash git -C "MAIN" merge-tree --write-tree v2-on-ui task/<ID>-<slug>; echo MT_RC=? @T3@

判据 = 打印出一个 tree sha 且 RC=0(实测打出 4220906b… + MT_RC=0 才敢往下走)。⚠ 本机 git 太旧没有 这个子命令时退回 $TW done <ID> --dry-run——它的前置也是「主树干净」,可用环境变量 DTU_INTEGRATION_WORKTREE 把集成点指到一棵干净的临时树去演练(见本文开头的环境变量);别为了跑 dry-run 去动主树那份脏改动。 预演报冲突 → 回到冲突本身处理,不许直接 --no-ff 硬合。 3. 主工作树里手工 --no-ff 合并(合并信息走 -F 文件,放在被忽略的 ci-artifacts/;编码口径同 C 第 5 步: UTF-8 无 BOM、正文每行 ≤80 字符;标题格式 merge: task/<ID>-<slug> 合入 <base>):

@T3@bash git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-.txt task/- @T3@

pre-commit 会自己把那份未提交改动 stash 起来、跑完钩子再放回(输出里能看到 [INFO] Stashing unstaged files to …) ——这条不是让你去手工 stash,正好相反:钩子的 stash 往返让它自己走完,你只需在第 4 步验证它把东西原样还回来了。 为什么可以绕开 done(报告里要写清这句):done 的前置要求主工作树干净,而那份外部改动既不能提交也不能 丢弃,所以合并只剩「手工预演 + 手工合并」一条合法路径;被跳过的门禁与审计由第 2 步(预演 sha + RC=0)、 第 4 步(blob id 对账)、G 节(合并后在主树跑全库基线)、第 6 步(lock status)逐项补回。 这不是绕过保护,是把保护要求的证据逐条自己交出来。 4. 自证那份外部改动原样还在(本节唯一的硬证据,两次实测都靠它结案):合并后再跑一次同一对命令, 与第 1 步存的两个值逐项相同:

@T3@bash git -C "$MAIN" hash-object "$MAIN/$EXT" | tee ci-artifacts/ext-blob-after.txt git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-after.txt diff ci-artifacts/ext-blob-before.txt ci-artifacts/ext-blob-after.txt # 空输出才算过 git -C "$MAIN" status --porcelain # 该文件仍是 M:没被顺手提交,也没被清成干净 @T3@

实测:合并前后 blob id 同为 369380ca8eb024b593d872455b6a020797abcd8d、diff --stat 同为 2+/2− → 证明 pre-commit 的 stash 往返没动别人的工作;合进主干的那份文档另按 G 节复核(零残留 + 全库基线)。 blob id 变了 = stash pop 出了半冲突(现场会留下 stash 与冲突标记)→ 立刻停止收工,把两份 blob id 与两份 diff 原样贴进报告交给该文件属主;绝不用 git checkout -- $EXT 抹平(那是销毁别人的工作,项目级 Never)。 5. 收工仍走技能自身的代码路径,别自己手写 rm:先把台账备份成 *.state-bak(后缀与 E 第 6 步统一), 再让脚本自己摘联接:

@T3@bash cp "$WTROOT/.state/.json" "$MAIN/ci-artifacts/.json.state-bak" # 先备份再动 "$PY" "$TW" done # 打「已合入 ,跳过合并,直接清场」→ 先摘 .venv 与 simulator/SDL2 联接 → 删工作树 → 删分支 → unlink 台账 @T3@

实测这条路径能走完(日志:state backup -> ci-artifacts\screenlink-doc-history.json.state-bak + [ok] 已摘除联接 .venv + [ok] 已摘除联接 simulator/SDL2)。若它仍拿「主工作树不干净」挡在合并前(没走到 清场分支),改走 E 节的手工回收路径:台账仍按本步先 cp 成 .state-bak;第 1~2 步的前置换成 「status --porcelain -uall 为空 + 唯一提交为空 + merge-base --is-ancestor 判 MERGED」三条(本次实测三条齐), 第 3 步的联接预扫 + rmdir 摘联接一步不能省;再 git worktree remove(三条前置齐了就不必 --force)、 删分支、git worktree prune。清完再敲一次 done 会得到「任务 不存在(缺台账 …json)」(exit 2)—— 这不是失败(台账已被 unlink;判完成以 git log 为准,见 C 第 7 步)。 6. 复验收工:git -C "$MAIN" worktree list 只剩主树 + 别人的树(那棵不是你的,保留并在报告里点名, 别顺手回收 → E 节);"$PY" "$TW" lock status 打 build 空闲(TTL 1.0h) + bench 空闲(TTL 6.0h) 才收工。

并发会话证据必须写进报告:主树里那份外部脏文件、中途冒出来的第二棵任务树,都要点名——是谁的、 你怎么定性的、对这次合并的影响(本例:与本合并无交集 / 不丢弃 / 不进本次提交),以及你没动它的证据 (blob id 前后一致)。「不能丢弃别人的工作」类问题九成是并发会话造成的,不写清就等于没取证。