Skip to content

feat(vcs): 完善源代码管理、Git 引用操作与推送工作流 - #365

Open
mirakyux wants to merge 7 commits into
1lck:previewfrom
mirakyux:feature/vcs
Open

feat(vcs): 完善源代码管理、Git 引用操作与推送工作流#365
mirakyux wants to merge 7 commits into
1lck:previewfrom
mirakyux:feature/vcs

Conversation

@mirakyux

Copy link
Copy Markdown

关联 Issue

Related to #345

主要修改

源代码管理

  • 分离复选框提交选择与文件条目选择
  • 保留提交消息、选中文件和目录折叠状态
  • 支持目录批量暂存、回滚、搁置和聚合差异
  • 未跟踪文件提交时自动加入 VCS
  • 完善删除、回滚、.gitignore 和本地 exclude 操作
  • 统一文件树展示、状态颜色、目录文件数量和右键菜单
  • 改善大文件、删除文件、多文件 Diff 以及滚轮交互

提交历史

  • 支持提交记录多选和文件变更聚合
  • 增加压缩提交、编辑消息、删除提交、重置和 Cherry-pick
  • 引用位置显示与远端的 ahead/behind 差异
  • 修复提交文件 Diff 打开、滚动和布局问题

分支与引用

  • 为当前分支、本地分支、远程分支和 Tag 提供适用的右键操作
  • 支持检出、新建分支、比较、合并、变基和远程拉取
  • 支持更新、推送、设置跟踪分支、重命名、删除和创建 Worktree
  • 全链路保留 fullNameshortNamekind,避免将远程引用误写为本地引用

推送流程

  • 新增推送预览弹窗,显示目标远端、目标分支和待推送提交
  • 支持首次发布分支及 upstream 解析
  • 支持 force-with-lease
  • 支持全部标签或可达标签推送
  • 推送成功后统一刷新历史、引用和远端状态

共享 Core

  • 扩展 Rust Core Git 引用、写操作和推送预览契约
  • 增加 checkout-and-rebase、显式远程引用拉取等共享语义
  • 补充稳定校验、错误返回、共享 fixtures 和跨边界测试
  • macOS Rust 适配层同步消费完整 Git 引用模型

验证

  • Windows Frontend:541 项通过
  • Shared Rust:347 项通过
  • Windows Rust:104 项通过
  • bun run typecheck
  • Rust 格式与 Core 注释检查
  • Windows React/Tauri 边界检查
  • git diff --check
  • CodeGraph 索引同步

验证限制

当前环境为 Windows,缺少 Swift、zsh 和 Ruby,因此未执行:

  • ./scripts/test-macos.sh
  • ./scripts/verify-shared-contracts.sh
  • ./scripts/verify-git-graph.sh

变更涉及的 Git JSON fixtures 已单独完成结构化解析校验。

- 优化变更树选择、目录聚合差异与文件状态交互
- 完善提交历史多选、引用菜单及 ahead/behind 标识
- 新增推送预览、force-with-lease 与标签推送选项
- 扩展 Rust Core Git 契约、共享 fixture 与覆盖测试
@mirakyux
mirakyux requested a review from 1lck as a code owner August 31, 2026 08:39
@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

CI挂了麻烦看一下

@mirakyux

Copy link
Copy Markdown
Author

CI挂了麻烦看一下

在看

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

1. rust/lithe-core/src/git/mod.rs

定位:selected commit 中先执行 git add -A -- <paths> 的位置

[P1] Preserve the original index when committing selected paths

这里为了实现 selected-file commit,会先执行 git add -A -- <paths>,然后再执行 git commit --only ...

问题是如果后续 commit 因 pre-commit hook、签名失败、用户配置等原因失败,这一步对真实 index 的修改不会被恢复。这样 UI 中“commit selection”和“staging state”虽然已经被设计为两个独立概念,但一次失败的 commit 仍然会偷偷改变用户的 staging state。

建议不要直接修改真实 index。可以考虑使用临时 GIT_INDEX_FILE 构造本次提交,或者在操作前保存 index snapshot,并确保所有失败路径都恢复原 index。

至少需要补一个集成测试:

  1. 文件最初 unstaged
  2. selected commit
  3. 让 commit hook 返回非零
  4. 验证 command 失败后 git diff --cached 与操作前完全一致

2. windows/tauri/src/features/git/components/git-commit-panel.tsx

定位:getConflictMarkerPaths() 检查 marker 的位置

[P1] Check conflict markers against the snapshot that will actually be committed

这里检查 conflict marker 时,Core 使用的是 git grep --cached,也就是当前 index。

但 selected commit 后面还会执行 git add -A <selected paths>。因此下面这种情况会漏检:

index:    clean
worktree: contains <<<<<<< / >>>>>>>
selected: true

pre-check 会认为文件没有 conflict marker,随后 git add -A 又会把 worktree 中的 marker 放进 index 并提交。

这个检查需要针对“本次最终要提交的 snapshot”执行,而不是针对 staging 前的 index 执行。它和 selected-commit 的 index 实现最好一起解决。


3. windows/tauri/src/features/git/utils/git-status-model.ts

定位:displayFileByPath 优先保留 staged entry 的逻辑

[P1] Partial-staged files can show a different diff from what will actually be committed

同一路径同时存在 staged 和 unstaged 修改时,这里会把两条状态压成一个 visible row,并优先展示 staged entry。

但 selected commit 最终会对该 path 执行 git add -A,所以实际 commit 会包含 staged + unstaged 两部分。

这会导致一个比较危险的行为:

user reviews: staged half
actual commit: staged half + unstaged half

Git GUI 应该保证用户 review 到的内容和实际提交内容一致。

这里需要明确产品语义:

  • 如果 checkbox 表示“提交整个 working-tree path”,diff 也应该展示最终即将提交的完整 snapshot;
  • 如果希望保留 partial staging 语义,就不能在 commit 时无条件 git add -A <path>

建议补一个 partial-stage integration test,明确验证“展示/选择语义 == 最终 commit tree”。


4. rust/lithe-core/src/git/mod.rs

定位:default_push_remote()

[P1] Respect Git's configured push remote instead of defaulting to origin

当前没有 upstream 时,push target 会优先选择 origin,否则选择第一个 remote。

这没有遵循 Git 自己的 push remote 配置,例如常见的 fork workflow:

branch.main.remote = upstream
branch.main.pushRemote = fork
remote.pushDefault = fork

这种情况下用户预期从 upstream 拉取,但向 fork 推送;当前 resolver 可能会把 preview 和实际 push 都指向错误 remote。

建议至少遵循类似:

branch.<name>.pushRemote
→ remote.pushDefault
→ configured upstream/default remote

的解析顺序。

好的一点是 preview 和 execute 目前共享同一个 resolver,所以这里修一次即可同时保证两边一致。建议也补 pushRemoteremote.pushDefault 的 Core test。


5. windows/tauri/src/features/git/utils/git-reference-actions.ts

定位:parseRemoteBranch()

[P1] Do not parse a remote reference by splitting shortName at the first /

这里:

const [remote, ...branchParts] = reference.shortName.split("/");

假定 remote name 本身不包含 /

但 Core 已经支持这种情况,并且会通过已配置 remotes 做最长前缀匹配。例如:

remote:    team/origin
branch:    feature/foo
shortName: team/origin/feature/foo

这里会被错误解析成:

remote: team
branch: origin/feature/foo

随后 delete remote branch / worktree naming 等操作都会得到错误目标。

既然 GitReference 已经携带完整 fullName + shortName + kind,这里不应该重新从 shortName 推断 identity。建议把完整 typed reference 继续传到 Core,由 Core 中现有的 remote_branch_components() 做 authoritative resolution。

另外建议补一个 frontend test,remote name 使用 team/origin,避免上下层 reference contract 再次发生偏差。


6. windows/tauri/src/features/git/api/git-commits-api.ts

定位:runHistoryMutation()

[P1/P2] Refresh repository state even when a history mutation exits with conflicts

emitGitChanged() 只会在 git.write resolve 后执行。

但 cherry-pick / rewrite-based delete 等操作即使返回失败,也可能已经改变 repository state,例如留下:

CHERRY_PICK_HEAD
rebase state
conflicted index

Windows platform adapter 会把 non-zero exit code 转成 rejected Promise,因此这些情况下这里不会 emit refresh。

项目现有 merge/rebase API 已经考虑了这一点:Git command reject 后仍然刷新并读取 operation state。

建议 history mutation 保持相同语义——对于可能产生中间状态的 mutation,不论 success / reject 都 invalidation repository state,并在需要时读取 operation state,让现有的 continue / abort recovery UI 能及时出现。


7. windows/tauri/src/features/git/components/git-commit-panel.tsx

定位:isCommitDisabled

[P2] Disable Commit when no files are selected

现在:

const isCommitDisabled =
  isCommitting ||
  isGenerating ||
  (selectedFilesCount > 0 && !commitMessage.trim());

selectedFilesCount === 0 时,Commit button 仍可能是 enabled,点击之后才在 handleCommit() 中报 “select files”。

建议直接把 selection 作为 disabled 条件:

const isCommitDisabled =
  selectedFilesCount === 0 ||
  !commitMessage.trim() ||
  isCommitting ||
  isGenerating;

这样 UI state 和实际可执行条件是一致的。

提交选中文件时保留并在失败后恢复原始索引,同时针对最终提交快照检查冲突标记并对齐差异预览。

遵循 Git 推送远端配置,保留完整远程引用;历史修改失败后刷新仓库状态,并在未选择文件时禁用提交按钮。
@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

@lithe review

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Lithe Review

结论: ❓ 审查未完成

审查状态:cancelled
请查看本次 Actions 日志与诊断附件后重新召唤。

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

建议把这次对 .agents/skills/write-stable-tests/ 的修改从当前 PR 里拆出去,单独开一个 PR 处理。

这几个提交确实暴露出了 Swift test runner 的一些真实问题,尤其是并发测试共用单一 timer、Swift Testing 输出解析和 stdout buffering 等,但这些属于共享测试基础设施;当前 PR 的主体还是 Git/VCS 功能。

现在把两类修改混在一起,一方面会扩大当前 PR 的 review 和回归范围,另一方面 runner 本身看起来还在随着 CI failure 持续迭代,不太适合继续塞在这个功能 PR 里。

建议:

  • 当前 PR 只保留与 Git/VCS 功能直接相关的修改;
  • .agents/skills/write-stable-tests/ 相关修改移出当前 PR;
  • 单独开一个 PR 专门修 Swift test runner;
  • 在那个 PR 里集中覆盖 concurrent tests、partial stdout、failure output、buffering 等 case,再独立验证 runner 的稳定性。

这样功能实现和测试基础设施修改的边界会清楚很多,也更容易判断当前 Git 功能本身有没有问题。

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