Skip to content

fix(commit-message): 尊重已选仓库 repoRoot,修复多仓库工作区下的 not-a-repo - #10

Merged
KannaKuron merged 1 commit into
KannaKuron:mainfrom
pardSun:fix/commit-message-repo-root
Oct 9, 2026
Merged

KannaKuron merged 1 commit into
KannaKuron:mainfrom
pardSun:fix/commit-message-repo-root

Conversation

@pardSun

@pardSun pardSun commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

问题

工作区根目录本身不是仓库、其下并列多个仓库时(本机工作区 D:\workspace\chemJobs-project 下有 ChemJobs、chemjobsapp),仓库选择器一切正常 —— 分支树 / 提交图谱 / 变更 / 普通提交与推送都能用 —— 但点提交框旁的 ✨ AI 生成提交信息必然失败:

生成失败: [not-a-repo] not a git repository: D:\workspace\chemJobs-project

复现(直接打宿主路由;DSH 桌面 0.2.0-rc.2 / Windows / dsh-ide-git 0.14.3)

POST /dsh-ide-git/api/commit-message
{"cwd":"D:/workspace/chemJobs-project","repoRoot":"D:/workspace/chemJobs-project/ChemJobs","sessionId":"<会话>"}
→ 409 {"code":"not-a-repo","message":"not a git repository: D:\\workspace\\chemJobs-project"}

# 改成 cwd 直接指向仓库:能进到仓库(干净仓库在调模型前就返回)
{"cwd":"D:/workspace/chemJobs-project/ChemJobs","sessionId":"<会话>"}
→ 400 {"code":"bad-request","message":"nothing to describe: stage or edit something first"}

也就是说:该仓库本身完全可用,只有 AI 这一条路径拿不到它。

根因:repoRoot 在这条链路上漏了两次

repoRoot 是「用户在仓库选择器里选中的仓库」。其它所有 git 调用都带着它 —— 宿主侧 20 余处统一 await repoRootOf(cwd, payload);客户端侧统一 Object.assign({}, base, …),其中 base = { cwd, repoRoot }。只有这条链路例外:

  1. 宿主 src/index.js → commitMessage():只取 cwdOf(payload),随后的 summary({ cwd }) / diff({ cwd }) 都不带 repoRoot。
  2. 客户端 src/client.js → aiRun():手工拼 payload { cwd, sessionId, model, reasoningEffort, prompt } 时漏了 repoRoot。

于是宿主拿工作区根去跑 git rev-parse --show-toplevel → 不是仓库 → 409 not-a-repo。

我把客户端全部 9 个 request(...) 调用点都过了一遍,漏 repoRoot 的只有这一处(repos 是列仓库、commit-models / commit-efforts 是取模型目录,本就不该带;NativePreviewPanel 的 diff 在调用前就赋了 payload.repoRoot)。

改动

  • 宿主:commitMessage() 先 const repoRoot = await repoRootOf(cwd, payload),再把 { cwd, repoRoot } 传给 summary / diff(与其余各处同一套写法)。
  • 客户端:Panel 把 base 下传给 ChangesPanel;aiRun() 改用 Object.assign({}, props.base, { … })。

测试(都已在未修复代码上验证为失败)

  • tests/api.test.mjs 新增:工作区 cwd 不是仓库 + repoRoot 指向一个干净仓库 → 400 nothing to describe;不带 repoRoot 时仍 409 not-a-repo(守卫没有被放宽)。刻意选干净仓库:它在调用模型之前就返回,所以测试不消耗任何模型额度。
  • tests/smoke.mjs 新增:静态断言 aiRun 走 Object.assign 且带上 props.base,并断言 Panel 确实把 base 下传给了 ChangesPanel(没被下传的 prop 读出来是 undefined,正是这次漏掉的形状)。

对未修复代码:api 侧 actual: 409, expected: 400;smoke 侧 payload 断言不匹配。修复后两者转绿。

验证

  • npm test 106/106(基线 104/104;+2 为本 PR 新增)。
  • 真机端到端(DSH 桌面 0.2.0-rc.2 / Windows,装在真实 profile 里):修复前同一路由带 repoRoot 返回 409 not-a-repo;修复后同一请求返回 400 nothing to describe(证明已正确解析到所选仓库),不带 repoRoot 时仍返回 409。
  • scripts/ssr-check.mjs:在未改动的 HEAD 上同样报错(Objects are not valid as a React child,react-dom 从全局 dsh 安装解析而来,本机没有仓库内 node_modules),因此这一点不是本改动的证据,也不该算门槛。需要的话我可以按要求搭隔离实例再跑 layout-probe / conversation-probe。

