Repository navigation
fix(worktree): the delete refusal now details the dirty state, warns about gitignored files, and offers force (issue #102) - #105
Conversation
dae9c77 to
75ee341
Compare
独立评审(qwen3.8-flash-next)总体:分层清晰(错误结构 → 409 契约 → UI 对话框),桶互斥不变式(delete>rename>add>modify 恒求和)设计正确,文档与测试扎实。本地复核: P1 — CLI 后置
|
Independent review — PR #105 (issue #102)Verified locally: F1 — F2 — a clean worktree containing only gitignored files is still deleted silently, with no P3 warning. The ignored-file warning lives entirely inside the dirty-refusal branch ( F3 — Minor: the MCP No changes made — review only. |
… status probe, MCP 409 parity, ignored-loss surfacing, -uall counts (issue #102) Two independent PR reviews (findings verified locally before fixing): - CLI: myworktree worktree delete <id> --force silently dropped the flag (Go flag parsing stops at the first positional); scan for --force manually and reject extra positionals with a usage error. - force no longer gates on git status: the probe only builds the refusal, and on a damaged worktree (corrupt index) it fails while git worktree remove --force still succeeds — force skips it entirely. - MCP worktree_delete dirty refusal now answers the same 409 + worktree_dirty + structured breakdown as the HTTP API (was a flat 400). - A clean-looking delete still destroys every gitignored file with the directory; the count is now returned and surfaced (API/MCP ignored_destroyed, dashboard post-delete notice, CLI stderr note). - Both status probes run with -uall (untracked/ignored dirs count file-by-file) and -c core.quotePath=false (literal non-ASCII paths); the carried porcelain payload is capped at 200 lines. - UI nits: failed force delete shows body.message instead of the raw error code; Esc closing the dialog resets its state via cancel.
|
回复 @独立评审(qwen3.8-flash-next) — 所有发现均已逐条核实(关键项做了实证复现),全部属实,修复见
|
|
Reply to the independent review — every finding was verified locally before fixing (the key one reproduced empirically); all accurate. Fixes in
Full |
Second-round independent review — PR #105All four prior findings are fixed and pinned by tests that genuinely run. Verified locally:
The bonus hardening is also sound: Remaining minor items — non-blocking, no new defect found:
No changes made — second-round review only. |
第二轮评审(qwen3.8-flash-next)复核范围:修复提交 da2171a 全量 diff + 整条 PR 重扫。本地验证: 第一轮 7 项全部确认修复且各有测试钉住:
本轮新发现(均为 minor/nit,不阻塞):
结论:建议合并。核心逻辑、契约(409/ |
… --force=<bool> and -- parsing, cap-comment nits (issue #102) Both second-round reviews confirmed every first-round fix; remaining nits: - ignored_destroyed is now present only when counted: a forced success omits the field instead of answering a 0 that read as 'none destroyed' while meaning 'not counted' (HTTP + MCP). - The CLI's manual scan honors --force=<bool> and the -- terminator like the flag-based subcommands; a garbage --force value fails loudly. - Stale maxDirtyFirstPaths comment (porcelain is capped now), singular 'line' in the truncation marker, and a comment naming the ignored-count probe-timeout edge case.
|
回复第二轮评审(qwen3.8-flash-next) — 感谢确认。本轮 4 项处理如下(
另:英文评审提的
|
|
Reply to the second-round independent review — thanks for confirming the round-1 fixes. Your three remaining items, all in
Also folded in the other review's nits (stale Full |
) The CLI keys the project state dir off HashPath(git rev-parse --show-toplevel), which returns the REAL path; on macOS t.TempDir() lives under /var, a symlink to /private/var, so the test wrote state.json under the unresolved path and the CLI looked it up under the resolved one — 'unknown worktree id' on the three delete tests that reach Manager.Delete. Reproduced locally by pointing TMPDIR through a symlink (fails without this fix, passes with it).
Closes #102
Problem
Deleting a worktree was refused with the flat
worktree has uncommitted or untracked changes; delete is refused— zero detail, no force exit. In the reported instance 133 tracked source files had vanished BEFORE the delete attempt, and the refusal presented a damaged workspace as an everyday "you have changes" prompt. The only way out was a manualgit worktree remove --forcethe daemon never noticed (its state record survived). (The file disappearance itself was investigated in the issue and is NOT a myworktree bug.)Fix (the issue's P1–P3)
Manager.Delete(id, force)returns a structuredDirtyWorktreeError: counts by category (one bucket per porcelain line, delete > rename > add > modify, buckets always sum to the total), up to 5 first paths, and the FULLgit status --porcelainoutput. Message now reads e.g.… refused: 133 entries (133 deleted, 0 modified, 0 untracked); first paths: ….POST /api/worktrees/deletetakesforce, the MCPworktree_deleteverb takesforce, CLI grewmyworktree worktree delete --force <id>; force passes--forcetogit worktree remove.git status --porcelain --ignored(!!lines the blocking check never sees — 89 ignored files, 64 single-copy, would have silently vanished) and warns that force destroys them unrecoverably.API: refusal answers 409 with contract code
worktree_dirty+message(one-line summary) + structureddirtybreakdown (documented indocs/API.md).UI:
deleteWorktreematches the code and opens a dialog: summary, collapsible full git status output, gitignored-file danger warning, and a two-step-confirm Force delete button that resends withforce: true.P4 (state.json's recorded branch can lag a
git switch) needed no change:Manager.Listalready re-resolves the live branch per worktree for display.Tests (full suite green)
internal/worktree:TestDeleteDirtyRefusalCarriesDetails/TestDeleteForceRemovesDirtyWorktree(real-git integration),TestDirtyWorktreeErrorSummarizePorcelain/TestCountIgnoredPorcelain/TestDirtyWorktreeErrorMessage(unit).internal/app:TestHandleWorktreeDeleteDirtyRefusalIsStructured/TestHandleWorktreeDeleteForceDeletes(API contract, real git).internal/ui:TestWorktreeDirtyDeleteDialog(asset anchors).--forceparsing smoke-tested with a built binary.CHANGELOG entry under Unreleased.