feat(git): git worktree 支持与侧边栏项目分组 - #404
Conversation
|
PR governance checks passed. Awaiting human review. |
|
希望webui同步功能呢 |
|
难绷,又有冲突了 |
coder-hhx
left a comment
There was a problem hiding this comment.
整体检视了一遍,质量很高:worktree 名称校验(check-ref-format + 路径分隔符/控制字符拒绝)、移除前的登记列表校验、enableWebGit 写门控正反向测试、桌面端与 WebUI 的 i18n key 完全一致、settings 白名单修复带 round-trip 测试,Rust 侧 8 个 worktree 测试覆盖了 stale prune 和 force 保护等边缘场景。👍
发现 1 个建议合并前修复的功能问题(M1)和 3 个小问题,已按行内评论标注:
- M1(建议修复):
git worktree list --porcelain的第一个条目是主工作树本身(带branch refs/heads/<当前分支>),git_worktrees_sync未过滤。在 worktree 工作区打开分支选择器时,主仓库检出的分支会被误判为"被 worktree 检出",删除入口错误显示为"删除 Worktree",确认后 git 拒绝移除主工作树,用户看到报错。无数据损失(git 自身兜底),但属于新功能日常路径上的语义错误。 - L1:
assignWorkspaceProjectToGroup结尾return touched ? next : next;两分支相同,touched无效,幂等调用也会触发 settings 写入。 - L2:
git_remove_worktree_sync的fs::canonicalize(用户输入)要求路径存在,stale worktree(目录已被手动删)走不了该 API;好在 delete_branch 的 prune 前置已覆盖。仅提示。 - L3:
repo_worktree_id把 fnv1a64 截断为 32 位,建议用满 64 位,零成本消除碰撞顾虑。非阻塞。
结论:建议修复 M1 后合并(L1 顺手带上,L2/L3 可 follow-up)。CI 已全绿。
| } | ||
| } | ||
| if !path.is_empty() { | ||
| worktrees.push(GitWorktreeInfo { path, branch }); |
There was a problem hiding this comment.
M1:主工作树混入列表。 git worktree list --porcelain 的第一个 block 是主仓库自身,且带 branch refs/heads/<当前分支>(已在本地 git 实测确认)。这里未过滤,导致前端把"被主仓库检出的分支"当成"被 linked worktree 检出"。
触发场景:在 worktree 工作区打开分支选择器 → 主仓库检出的分支(如 main,对当前工作区非 current,满足删除入口显示条件)被匹配到主仓库路径 → 入口显示"删除 Worktree"而非"删除分支" → 确认后执行 git worktree remove <主仓库路径> → git 拒绝,用户看到报错。
建议:解析时跳过第一个 block,或过滤 path == repo_root 的条目。同时建议 git_remove_worktree_sync 的登记校验也显式排除主工作树(目前主仓库也算"已登记",虽然 git 最终会拒绝,防御深度上最好前置拦截)。
| }, [branchAction, confirm, gitClient, resetBranchAction, runSheetMutation, t, workdir]); | ||
|
|
||
| // 分支被 worktree 检出时,删除入口切换为删除 worktree(连带分支)。 | ||
| const checkedOutWorktreePath = branchAction |
There was a problem hiding this comment.
M1 的前端侧:worktrees 含主工作树条目时,这里的 find 会把主仓库检出的分支误判为被 worktree 检出(详见后端 git_worktrees_sync 处的评论)。后端过滤掉主工作树后此处无需改动;若选择在前端过滤,可对比 state.repoRoot 排除。
| : group.projectPaths.filter((path) => workspaceProjectPathKey(path) !== targetKey), | ||
| }; | ||
| }); | ||
| return touched ? next : next; |
There was a problem hiding this comment.
L1:touched ? next : next 两个分支相同,touched 完全未生效。意图应是无变化时返回原引用(groups),让两端 updateWorkspaceProjectGroups 里的 next === prev.system.workspaceProjectGroups 引用相等短路生效;现在恒返回新数组,幂等操作(如重复打开已入组的 worktree)也会触发一次 settings 写入/同步。
| if trimmed.is_empty() { | ||
| return Err("Worktree 路径不能为空。".to_string()); | ||
| } | ||
| let canonical = |
There was a problem hiding this comment.
L2(提示,非阻塞):fs::canonicalize 要求路径存在——worktree 目录被手动删除(stale 登记)时此 API 直接报"无法解析路径",无法用于清理;好在 git_delete_branch_sync 的 worktree prune 前置覆盖了这条路径且有测试。另外校验用 canonical 路径、执行传原始 trimmed,存在极小 TOCTOU 窗口,本地桌面场景可接受。
|
|
||
| /// 稳定且唯一的 repo id:`<sanitized-basename>-<fnv1a64 低 32 位 hex>`。 | ||
| /// 同一仓库根路径永远映射到同一 id,目录可读;32 位哈希碰撞概率低,但非绝对。 | ||
| fn repo_worktree_id(repo_root: &str) -> String { |
There was a problem hiding this comment.
L3(非阻塞):既然已经算出 fnv1a64 的 64 位哈希,建议直接用满 16 个 hex 字符而不是截断成 32 位(as u32)——零成本消除注释里提到的碰撞顾虑。
Code reviewFound 1 issue:
LiveAgent/crates/agent-ui/src/lib/workspaceProjects.ts Lines 474 to 488 in 5e95e86 调用点与 show-all 条件: LiveAgent/crates/agent-ui/src/components/chat/ChatHistorySidebar.tsx Lines 2076 to 2082 in 5e95e86 LiveAgent/crates/agent-ui/src/components/chat/ChatHistorySidebar.tsx Lines 3003 to 3005 in 5e95e86 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
|
补充两点(承接上面的 code review 与此前 review 意见): 1. 先前 review 的 M1/L1/L2/L3 在当前 head(5e95e86)均未修复。 Review 之后仅合入了一个 upstream/main 的 merge commit,所有被评论的行原文未变,M1( 2. 追加一个建议顺手修复的问题:删除 worktree 时 LiveAgent/crates/agent-gui/src-tauri/src/commands/workspace/git.rs Lines 3191 to 3201 in 5e95e86 当 worktree 移除成功、但分支因存在未合并提交而删除失败时(活跃 worktree 分支的常态),返回"Worktree 已移除,但分支删除失败"。而前端 LiveAgent/crates/agent-ui/src/components/git/GitBranchSelector.tsx Lines 1176 to 1180 in 5e95e86 兜底路径存在(普通删分支入口先 🤖 Generated with Claude Code |
5e95e86 to
642beec
Compare
coder-hhx
left a comment
There was a problem hiding this comment.
检视意见(Comment)
整体质量很高:后端路径安全校验、Gateway enableWebGit 门控、双端(桌面/WebUI)镜像、i18n key 一致性、测试覆盖都到位,顺手修掉的 workspaceProjectGroups 持久化 bug 也补了 round-trip 测试。CI 全绿,不阻塞合并。以下 2 个建议级问题望跟进,备注级供参考。
建议级
M1 · 无分组场景 pinned 分隔线疑似回归(ChatHistorySidebar.tsx)
旧逻辑在扁平项目列表中,pinned 块与非 pinned 块之间有分隔线。新的 firstUnpinnedSectionIndex 只在分组区块之间和「未分组」标题前渲染分隔线;当用户没有任何分组(最常见场景)且存在 pinned 项目时,计算走到 ungrouped[0]?.isPinned === true → return -1,加上 ungrouped 列表内部没有逐项分隔线渲染点,分隔线整体消失。建议在纯 ungrouped 渲染路径上恢复 pinned/非 pinned 之间的分隔线。
M2 · 强制移除 worktree 时未合并分支被静默 -D(deleteWorktreeFlow + git_remove_worktree_sync)
脏 worktree 路径:removeWorktree(force=false) 被 git 拒绝 → 弹「强制移除」确认(文案只提"丢弃未提交改动")→ removeWorktree(force=true, branch),后端 force 同时把分支删除从 -d 升级为 -D。若该分支还有未合并提交,用户只确认了丢弃未提交改动,提交也被一并删除——绕过了非脏路径下专门的「分支尚未完全合并」二次确认。
建议:强制移除 worktree 时分支删除仍用 -d,失败后复用已有的 not-fully-merged 确认流(isGitWorktreeBranchNotFullyMergedError 分支已存在);或至少在 deleteWorktreeForceDescription 文案中明确「分支及其未合并提交也将被删除」。
备注级(可不改)
isGitWorktreeBranchNotFullyMergedError正则匹配后端中文错误前缀(worktree .*分支删除失败),前后端字符串耦合。目前有测试锚定、git 侧有LC_ALL=C保障,但后端文案一改即静默失效,长期看结构化错误码更稳。sliceWorkspaceProjectSections遇到第一个放不下的分组即break,后续更小的分组也全部隐藏;首组成员数 >30 时折叠视图一个分组都不显示。边缘场景,行为可预期,提一下。- 设置归一化中硬编码
"未命名分组"中文 fallback,英文 locale 下会露中文(理解非 React 上下文拿不到t())。 - 有分组存在时,pinned 的未分组项目排在所有分组之后、不再浮顶——如果是分组布局的预期取舍,建议在 PR 描述或代码注释中明确一句。
- Create git worktrees under ~/.liveagent/worktree/<repo_id>/<name> from the branch selector; the new worktree opens as a workspace in the sidebar - Detect branches checked out in a worktree and offer "Delete Worktree" (removes the worktree and its branch) instead of a failing branch delete, with a force-removal escalation for dirty worktrees - Add user-defined sidebar project groups with create/rename/delete/move and collapsed state; worktrees auto-group under their source repository project, reusing renamed groups via sourceProjectPath - Persist workspaceProjectGroups in system settings (the save whitelist previously dropped the field, losing groups on restart) - Move the "New Worktree" entry next to "Create New Branch" in the git menu; keep worktree names free of auto-capitalization
…empotency - M1: git_worktrees_sync 过滤主工作树(path == repo_root),主仓库检出的 分支不再被误判为被 linked worktree 检出;git_remove_worktree_sync 的 登记校验随之天然排除主仓库(防御深度前置)。 - L1: assignWorkspaceProjectToGroup 无变化时返回原引用,让两端 updateWorkspaceProjectGroups 的引用相等短路生效,幂等操作不再触发 多余的 settings 写入/同步。 - L3: repo_worktree_id 使用完整 64 位 fnv1a64 hex(16 字符),消除 32 位截断的碰撞顾虑。 - Bug: sliceWorkspaceProjectSections 改为按成员数裁剪(分组整组纳入、 绝不拆开),未分组项目也受渲染上限约束,hiddenProjectCount 统计 全部被隐藏成员,show-all 按钮恢复生效。 - 附带 rebase 至最新 upstream 后的对齐修正(settings 测试 row_count、 import 布局、i18n 格式、loader 路径)。
- git_remove_worktree_sync: force 传导到分支删除,--force 时用 git branch -D 让未合并提交的分支也能被清理 - GitBranchSelector: worktree 已移除但分支未完全合并时,不再重试已注销的 worktree,直接对残留分支提供强制删除确认(复用 deleteForce 文案) - 新增 isGitWorktreeBranchNotFullyMergedError 错误分类纯函数及单测 - Rust 集成测试覆盖普通失败保留分支与 force 成功删除分支两条路径
642beec to
412205b
Compare


Closes #403
概述
为分支选择器添加 git worktree 创建/移除能力,并在侧边栏支持项目分组,worktree 自动聚合到原始仓库项目下。桌面端(agent-gui)与 Gateway WebUI 均已同步支持。
Worktree
~/.liveagent/worktree/<repo_id>/<name>(repo_id = 规范化仓库根路径的清洗名 + fnv1a64 哈希),检出同名新分支,并在侧边栏打开为工作区。git worktree list登记列表中。enableWebGit门控。侧边栏分组
sourceProjectPath匹配,重命名后仍复用;嵌套 worktree 溯源回原始仓库。修复
workspaceProjectGroups缺失于设置保存白名单导致重启丢失——现已持久化,含 round-trip 测试。验证
cargo test:676 通过(含 worktree 创建/移除/校验与设置 round-trip)tsc --noEmit、1460 个 node 测试、biome 干净(仅基线警告)cargo fmt --check干净Screenshots / preview
新建 Worktree 弹窗(桌面端):
侧边栏项目分组(worktree 自动聚合到源项目分组):
说明
enableWebGit写权限门控。