备注

CHANGELOG 与版本号留给你在发版时填(按 AGENTS.md 的发版纪律);要我补一版条目说一声即可。

AI 生成提交信息是唯一没把「已选仓库」(repoRoot) 传到宿主的链路,工作区
根不是仓库时(并列多个仓库)必然失败:

- 宿主半 `commitMessage()`:只用 `cwdOf(payload)`,随后 `summary({ cwd })`
  / `diff({ cwd })` 都不带 repoRoot;而宿主里其余各处 git 调用统一走
  `repoRootOf(cwd, payload)`。于是 `git rev-parse --show-toplevel` 在工作区
  根上失败 → 409 `not-a-repo`。
- 客户端半 `aiRun()`:手工拼 payload `{ cwd, sessionId, model,
  reasoningEffort, prompt }` 时漏了 repoRoot,而其它调用一律走
  `Object.assign({}, base, …)`(`base = { cwd, repoRoot }`)。

修复:宿主按 `repoRootOf(cwd, payload)` 解析仓库并把 `{ cwd, repoRoot }`
传给 summary/diff;客户端把 `base` 下传 ChangesPanel 并用于该请求。

回归测试两处,均已在未修复代码上验证为**失败**:

- `tests/api.test.mjs`:工作区 cwd 不是仓库 + repoRoot 指向一个干净仓库
  → 400 `nothing to describe`(修复前 409 `not-a-repo`,因为干净仓库在调
  模型前就返回,测试不消耗任何模型调用);不带 repoRoot 时仍 409,守卫
  没有被放宽。
- `tests/smoke.mjs`:静态断言 `aiRun` 走 `Object.assign` 且带上
  `props.base`,并断言 Panel 确实把 `base` 下传给了 ChangesPanel。

验证:`npm test` **106/106**(原 104/104 基线);真机 DSH 桌面 0.2.0-rc.2
上,同一路由带 repoRoot 由修复前的 409 `not-a-repo` 变为 400
`nothing to describe`,不带 repoRoot 仍为 409。

@KannaKuron KannaKuron left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified end to end — LGTM.

宿主侧(src/index.js)

  • commitMessage() 补上的 const repoRoot = await repoRootOf(cwd, payload) 与其余 30 处调用点完全同构;summary(scope) / diff(scope) 走 { cwd, repoRoot },两函数内部各自的 repoRootOf 直接命中 payload.repoRoot 快路径(还省掉了三次重复 rev-parse)。
  • 守卫没有被放宽:repoRootOf 零改动,不带 repoRoot 时仍从 cwd 解析、失败仍 409 not-a-repo —— 新 api 测试显式断言了这一点。
  • commitRouteOf(payload) 只读 model/sessionId,不受影响;commit-message 不在 WRITE_METHODS(只读 git + 调模型),无需登记。

客户端侧(src/client.js)

  • base 恰好是 { cwd, repoRoot } 两个键(8448 的 useMemo),Object.assign({}, props.base, {…}) 不会扩散多余字段;repoRoot 为 undefined 时宿主侧 !== undefined 检查正确回退。
  • E(ChangesPanel, { …, base: base }) 与既有 props 传法一致;ChangesPanel 唯一渲染点在 Panel 内,新加的 props.base === undefined 回退分支实际不可达,纯防御、无害。
  • 核对了其余 8 处 request 调用全部已是 Object.assign({}, base, …),本 PR 后 aiRun 与家族写法统一;repos / commit-models / commit-efforts 确实本就不该带。

测试

  • npm test 106/106(基线 104 + 本 PR 2 个新测试)。
  • 独立复现了你声明的「未修复即失败」:checkout main 的 src 两个文件、保留 PR 测试再跑 → 恰好这两个新测试失败(104 pass / 2 fail),恢复后转绿 —— 测试有效性属实。
  • api 新测试断言 seen.length === 0(fake stream 零调用)验证了「干净仓库在调模型前返回」的设计,不消耗模型额度,严谨。
  • smoke 新测试的切片锚点(E(ChangesPanel, { / onCommit:)在全文唯一,断言稳健。
  • 顺带:本机(有 web profile)scripts/ssr-check.mjs 4 项渲染全绿 —— 印证你在 PR 里说的失败是本机无 profile 的环境问题,不算门槛;本机通过反而额外确认客户端半改动无 SSR 渲染回归。

按仓库惯例 squash merge + 删分支。感谢这份分析扎实的 PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants