diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a56758..843fdeb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: name: Lint & Build runs-on: ubuntu-latest steps: - - uses: pnpm/action-setup@v6 - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version: 22 @@ -37,8 +37,8 @@ jobs: os: ${{ (github.event_name == 'pull_request') && fromJson('["ubuntu-latest"]') || fromJson('["ubuntu-latest", "macos-latest", "windows-latest"]') }} runs-on: ${{ matrix.os }} steps: - - uses: pnpm/action-setup@v6 - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version: 22 @@ -60,8 +60,8 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: pnpm/action-setup@v6 - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version: 22 @@ -83,6 +83,8 @@ jobs: permissions: contents: write steps: + # 检出仓库:使 body_path 指向的 Release Note 文件可见 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v8 with: name: vsix @@ -91,10 +93,11 @@ jobs: with: files: '*.vsix' prerelease: ${{ contains(github.ref_name, 'rc') }} - generate_release_notes: true + # Release 正文取自版本控制的 Release Note(单一事实源,按 tag 名取对应文件) + body_path: docs/releases/${{ github.ref_name }}.md fail_on_unmatched_files: true - # 发布:仅 tag 触发,双市场(Marketplace + OpenVSX),生产环境审批门 + # 发布:仅 tag 触发;OpenVSX 默认发布,VS Code Marketplace 由 ENABLE_MARKETPLACE_PUBLISH 变量门控(默认关闭);生产环境审批门 publish: name: Publish needs: package @@ -102,8 +105,8 @@ jobs: runs-on: ubuntu-latest environment: production steps: - - uses: pnpm/action-setup@v6 - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version: 22 @@ -112,13 +115,20 @@ jobs: - uses: actions/download-artifact@v8 with: name: vsix + # VS Code Marketplace 发布:默认关闭(未配置 VSCE_PAT)。 + # 配好 VSCE_PAT 后,在仓库 Settings → Secrets and variables → Actions → Variables + # 新增 ENABLE_MARKETPLACE_PUBLISH=true 即恢复,无需改代码或发 PR。 - name: 发布到 VS Code Marketplace + if: ${{ vars.ENABLE_MARKETPLACE_PUBLISH == 'true' }} run: | PRE_FLAG=$([[ "${GITHUB_REF_NAME}" == *rc* ]] && echo "--pre-release" || echo "") pnpm dlx @vscode/vsce publish --packagePath *.vsix $PRE_FLAG env: VSCE_PAT: ${{ secrets.VSCE_PAT }} + # OpenVSX 不支持覆盖已存在版本:若该版本号已发布则此步报错。 + # 以 continue-on-error 隔离——不阻断 GitHub Release 产出,亦不让首发 workflow 标红。 - name: 发布到 OpenVSX(覆盖 Cursor / Windsurf 等 AI IDE) + continue-on-error: true run: | PRE_FLAG=$([[ "${GITHUB_REF_NAME}" == *rc* ]] && echo "--pre-release" || echo "") pnpm dlx ovsx publish *.vsix $PRE_FLAG diff --git a/.gitignore b/.gitignore index 034a9f5..053535e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ out/ # VS Code Test 临时 .vscode-test/ +.idea/ # 临时产物(执行计划等,一律收敛至此并即时清理) .temp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d75998..2bc88f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,172 +2,74 @@ 本项目的所有重要变更均记录于此文件。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。 -## [Unreleased] +> 面向用户的发布说明(含完整特性叙述与安装指引)见 [`docs/releases/`](./docs/releases/README.md)。 -## [0.0.1-rc.4] - 2026-06-29 — 第四个预发布候选 - -> 修复用户截图反馈的两处工具窗口缺陷:活动栏图标缺失「未提交文件数」角标、Branches「Push」对无上游分支失败。 -> -> 版本号遵循 VS Code Marketplace 约定(`major.minor.patch` = `0.0.4`),预发布语义由 `--pre-release` 标记 + git tag `v0.0.1-rc.4` 体现。 +## [0.0.7] - 未发布 ### Fixed +- 修复 `vsce package` 打包因 ESLint `no-useless-assignment` 阻断:`engine/ci/remote-parser.ts` 中 `host`/`path` 的空串初值为 dead store(`hasScheme` 真假两分支均无条件重赋、解析失败处先 `return null`),改为带类型标注的纯声明消除,由 TS 确定赋值分析接管,运行时行为不变。 -- **活动栏未提交角标(#17)**:`hyperGit.changes` 原经 `registerTreeDataProvider` 注册,仅得 `Disposable` 无法承载 `.badge`;改用 `createTreeView` 获取 `TreeView` 句柄并新增 `updateChangesBadge()`,计数复用 `GitRepositoryService.getChanges()`(index+工作区+未跟踪去重),接入既有 `refreshAll` 防抖链路与首帧保险,计数为 0 时清空。活动栏容器图标角标为容器内各视图 `badge.value` 之和,故 Hyper Git 图标自此实时显示未提交文件数,对齐原生 SCM。 -- **Branches Push 对无上游分支失败(#17)**:`hyperGit.push` 原以零参数调 `repo.push()`,当前分支无上游时 `git push`(`push.default=simple`)必然失败,vscode.git 包装为 `GitError`、其 `.message` 恰为通用串「Failed to execute git」而真实 stderr 被吞没。改为以 `HEAD.upstream` 是否存在分流:有上游沿用 `repo.push()`;无上游则选定 remote(单 remote 直用、多 remote 弹选)并以 `repo.push(remote, branch, true)` 建立 `-u` 追踪。同时增强 `errMsg` 优先暴露 `GitError.stderr`,使「无上游」「non-fast-forward」等失败可读(push/pull/fetch 等同享)。 - -## [0.0.1-rc.3] - 2026-06-29 — 第三个预发布候选 - -> 包含 rc.2 后的全部 Parity Recovery(Batch 5-12),完成 IDEA Git 工具窗口全量对齐;并修复发布流水线,使 GitHub Release 自此自动附带「可本地安装的 `.vsix`」资产。 -> -> 版本号遵循 VS Code Marketplace 约定(`major.minor.patch` = `0.0.3`),预发布语义由 `--pre-release` 标记 + git tag `v0.0.1-rc.3` 体现。 - -### CI / 发布 - -- **GitHub Release 自动附带 `.vsix`**:CI 新增独立 `github-release` job(`softprops/action-gh-release`),对每个 `v*` tag 复用 `package` job 产出的 vsix artifact,自动创建 Release(`*rc*` tag 自动标记 prerelease)并上传 `.vsix` 资产,用户可直接「`Extensions: Install from VSIX`」本地安装。该 job 与市场 `publish` 解耦(`needs: package`、不挂 `production` 环境),即便市场发布待审批/失败亦不影响 Release 资产产出;`fail_on_unmatched_files` 杜绝「空资产 Release」。 - -### Added — Parity Recovery(Batch 5-12,IDEA 全量对齐) - -> 基于「IDEA Git 功能复刻全量对齐」评审(用户截图反馈 Branches 空白/工具栏缺失),经 3 路代码审计确认「功能多数已实现但被视图 bug + 工具栏未浮现掩盖」,本批次先解除可见痛感再全量补齐。共新增 **13 个 engine 纯逻辑模块 + 21 个命令**,单测 64 → 166。 - -- **Branches 视图渲染修复(Batch5)**:`engine/ref/for-each-ref`(NUL 分隔 + upstream/HEAD/ahead-behind track)+ async getChildren + `repo.state.refs` 为空时 `git for-each-ref` CLI 兜底 + 首帧刷新(修复初始 `_onDidChange.fire` 早于订阅挂载而丢失的根因)。 -- **工具栏 Action 组补齐 + 命令 bug(Batch5)**:Changes 加 fetch/pull/push/commit;Branches 加 fetch;Log 加 cherry-pick/revert/reset;cherry-pick/revert 成功后刷新;resetHead 重写(修 `HEAD~0` 致 mixed/keep 失效);branchDelete 加 `--merged` 检查;merge/rebase 加确认。 -- **冲突兜底引导(Batch5)**:`engine/git-state/conflict-detector` + `adapter/conflict-ui`;merge/rebase/pull/cherry-pick/revert/stash-pop/unshelve 失败检测冲突弹「解决/中止」。 -- **Branches 对齐 IDEA(Batch6)**:Favorites/Local/Remote/Tags 四段分组 + ahead/behind/upstream 展示 + `engine/ref/favorites`/`cleanup`(提取复用)+ toggleFavorite/checkoutAsNew/compareWithCurrent/tagCreate/tagDelete/tagCheckout。 -- **交互式 Rebase webview 1:1 复刻(Batch7)**:补 reword/edit + 拖拽重排序;reword 经 `process.execPath` 跑 Node editor helper + state 文件非交互写入新 message(已功能性验证);`engine/rebase/todo`。 -- **Log 提交详情面板 + 高级过滤(Batch8)**:commit 展开显示变更文件(`engine/log/commit-files`)+ 单文件 diff(commit^ vs commit)+ `engine/log/log-filter`(合并模式/日期/正则)+ per-commit 操作(resetToHere/createBranchFromCommit/createTagFromCommit/showContainingBranches)。 -- **真实 SVG 提交拓扑图(Batch9)**:解析 `git log --graph` 字符粒度渲染(`*` 圆点可点击 / `|` 竖线 / `/ \` 斜线,多色 lane)+ 实时刷新 + 节点点击 QuickPick;`engine/log/graph-parser`。 -- **Push/Update/Merge 对话框(Batch10)**:force/force-with-lease/push tags、pull --rebase/--no-rebase、merge ff-only/no-ff/squash + message;fetch --prune;`engine/commit/trailer`(Co-authored-by)。 -- **自绘 3-way Merge Editor(Batch11)**:自实现 `engine/merge/diff3`(基于 LCS,双方同改动自动消解)+ WebviewPanel 三栏(OURS/RESULT 可编辑/THEIRS)+ Accept 按钮 + 写回 `git add`;handleGitConflict「解决冲突」直接调起。 -- **Phase 4/5 收尾(Batch12)**:Stash 高级(keep-index/clear/branch)、Patch create/apply、Reflog 视图、编辑器内 Blame 行内注解(`engine/blame/blame-parser` + `adapter/editor/blame-annotation`)。 - -### 架构与质量 -- 新增 engine 模块(零 vscode 依赖、可单测):`ref/{for-each-ref,favorites,cleanup}`、`rebase/todo`、`log/{log-filter,commit-files,graph-parser}`、`merge/diff3`、`commit/trailer`、`git-state/conflict-detector`、`blame/blame-parser`。 -- 命令 56 → 77;单测 64 → 166(+21 文件);集成 3/3;lint/类型/构建全程 GREEN。 -- 关键算法(reword helper / graph 解析 / diff3 / blame 解析)均做真实 git 数据功能性验证。 -- git 底座:稳定 API 能做的用 `Repository.*`;缺口用 `GitRepositoryService.execGit`(复用 `api.git.path` 同一二进制)。 - -### 待补(后续) -- Commit webview 的 Co-authored-by / author 覆盖(`--author`)/ undo-last-commit 按钮 UI 接线(engine trailer 已就绪)。 -- Partial 多文件选择 UX、行级 split chunks(IDEA "Include Selected Lines")。 -- 目录/folder diff(虚拟文档)、Submodules 管理。 -- M5 AI Agent(5 个接缝已预埋 Null 实现,本轮未启动)。 - - - -## [0.0.1-rc.2] - 2026-06-28 — 第二个预发布候选 - -包含 rc.1 后的全部 Parity Recovery(Batch 1-4 + Editor Inline Commit),大幅补齐 IDEA Git 功能复刻。 - -### 核心新增(自 rc.1) -- **Editor Inline Commit**(#13):CodeLens「✓ 提交此 Hunk」→ 仅暂存该 hunk → 提交。 -- **Cherry-pick / Revert / Reset / Branch rename / Compare / Ignore / Reword**(经 `api.git.path` CLI 通道)。 -- **Git 提交图**(Webview `git log --graph` 着色拓扑)+ **Console**(git 命令输出面板)。 -- **partial/hunk 级提交**(hunk 解析引擎 + 选择暂存 + 光标处暂存)+ undo/drop/fixup + cleanup branches + 3-way diff。 -- **忠实 Shelf**(patch 存储 + unshelve 三方合并)+ **交互式 Rebase UI**(pick/squash/fixup/drop Webview)+ **Move Hunk to Changelist**(#25)。 -- **Stash 多栈列表** + Discard + Pull/Push/Fetch + PNG 图标 + Dependabot。 - -### Added — Parity Batch 4(忠实 Shelf + 交互式 Rebase + Line→CL,0.0.6) - -- **忠实 Shelf**(IDEA patch-based,#27-28):Shelve(`git diff` → patch 存扩展存储 → `git checkout --` 移除工作区)+ Unshelve silently / with 3-way merge + Delete + Shelf TreeView。与 Stash 独立并存。 -- **交互式 Rebase UI**(Webview,#44):commit 列表 + pick/squash/fixup/drop 动作 → 非交互 rebase(`GIT_SEQUENCE_EDITOR=cp ` + `GIT_EDITOR=:`)。 -- **Move Hunk to Changelist**(#25):编辑器内光标处 hunk → QuickPick changelist → ChangelistRegistry 持久化 hunk→CL 归属。 - -### Added — Editor Inline Commit(#13,0.0.5) - -> IDEA editor inline commit 的 VS Code 等价(补齐最后一块主要拼图)。 - -- **行内提交 CodeLens**:编辑器中每个未暂存 hunk 上方渲染可点击 CodeLens「✓ 提交此 Hunk (+N -M)」→ 仅暂存该 hunk(patch 重建 + `git apply --cached`)→ 输入 message → `git commit`。 -- 新增 `engine/diff/editor-mapping`(hunk → 编辑器行区域映射,纯逻辑 + 5 单测);gutter 行标记(绿/红/蓝)由原生 git quickDiff 提供。 -- 其他已暂存内容会一并提交时给出二次确认提示。 - -### Added — Parity Batch 3(partial commit + 高级操作,0.0.4) - -- **partial / 行级提交**(IDEA PartialChangesUtil 等价):`engine/diff/hunk-parser`(unified diff 解析,7 单测)+ hunk 选择暂存/取消暂存(QuickPick 勾选)+ 光标处 hunk 暂存——经 patch 重建 + `git apply --cached`。 -- **undo commit**(soft reset,保留改动到暂存区)、**drop commit**(`git rebase --onto`,重写历史二次确认)、**fixup**(autosquash rebase,经 env 注入 `GIT_SEQUENCE_EDITOR`)。 -- **cleanup branches**(`git branch --merged` 批量删除)、**copy branch ref**、**3-way diff 概览**(HEAD↔Staged↔Working)。 -- `execGit` 支持 env 注入(为 autosquash 等 rebase 自动化铺路)。 - -### Added — Parity Batch 2(UI 丰富度,0.0.3) - -- **Git 提交图(WebviewPanel)**:`git log --graph --oneline --decorate --all`(CLI)获取拓扑,语义着色渲染(graph 连线 / refs / hash)——补齐 IDEA Log 提交图的可视化拓扑。命令面板 + Log 视图标题按钮。 -- **Console**:Hyper Git Console(OutputChannel)记录所有 `execGit` 命令与输出(对齐 IDEA Console 标签页)。 - -### Added — Parity Batch 1(CLI 功能补齐,0.0.2) - -> 关键转向:引入 `GitRepositoryService.execGit`(复用 vscode.git 的同一 git 二进制 `api.git.path`),补齐稳定 API 未暴露的操作——修正此前"API 限制延后"的过度自我设限。 - -- Cherry-pick(Log 右键)、Revert commit(Log 右键)——经 `git cherry-pick` / `git revert`。 -- Reset HEAD(soft/mixed/hard/keep,命令面板,hard 二次确认)。 -- 分支重命名(Branches 右键 `git branch -m`)、比较分支(`git diff --stat a...b`)。 -- Ignore(写 .gitignore)、改写最新提交(amend)。 -- **Stash 多栈列表**:用 `git stash list`(CLI)修复——apply/pop/drop 按真实 `stash@{n}` 索引(此前 `log({refNames:['stash']})` 仅返回最新 stash 内部提交、语义错误,已弃用)。 - -## [0.0.1-rc.1] - 2026-06-27 — 首发候选(Pre-release) +## [0.0.6] - 2026-06-30 — 首个 MVP 正式版 -首个公开预发布候选,整合 M0-M4 里程碑交付 + 两轮独立 code review 修复。 -版本号遵循 VS Code Marketplace 约定(仅 `major.minor.patch` = `0.0.1`),预发布语义由 `--pre-release` 标记 + git tag `v0.0.1-rc.1` 体现。 +首个对外正式版本,在 VS Code Marketplace / OpenVSX 上以 **「Hyper Git - Agentic Git」** 之名发布。为 VS Code 提供统一的 Git 变更管理与提交工作流(多变更分组、自绘提交面板、可视化提交图、Shelf、行级提交)。采用**路径 B**(消费 `vscode.git` 稳定 `Repository` API 为底座;稳定 API 未覆盖的能力经 `GitRepositoryService.execGit` 复用同一 git 二进制 `api.git.path` 的受控 CLI 通道实现),与原生 Source Control 平行共存、零冲突。规模:**7 视图 / 93 命令 / 6 配置项**,**280 单元测试** + 集成测试,CI 三平台矩阵全程 GREEN。完整特性见 [Release Note v0.0.6](./docs/releases/v0.0.6.md)。 -**核心能力**:多 changelist Changes 视图、Commit 提交窗口(Conventional Commits 校验 + Amend/sign-off/skipHooks + Checkin hook 链)、Log/Branches/Blame/Show History、Stash(create/apply/pop/drop)、Discard 改动、Pull/Push/Fetch。架构路径 B(消费 vscode.git 稳定 API),5 个 AI 接缝已预埋(M5 实现)。 +### Added -**已知限制**(vscode.git 稳定 API 边界):cherry-pick / revert / reset / 分支重命名 / 行级 hunk 暂存 / 多 stash 列表枚举 / Author 覆盖暂不可用,详见 [实施状态 §3](./docs/milestones/implementation-status.md#3-api-限制汇总vscodegit-稳定-api-边界)。 +#### 变更与 Changelist +- 按 Changelist 分组的 Changes 树视图:新建 / 重命名 / 删除 / 设活动列表 / 跨列表移动文件,`workspaceState` 持久化(重启恢复);文件状态色复用 `gitDecoration.*` 主题色;单击打开原生 Diff(HEAD ↔ Working)。 +- 文件级操作:丢弃改动、加入 `.gitignore`、显示文件历史。 -### Added — M4 Stash/Shelf(0.5.0) +#### Commit 提交窗口 +- 自绘提交面板(WebviewView):活动 Changelist 文件勾选 + 多行消息编辑器 + Amend / Signed-off-by / 跳过 Git Hooks + **提交** / **提交并推送**;勾选集即提交权威范围(仅提交选中的文件集合);最近消息一键复用。 +- Conventional Commits 实时校验(可配置)+ 内置 `ConventionalCommitCheck` Checkin Hook;`CommitPipeline` 责任链设计参考 JetBrains `CheckinHandler`(校验 → 暂存 → Hook 链 → 提交 → 可选推送)。 -- **Stash 视图**:`StashTreeProvider` 经 `Repository.log({ refNames: ['stash'] })` 枚举 stash(API 不暴露 `git stash list`,以此近似)。 -- **Stash 操作**:`createStash` / `applyStash` / `popStash` / `dropStash`(经 vscode.git 稳定 API),配视图标题按钮与右键菜单 + viewsWelcome。 -- **Shelf(MVP)**:以 stash 近似 IDEA shelve(工程方案 §4 P2 约定);忠实 patch Shelf 受 API 限制延后。 -- **API 限制(文档化延后)**:行级 partial commit(vscode.git `add` 仅整文件,无 hunk 暂存)、忠实 patch Shelf、Staging Area 模式开关、cherry-pick/revert/reset/分支重命名——均无稳定 API 对应,未来可经 git CLI 兜底或 proposed API 评估。 +#### Log 提交图与历史 +- 自绘 **Graph DAG** webview:基于父子关系自计算 lane 布局,彩色泳道 / 节点 / 分叉·合并连线 / HEAD·分支·标签徽标,`--topo-order` 保拓扑序,行宽随实际 lane 自适应;虚拟化滚动增量加载、↑↓ 键导航;选中提交内联展开变更文件并打开单文件 Diff。 +- **提交图 × CI 状态**:每条提交行最右侧显示 GitHub CI 最终状态(绿勾/红叉/运行中),悬停 Tooltip 展示各项检查 + 未通过原因 + 跳转链接;复用 VS Code 内置 GitHub 认证(`vscode.authentication`,凭证不经 chat/日志/webview),仅取可见行懒加载、批量 GraphQL(≤100 oid)+ 限流冷却、终态缓存;非 GitHub 远程零图标零请求,支持 github.com 与 GitHub Enterprise;配置 `hyperGit.log.ci.{enabled,remote,provider}`。 +- **Checkpointer 过滤**:Log 视图新增 Checkpointer 选项,默认剔除 AI 编码工具产生的自动快照(checkpoint)提交,提交图更干净,可按需开启。 +- **7 个可组合过滤器**:作者、路径、message(grep)、message(正则)、合并模式、日期、一键清除;复制 commit hash、刷新。 +- per-commit 操作:Reset 到此(soft/mixed/hard/keep)、从此新建分支 / 标签、查看包含此提交的分支、Cherry-Pick、Revert。 -### Added — M3 Log/Branches/Diff·Blame(0.4.0) +#### Branches 与 Tags +- 四段分组(收藏 / 本地 / 远程 / 标签)+ ahead·behind·upstream 跟踪展示;新建 / 检出 / 删除 / 重命名 / 合并 / 变基 / 从选中新建并检出;收藏切换、与当前分支比较、任意两分支比较、复制引用、清理已合并分支。 +- **多选批量操作**:`createTreeView({ canSelectMany: true })` 支持框选,批量删除分支/标签、批量复制引用、批量收藏;删除前 `git branch --merged` 分类已合并/未合并并诚实分栏确认强制删除风险;仅单目标语义的操作经 `!listMultiSelection` 在多选时隐藏。 +- 标签:新建(轻量/附注)、删除(多选)、检出(detached HEAD)。 -- **Log TreeView**:消费 `Repository.log()`,按 author/path 过滤(清除过滤)、复制 commit hash、显示文件历史。完整提交图(SVG 拓扑连线)作为后续增强(M3.x)。 -- **Branches TreeView**:消费 `Repository.state.refs`(Local/Remote 分组),活动分支高亮;支持新建/检出/删除/合并/变基(rebase)。 -- **Blame**:`Show Blame` 命令对当前文件执行 `repo.blame` 并以只读文档展示。 -- **Show History**:从 Changes 文件右键跳转 Log 并按该文件路径过滤。 -- **API 限制(文档化延后)**:vscode.git 稳定 API 不含 cherry-pick / revert / reset / 分支重命名,这些 IDEA 功能暂不可用(未来可 CLI 兜底)。 +#### Stash 与 Shelf +- Stash:创建、保留已暂存创建、应用、Pop、删除、从 Stash 创建分支、清空全部,按真实 `stash@{n}` 索引操作。 +- Shelf(基于 patch、独立于 git stash 的改动搁置机制):Shelve 暂存、静默 Unshelve、带 3-way 合并 Unshelve、删除;独立 TreeView。 -### Fixed — M0/M1/M2 审查修复(0.3.1) +#### 远程与冲突 +- Pull / Push / Fetch(无上游分支自动选定 remote 并建立 `-u` 跟踪;`GitError.stderr` 优先暴露使失败可读)。 +- 对话框:**Push…**(normal / force-with-lease / force + 推送标签)、**Update Project…**(merge / rebase)、**Merge…**(ff-only / no-ff / squash + 自定义消息)。 +- 冲突兜底引导:merge/rebase/pull/cherry-pick/revert/stash-pop/unshelve 失败时检测冲突并弹「解决/中止」;自绘 **3-way Merge Editor**(OURS / RESULT 可编辑 / THEIRS + 写回 `git add`);冲突文件「采用 Ours / Theirs」。 -经 3 路独立 code review(正确性 / 架构 / 完整性)交叉复核后修复: +#### 历史编辑与高级操作 +- Cherry-Pick、Revert、Reset HEAD(soft/mixed/hard/keep)、交互式 Rebase(webview:pick/squash/fixup/drop + reword + 拖拽重排,经 `GIT_SEQUENCE_EDITOR` 非交互写入)、撤销最近提交(soft)、删除提交(rebase)、Fixup(autosquash)、改写最新提交信息。 -- **GitRepositoryService**:仓库切换时 `onDidChange` 订阅累积泄漏 → 改用单 `repoSub`,切换/卸载时 dispose。 -- **pickRepository**:`startsWith` 误匹配(无路径边界)→ 改用 `api.getRepository(folder.uri)` 精确匹配。 -- **getChanges**:缺失 `indexChanges`(已暂存文件不可见)→ 合并 index/working/untracked 按相对路径去重(index 优先)。 -- **commit 语义**:未勾选的已暂存文件先 `restore --staged`,让勾选集成为提交权威范围(对齐 IDEA「提交该集合」)。 -- **push 失败**:commit 成功后 push 失败误报「提交失败」→ 返回 `ok:true` + `warning`。 -- **extension.ts**:三个 `onDidChange` 订阅入 `subscriptions`(修复卸载泄漏)。 -- **refresh**:`await repo.status()` 后再刷新(避免陈旧数据)。 -- **conventional-linter**:Windows `\r\n` 行尾 + 中文/Unicode scope 支持。 -- **commit-webview**:`onDidReceiveMessage` 绑定 `view.onDidDispose`(修复重载泄漏);nonce 改用 `crypto.randomBytes`;选中态 `setState` 持久化。 -- **changes-tree**:tooltip 显示状态全称(Modified 而非 M);清理 `CommitFileItem` 冗余 `status/statusName` 字段。 -- **测试补齐**:`ConventionalCommitCheck`、`CommitService.executeCommit`(mock Repository,覆盖 CC 阻断/无文件/amend 透传/unstage/push 警告)、`git-status-map` 全量、`amend` 真实集成。 +#### 编辑器内能力 +- 行内提交:每个未暂存 Hunk 上方渲染 CodeLens「提交此 Hunk」→ patch 重建 + `git apply --cached` 仅暂存该 Hunk → 提交(含他处已暂存内容的二次确认)。 +- 部分暂存 / 取消暂存、光标处暂存、Hunk 归属 Changelist(持久化 hunk→CL 映射)。 +- Blame 行内注解:逐行作者 / 日期 / hash 显示于编辑器内,悬浮展示提交详情,文档编辑时自动清除。 -### Added — M2 Commit 提交窗口(0.3.0) +#### Worktrees +- 全生命周期管理:新建(新分支 / 检出已有 / detached)、在新窗口打开、锁定 / 解锁、移动、复制路径、删除(安全 / 强制)、清理失效 Worktree、刷新。 -- Commit 提交窗口(WebviewView 自绘 IDEA 风格):活动 changelist 文件勾选 + 多行 Commit Message 编辑器 + Amend / Signed-off-by / 跳过 Git hooks 选项 + Commit / Commit and Push 按钮。 -- Conventional Commits 实时校验:`engine/commit/conventional-linter` 纯函数 + webview 指示器(ok/warning/error)+ 内置 `ConventionalCommitCheck` Checkin hook(pipeline 内阻断不合规提交)。 -- `CommitPipeline` 责任链接入提交流程(对齐 IDEA `CheckinHandler`:校验 → stage → hook 链 → commit → 可选 push)。 -- AI 接缝 5 接口 + Null 实现注入 CommitService(`ILlmProvider` / `ICommitMessageProvider` / `IPreCommitInspector` / `IChangelistGrouper` / `IConflictResolver`),M5 替换为真实实现。 -- 最近提交消息复用(`workspaceState` 持久化,webview 一键填入)。 -- 真实 git 提交闭环集成测试(fixture 仓库 + `vscode.git` add/commit + git log 校验)。 +#### 工具与配置 +- 导出 / 应用 Patch、查看 Reflog、3-way Diff 概览(HEAD ↔ Staged ↔ Working)、Console 命令输出面板。 +- 配置项:`hyperGit.commit.template`、`hyperGit.commit.conventional`、`hyperGit.ai.enabled`(M5 预留,暂不生效)、`hyperGit.log.ci.{enabled,remote,provider}`(提交图 CI 状态)。 -### Added — M1 Git Adapter + 多 changelist Changes(0.2.0) +#### 架构与质量 +- 正交分层:`engine/`(纯逻辑,零 vscode 依赖、Vitest 可测)、`adapter/`(唯一接触 vscode API)、`agent/`(AI 接缝)、`ui/`、`shared/protocol.ts`(Webview ↔ Host 契约单一事实源)、`infra/`。 +- AI 接缝预埋 5 接口 + Null 实现(`ILlmProvider` / `ICommitMessageProvider` / `IPreCommitInspector` / `IChangelistGrouper` / `IConflictResolver`),设计参考 JetBrains `CheckinHandler` 提交生命周期,M5 替换为真实实现。 +- 品牌图标统一为「Git Pull Request」造型(活动栏 SVG + Marketplace 徽标 + README 头图,字形改编自 Tabler Icons,MIT);活动栏图标实时显示未提交文件数角标。 +- CI 流水线:lint → 类型 → 构建 → 三平台测试矩阵(Ubuntu/macOS/Windows,Linux 经 xvfb)→ 打包 vsix;`v*` 标签触发 GitHub Release(附带可本地安装的 `.vsix`,正文取自 `docs/releases/`)+ OpenVSX 发布;VS Code Marketplace 由 `ENABLE_MARKETPLACE_PUBLISH` 变量门控。 -- Git Adapter:`GitRepositoryService` 封装内置 vscode.git 稳定 `Repository` API(读取 workingTreeChanges/untrackedChanges、状态变更事件、diff/toGitUri 委托)。 -- 多 changelist:`ChangelistRegistry`(active 列表、新建/重命名/删除/移动 + `workspaceState` 持久化,重启恢复)+ 引擎纯分组逻辑 `groupByChangelist`。 -- Changes TreeView:changelist 一级节点 + 文件叶子,状态色复用 `gitDecoration.*` 主题色(ThemeIcon + ThemeColor),文件单击打开原生 `vscode.diff`。 -- 命令:`refresh` / `newChangelist` / `setActiveChangelist` / `renameChangelist` / `deleteChangelist` / `moveChangelist` / `openDiff`,配视图标题与右键菜单(`viewItem` 上下文键)。 -- 测试:新增 `changelist-grouper`(5)+ `git-status-map`(9)单元测试;集成测试覆盖全部 M1 命令注册。 -- 工程修复:eslint flat config 忽略 `.vscode-test/**`(规避本地 test-electron 下载的 VS Code 导致 lint OOM)。 +### 已知限制 -### Added — M0 脚手架 + CI +- Commit 窗口的 Co-authored-by / Author 覆盖(`--author`)/ 撤销最近提交按钮 UI 接线(engine `trailer` 已就绪,仅缺 webview 交互)。 +- Partial 多文件选择 UX、行级 split chunks(按选定行拆分提交)。 +- 目录 / folder diff(虚拟文档)、Submodules 管理。 +- M5 AI Agent(5 个接缝已预埋 Null 实现,本版未启动)。 -- 初始化 pnpm + esbuild + TypeScript(strict)工程骨架,对齐官方 `esbuild-sample`。 -- 正交分层目录:`engine/`(纯逻辑,零 vscode 依赖)、`adapter/`、`agent/`(AI 接缝预留)、`ui/`、`shared/`、`infra/`。 -- 质量基础设施:ESLint 9 flat config + typescript-eslint + @stylistic、Prettier、Vitest(engine 单测)、@vscode/test-electron + Mocha(集成测试)。 -- 工程约束:`.npmrc`(`node-linker=hoisted` 规避 vsce/pnpm hoisting)、`.vscodeignore`、`engines.vscode ^1.85.0` 与 `@types/vscode 1.85.0` 严格对齐。 -- 扩展贡献点:活动栏视图容器 `hyper-git` + `hyperGit.changes` 树视图(M0 占位,M1 接入真实 changelist)+ `hyperGit.showVersion` 命令 + 配置项(commit 模板 / Conventional Commits 开关 / AI 开关预留)。 -- 引擎层纯逻辑:`engine/scm-mapping`(FileStatus → gitDecoration.* 主题色映射)、`engine/commit/pipeline`(Checkin hook 责任链,对齐 IDEA `CheckinHandler.ReturnResult`)。 -- AI 接缝接口 + Null 实现:`agent/llm-provider.ts`(`ILlmProvider`)、`agent/pre-commit.ts`(`IPreCommitInspector`)。 -- Webview ↔ Host 消息契约单一事实源:`shared/protocol.ts`。 -- CI 流水线 `.github/workflows/ci.yml`:lint → build → test 矩阵(ubuntu/mac/win + Linux xvfb)→ package vsix → artifact;`tag v*` → 双市场发布(Marketplace + OpenVSX)。 +[0.0.6]: https://github.com/ThreeFish-AI/hyper-git/releases/tag/v0.0.6 diff --git a/README.md b/README.md index 460c09e..e5e091d 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,67 @@ -# Hyper Git +

+ Hyper Git +

-> 在 VS Code 上完整复刻 IntelliJ IDEA 的 **Git 工具窗口** 与 **Commit 提交窗口**,并为未来 git 管理的 AI Agent 自主代理能力预留架构接缝。 +

Hyper Git

-[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) +

为 VS Code 带来统一的 Git 变更管理提交工作流——多变更分组、自绘提交面板、可视化提交图、Shelf、行级提交,并为未来 git 管理的 AI Agent 自主代理能力预留架构接缝。

-## 为什么需要 Hyper Git +

+ CI + License: MIT + VS Code >= 1.85 +

-IntelliJ IDEA 的统一 Git 工具窗口(顶部 `Commit / Shelf / Stash` 标签页 + Changes 变更树 + Commit Message 编辑区 + 提交前 Inspection)是开发者高频依赖的工作流。迁移到 VS Code 后,原生 Source Control 视图**缺少**:多 changelist、忠实的 Commit 窗口、提交前检查流水线、Shelf/行级提交等能力。Hyper Git 旨在 1:1 补齐这一体验。 +## 核心能力 -## 架构(路径 B:消费 + 自绘) +- **多 Changelist Changes 视图**:将改动分组到命名列表,设活动列表为提交目标,新建/重命名/删除/移动,`workspaceState` 持久化(重启恢复);状态色复用 `gitDecoration.*` 主题色。 +- **Commit 提交窗口**:自绘提交面板 + Conventional Commits 实时校验 + Amend / Sign-off / 跳过 Hook + 提交 / 提交并推送;勾选集即提交权威范围;最近消息复用。 +- **Log 提交图(自绘 DAG)**:彩色泳道、分叉·合并连线、HEAD/分支/标签徽标,虚拟滚动增量加载;7 个可组合过滤器(作者/路径/grep/正则/合并模式/日期/清除);per-commit 操作(Reset、新建分支·标签、Cherry-Pick、Revert、查看包含分支)。 +- **Branches 分支管理**:收藏/本地/远程/标签四段分组 + ahead·behind·upstream 跟踪;新建/检出/删除/重命名/合并/变基/比较/收藏;**多选批量**删除、复制引用、收藏(已合并/未合并诚实分栏确认)。 +- **Stash & Shelf**:Stash 全操作(含 keep-index / clear / 从 Stash 建分支);独立 **Shelf**(基于 patch、独立于 stash,含 3-way 合并 Unshelve)。 +- **Worktrees**:全生命周期管理——新建(新分支/检出/detached)、在新窗口打开、锁定/解锁、移动、删除、清理失效。 +- **行级与 Hunk 提交**:编辑器内 CodeLens「提交此 Hunk」、部分暂存/取消暂存、光标处暂存、Hunk 归属 Changelist。 +- **远程与冲突**:Pull/Push/Fetch + Push…/Update…/Merge… 对话框(force-with-lease / rebase / squash 等)+ 自绘 **3-way Merge Editor** 与冲突兜底引导。 +- **历史编辑**:Cherry-Pick、Revert、Reset、交互式 Rebase、Undo/Drop/Fixup/Reword。 +- **其他**:Blame 行内注解、Patch 导出/应用、Reflog、3-way Diff 概览、Discard、Diff(HEAD ↔ Working)。 -- **消费** 内置 `vscode.git` 扩展导出的稳定 `Repository` API 作为 git 操作底座(不重造 git 状态机)。 -- **自建** changelist registry(IDEA 的 active 列表 / 跨列表行级归属无法用原生 SCM group 表达)。 -- **自绘** 独立视图容器承载 IDEA 风格 UI,与原生 Source Control 视图零冲突、平行共存。 -- **AI**:现仅预留接口接缝(`ILlmProvider` / `IPreCommitInspector` 等,对齐 IDEA `CheckinHandler` 生命周期),实现延后至 M5。 +## 架构(路径 B:消费 + 自绘) -## 功能(v0.0.1-rc.1) +- **消费** 内置 `vscode.git` 导出的稳定 `Repository` API 作为 git 底座,不重造 git 状态机。 +- **受控 CLI 通道**:稳定 API 未覆盖的能力(cherry-pick / revert / reset / 分支重命名 / hunk 暂存 / stash 列表 / graph 拓扑 / shelf 等)经 `GitRepositoryService.execGit` 复用同一 git 二进制(`api.git.path`)实现。 +- **自绘视图** 承载完整的变更管理 UI;纯逻辑沉淀于 `engine/`(零 vscode 依赖、可单测)。 +- **AI 接缝**:预留 `ILlmProvider` / `IPreCommitInspector` 等 5 个接口(设计参考 JetBrains `CheckinHandler` 提交生命周期),实现延后至 M5。 -- **多 changelist Changes 视图**:active 列表、新建/删除/重命名/移动,`workspaceState` 持久化(重启恢复);文件状态色复用 `gitDecoration.*` 主题色。 -- **Commit 提交窗口**:多行编辑器 + Conventional Commits 实时校验 + Amend / Signed-off-by / 跳过 Git hooks + Commit / Commit and Push;勾选集为提交权威范围;最近消息复用。 -- **Log 提交历史**:按作者/路径过滤、清除过滤、复制 commit hash、显示文件历史。 -- **Branches**:本地/远程分组、活动分支高亮;新建/检出/删除/合并/变基(rebase)。 -- **Stash**:create / apply / pop / drop(作用于 stash@{0} 最新)。 -- **其他**:Discard 改动、Pull / Push / Fetch、Blame、Diff(HEAD ↔ Working)。 +

+ Framework +

## 安装 -- **VS Code Marketplace**:搜索 `Hyper Git`(发布后)。 -- **OpenVSX**(Cursor / Windsurf / Gitpod / VSCodium):同 `Hyper Git`。 -- **手动**:从 [Releases](https://github.com/ThreeFish-AI/hyper-git/releases) 下载 `.vsix`(形如 `hyper-git-0.0.4.vsix`)→ 命令面板 `Extensions: Install from VSIX`。 -- **要求**:VS Code ≥ 1.85.0 + 启用内置 Git 扩展(`vscode.git`,默认随附)。 +- **手动(当前推荐)**:从 [Releases](https://github.com/ThreeFish-AI/hyper-git/releases) 下载 `hyper-git-agentic-git-x.x.x.vsix` → 命令面板 `Extensions: Install from VSIX`。 +- **OpenVSX**(Cursor / Windsurf / Gitpod / VSCodium):搜索 `Hyper Git - Agentic Git`。 +- **VS Code Marketplace**:搜索 `Hyper Git - Agentic Git`(发布上线后可用)。 +- **要求**:VS Code ≥ 1.85.0 + 启用内置 Git 扩展(`vscode.git`,默认随附)。仅支持本地 git 仓库,不支持虚拟 / Web 工作区。 ## 已知限制 -> vscode.git 稳定 API 不含 cherry-pick / revert / reset / 分支重命名 / hunk 暂存 / stash list / graph topology / shelf / author 覆盖等;**这些均已通过 `GitRepositoryService.execGit`(复用 `api.git.path` 同一 git 二进制)的受控 CLI 通道实现**(Batch 5-12 全量对齐,详见 [CHANGELOG](./CHANGELOG.md) [Unreleased])。 - -当前仍待补: - -- Commit 窗口的 Co-authored-by / Author 覆盖(`--author`)/ undo-last-commit **按钮 UI 接线**(engine `trailer` 已就绪,仅缺 webview 交互)。 -- Partial 多文件选择 UX、行级 split chunks(IDEA "Include Selected Lines")。 +- Commit 窗口的 Co-authored-by / Author 覆盖(`--author`)/ 撤销最近提交的**按钮 UI 接线**(engine `trailer` 已就绪,仅缺 webview 交互)。 +- Partial 多文件选择 UX、行级 split chunks(按选定行拆分提交)。 - 目录 / folder diff(虚拟文档)、Submodules 管理。 -- M5 AI Agent(5 个接缝已预埋 Null 实现,本轮未启动)。 +- M5 AI Agent(5 个接缝已预埋 Null 实现,本版未启动)。 -详见[工程实施方案](./docs/architecture/engineering-plan.md)、[实施状态总览](./docs/milestones/implementation-status.md)与[知识索引](./.agents/knowledge-map.md)。 +详见[工程实施方案](./docs/architecture/engineering-plan.md)、[实施状态总览](./docs/milestones/implementation-status.md)与[知识索引](./docs/.agents/knowledge-map.md)。 ## 路线图 -| 里程碑 | 主题 | 状态 | -|---|---|---| -| M0 | 脚手架 + CI | ✅ | -| M1 | Git Adapter + Changes TreeView(多 changelist) | ✅ | -| M2 | Commit 提交窗口(模板 / Amend / CC 校验 / hook 链) | ✅ | -| M3 | Log + Branches + Diff/Blame | ✅ | -| M4 | Stash / Shelf MVP | ✅ | -| M5 | AI Agent(实现接缝) | ⏳ 留存设计,暂不实施 | - -> ⚠️ vscode.git 稳定 API 不含 cherry-pick / revert / reset / 分支重命名 / 行级 hunk 暂存;这些 IDEA 功能暂不可用(详见[实施状态 §3](./docs/milestones/implementation-status.md#3-api-限制汇总vscodegit-稳定-api-边界))。 +| 里程碑 | 主题 | 状态 | +| ------ | --------------------------------------------------- | ---- | +| M0 | 脚手架 + CI | ✅ | +| M1 | Git Adapter + Changes TreeView(多 Changelist) | ✅ | +| M2 | Commit 提交窗口(模板 / Amend / CC 校验 / Hook 链) | ✅ | +| M3 | Log(Graph DAG) + Branches + Diff/Blame | ✅ | +| M4 | Stash / Shelf / 行级提交 / Worktrees | ✅ | +| M5 | AI Agent(接缝已预留,实现待启动) | ⏳ | ## 开发 @@ -63,14 +69,16 @@ IntelliJ IDEA 的统一 Git 工具窗口(顶部 `Commit / Shelf / Stash` 标 pnpm install # 安装依赖 pnpm run compile # 类型检查 + lint + 构建 pnpm run watch # 监听构建(F5 启动 Extension Host 调试) -pnpm run test:unit # 单元测试(engine 纯逻辑,Vitest) +pnpm run test:unit # 单元测试(engine 纯逻辑,Vitest,280 例) pnpm run test:integration # 集成测试(@vscode/test-electron) pnpm run package # 生产构建 pnpm dlx @vscode/vsce package # 打包 .vsix ``` -> 包管理与脚本统一使用 `pnpm`(遵循 [AGENTS.md](./AGENTS.md) 工程规范)。 +- **分层**:`engine/`(纯逻辑)→ `adapter/`(唯一接触 vscode API)→ `ui/`;`agent/` 以接口注入 `engine`,不反向依赖 UI;`shared/protocol.ts` 为 Webview ↔ Host 契约单一事实源。 +- **发布**:`v*` 标签触发 CI 产出 GitHub Release(附 `.vsix`,正文取自 [`docs/releases/`](./docs/releases/README.md))+ OpenVSX;VS Code Marketplace 由 `ENABLE_MARKETPLACE_PUBLISH` 变量门控。 +- 包管理与脚本统一使用 `pnpm`(遵循 [AGENTS.md](./AGENTS.md) 工程规范)。完整文档见[文档中心](./docs/README.md)。 ## 许可证 -[MIT](./LICENSE) +[MIT](./LICENSE)。 diff --git a/.agents/browser-validation.md b/docs/.agents/browser-validation.md similarity index 100% rename from .agents/browser-validation.md rename to docs/.agents/browser-validation.md diff --git a/.agents/issue.md b/docs/.agents/issue.md similarity index 58% rename from .agents/issue.md rename to docs/.agents/issue.md index 6bd7b75..4cadad8 100644 --- a/.agents/issue.md +++ b/docs/.agents/issue.md @@ -62,4 +62,20 @@ - **后续防范**:该 job 与市场 `publish` **解耦**(不 `needs: publish`、不挂 `environment: production`),保证「Release 带 `.vsix`」不被市场审批门/密钥缺失阻塞;「仅出 Release、暂不发市场」时不审批 production 即可,无需改 publish job;最小权限仅本 job 提权 `contents: write`。 - **同类问题影响**:所有「CI 只上传 artifact + 发市场、却在 README 承诺 Release 手动下载」的 VS Code 扩展;artifact ≠ Release 资产,二者可见性/留存期差异易被忽视。 +## #8 Branches 视图无法多选(批量删除等批量操作缺失) + +- **表因**:用户截图反馈 Branches 视图中一组功能/工作分支无法框选多个、无法批量删除。 +- **根因**:`hyperGit.branches` 经 `vscode.window.registerTreeDataProvider` 注册——该 API **不支持** `canSelectMany`,故视图天然单选;所有分支命令处理器亦只接收单个 `BranchNode`。多选能力(`canSelectMany: true`)仅 `createTreeView` 的 `TreeViewOptions` 支持。 +- **处理方式**:改用 `createTreeView('hyperGit.branches', { treeDataProvider, canSelectMany: true })`(句柄入 subscriptions)。批量命令处理器签名扩展为 `(clickedNode, selectedNodes[])`——VS Code 多选树的 `view/item/context` 命令第 2 实参即完整选区数组。新增纯逻辑 `engine/ref/selection.collectBranchRefs`(谓词过滤 + shortName 去重 + 「点击在选区之外则以点击项为准」)与 `engine/ref/cleanup.partitionByMerged`/`formatBranchDeleteConfirm`,使 `branchDelete`/`tagDelete`/`copyBranchRef`/`toggleFavorite` 批量化(删除仅一次 `git branch --merged` 分类、汇总成功/失败、末尾单次刷新)。`package.json` 对仅单目标命令(检出/合并/变基/重命名/比较等)追加 `&& !listMultiSelection` 在多选时隐藏。 +- **后续防范**:① 需要承载 `.badge` 或 `canSelectMany` 等 `TreeViewOptions` 能力的视图,一律用 `createTreeView` 而非 `registerTreeDataProvider`(本仓 `hyperGit.changes` 已有先例)。② 多选命令正确性**只依赖处理器读取实参**(`clickedNode` + `selectedNodes[]`),不得依赖 `listMultiSelection` 上下文键——其对**自定义贡献视图**的可靠性无法确证,仅作菜单整洁的视觉优化;单目标命令因只读 `clickedNode` 即便该键失效仍安全。③ 「右键点击选区之外」须以点击项为准(手势目标优先),由归一化助手统一兜底。 +- **同类问题影响**:所有以 `registerTreeDataProvider` 注册却后续需要多选/角标的自定义 TreeView;以及误把单目标命令在多选下直接作用于「点击项」造成的隐性误操作。 + +## #9 LOG 视图残留「已删分支」提交(实为工具注入的内部引用污染 `git log --all`) + +- **表因**:用户截图反馈 LOG 的 All 范围下,一批本应随分支删除而消失的提交仍以游离泳道残留;运行「清理已删远程分支」(#44,`git fetch --prune`)后**依旧存在**。 +- **根因**:`engine/log/log-query.ts` 的 `buildLogArgs` 对 `all`/`checkpointer` 范围下 `git log --all`。`--all` 遍历 `refs/` 下**全部**引用,不止 heads/remotes/tags——还包括宿主工具(如 Conductor)注入的 `refs/conductor-checkpoints/*`(会话快照)、`refs/conductor-archive-heads/*`(已删/被取代分支头的归档)。这些归档头让**真实的游离提交**(被 amend/rebase 取代、或分支删除后仅靠归档存活者)仍可达,画成游离泳道。而既有的客户端 `CHECKPOINT_SUBJECT_RE=/^checkpoint:/i` 过滤**只能拦住 checkpoint 元数据提交本身**,拦不住作为其祖先的游离业务提交——故泄漏。`git fetch --prune` 仅清理 `refs/remotes/*`,对上述非远端跟踪引用**完全无效**,这正是「prune 后依旧存在」的根因。实证:本仓 `--all` 取 241 提交、`--branches --tags --remotes` 仅 70;refs 命名空间 135 conductor-checkpoints + 17 conductor-archive-heads,远多于 3 heads/3 remotes/2 tags。 +- **处理方式**:`all` 范围由 `--all` 改为 `--branches --tags --remotes`(仅三大标准命名空间,排除一切工具注入的内部引用),根治游离泳道;`checkpointer` 范围**保留 `--all`**——该 Tab 的职责即「原始完整视图,含内部 checkpoint 快照」,需触达 `refs/conductor-checkpoints/*`。客户端 `keepCheckpoint` 过滤作为双保险保留。更新 `tests/unit/log-query.test.ts` 断言(`all` 含三件套、不含 `--all`;`checkpointer` 含 `--all`、不叠三件套)作回归护栏。 +- **后续防范**:① 「全分支视图」语义应映射到 `--branches --tags --remotes` 而非 `--all`——`--all` 是「全部引用」而非「全部分支」,二者差异恰是工具注入引用的污染面。② 客户端按提交 message 正则过滤是**漏的抽象**(拦不住作为祖先被带入的游离提交);根治应在 ref 选取层(服务端参数)而非 subject 过滤层。③ **诊断 git 引用类问题时务必先 `git for-each-ref` 列出全部命名空间**——本案最初误判为「远端已删、本地未 prune」(#44 与一度推进的 prune-on-fetch 方案均为此误判),直到列出 refs 才发现真凶是 conductor-* 引用;「prune 无效」本身就是关键反证,应据其反向收敛而非强行加 prune。④ 修正「错漏逻辑」前先用 `git log --all` vs `--branches --tags --remotes` 的差集实证根因,避免再次基于关键字匹配机械式修改。 +- **同类问题影响**:所有在带「工具注入内部引用」环境(IDE/Agent checkpoint、`refs/stash`、`refs/replace/*`、`refs/notes/*` 等)下展示 `git log --all` 图的 Git GUI;凡把「范围 = 引用集合」与「范围 = message 过滤」混为一谈的实现均可能漏过游离提交。 + diff --git a/.agents/knowledge-map.md b/docs/.agents/knowledge-map.md similarity index 82% rename from .agents/knowledge-map.md rename to docs/.agents/knowledge-map.md index 00aff2a..dff7a5c 100644 --- a/.agents/knowledge-map.md +++ b/docs/.agents/knowledge-map.md @@ -16,10 +16,12 @@ ## 项目文档(docs/) - [文档中心](../docs/README.md) — 文档与调研资产总索引。 +- [Log 视图 CI 状态](../docs/features/log-ci-status.md) — 按提交显示 GitHub CI 最终状态(绿勾/红叉 + Tooltip 明细):认证、限流、懒加载、边界与配置。 - [实施状态总览](../docs/milestones/implementation-status.md) — M0-M5 交付记录 + API 限制 + M5 AI 设计 + 验证/发布(**实施看板**)。 - [工程实施方案](../docs/architecture/engineering-plan.md) — 路径 B 架构 + M0-M5 里程碑(**开发蓝图**)。 -- [IDEA 功能复刻矩阵](../docs/requirements/idea-feature-matrix.md) — 56 功能点 / 8 组(**验收基线**)。 +- [Git 功能矩阵](../docs/requirements/idea-feature-matrix.md) — 56 功能点 / 8 组(**验收基线**,参考 IDEA 等成熟实现)。 - [调研报告](../docs/research/README.md) — SCM 集成 / 工程蓝图 / 发布 CI / AI 接缝四路循证报告。 +- [发布说明](../releases/README.md) — 各正式版 Release Notes(GitHub Release 正文单一事实源;最新 [v0.0.6](../releases/v0.0.6.md))。 ## 架构分层(src/) > 依赖方向单向:`UI → Adapter → Engine`;`Agent` 以接口注入 `Engine`/`CommitPipeline`,不反向依赖 UI。 diff --git a/.agents/reference-specifications.md b/docs/.agents/reference-specifications.md similarity index 100% rename from .agents/reference-specifications.md rename to docs/.agents/reference-specifications.md diff --git a/docs/README.md b/docs/README.md index 4f7e7b0..b32bb9f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,14 +5,20 @@ ## 工程方案与需求基线(高频引用) - [实施状态总览(M0-M5)](./milestones/implementation-status.md) — 里程碑交付记录、P0/P1 达成矩阵、API 限制、M5 AI 设计、验证与发布状态(**实施看板**)。 - [工程实施方案](./architecture/engineering-plan.md) — 全链路调研结论 + 路径 B 架构 + M0-M5 里程碑路线图 + 风险与验证(**开发蓝图**)。 -- [IDEA 功能复刻矩阵](./requirements/idea-feature-matrix.md) — 56 个原子功能点 / 8 组 + CheckinHandler 生命周期(**验收基线**)。 +- [Git 功能完备性矩阵](./requirements/idea-feature-matrix.md) — 56 个原子功能点 / 8 组 + CheckinHandler 生命周期(**验收基线**)。 + +## 功能文档 +- [Log 视图 CI 状态](./features/log-ci-status.md) — 按提交显示 GitHub CI 最终状态(绿勾/红叉 + 悬停 Tooltip 明细):认证、限流、懒加载、边界与配置。 + +## 发布说明 +- [Release Notes 目录](./releases/README.md) — 各正式版发布说明(GitHub Release 正文单一事实源);最新:[v0.0.6 首个 MVP](./releases/v0.0.6.md)。 ## 调研报告(循证依据) - [02 · VS Code SCM API 与 vscode.git 集成路径](./research/02-vscode-scm-integration.md) — 路径 B 决策依据、SCM 稳定/proposed API 边界、changelist 模型映射。 - [03 · VS Code 扩展工程蓝图](./research/03-extension-blueprint.md) — 技术栈决策、工程骨架、IDEA→VS Code UI 表面映射表。 - [04 · 发布策略 + CI/CD](./research/04-publishing-cicd.md) — 双市场(Marketplace + OpenVSX)、CI 矩阵、版本治理、安全。 -- [05 · AI Agent 架构预留](./research/05-ai-agent-seams.md) — AI 接缝(ILlmProvider 等)+ IDEA CheckinHandler 对齐 + 渐进式引入路线。 +- [05 · AI Agent 架构预留](./research/05-ai-agent-seams.md) — AI 接缝(ILlmProvider 等)+ 借鉴 JetBrains CheckinHandler 责任链设计 + 渐进式引入路线。 ## 协作与规范 - [AGENTS.md](../AGENTS.md) — 协作协议与工程行为准则。 -- [知识索引](../.agents/knowledge-map.md) · [Issue 记录](../.agents/issue.md) · [引用规范 IEEE](../.agents/reference-specifications.md)。 +- [知识索引](./.agents/knowledge-map.md) · [Issue 记录](./.agents/issue.md) · [引用规范 IEEE](./.agents/reference-specifications.md)。 diff --git a/docs/architecture/engineering-plan.md b/docs/architecture/engineering-plan.md index 61c8683..a00565b 100644 --- a/docs/architecture/engineering-plan.md +++ b/docs/architecture/engineering-plan.md @@ -1,13 +1,13 @@ # Hyper Git — VS Code 扩展工程实施方案 -> 复刻 IntelliJ IDEA 社区版「Git 工具窗口 + Commit 提交窗口」全功能,并为未来 AI Agent 自主代理预留架构接缝。 +> 提供完整的「Git 变更管理 + Commit 提交工作流」(功能完备性参考 IntelliJ IDEA 等成熟实现),并为未来 AI Agent 自主代理预留架构接缝。 > 决策已与用户确认:**路径 B**(消费 `vscode.git` API + 自建 changelist registry + 独立视图容器)、扩展命名 **Hyper Git**、**双市场发布**、**AI 现仅预留接缝 + Null 实现、延后至 M5**。 --- ## 0. Context(背景与目标) -**为什么做**:IntelliJ IDEA 的统一 Git 工具窗口(顶部 `Commit/Shelf/Stash` 标签页 + Changes 变更树 + Commit Message 编辑区 + 提交前 Inspection)是 Java/全栈开发者高频依赖的工作流,但迁移到 VS Code 后只能用原生 Source Control 视图(无多 changelist、无忠实 Commit 窗口、无提交前检查流水线)。本项目目标是**在 VS Code 上 1:1 复刻该体验**,并在未来为 git 管理引入 AI Agent(提交信息生成 / 提交前代码审查 / 变更语义分组 / 冲突解决)。 +**为什么做**:IntelliJ IDEA 的统一 Git 工具窗口(顶部 `Commit/Shelf/Stash` 标签页 + Changes 变更树 + Commit Message 编辑区 + 提交前 Inspection)是 Java/全栈开发者高频依赖的工作流,但迁移到 VS Code 后只能用原生 Source Control 视图(无多 changelist、无独立 Commit 窗口、无提交前检查流水线)。本项目目标是**在 VS Code 上提供同等完备的 Git 变更管理与提交工作流**,并在未来为 git 管理引入 AI Agent(提交信息生成 / 提交前代码审查 / 变更语义分组 / 冲突解决)。 **当前状态**:仓库为全新 greenfield 工程(仅有 `.agents/` 文档脚手架与 AGENTS.md 协议,无源码 / 无 package.json / 无 README)。本方案从零搭建。 @@ -23,13 +23,13 @@ | 决策项 | 结论 | 循证依据 | |---|---|---| | **git 操作底座** | 消费内置 `vscode.git` 扩展导出的 `API`(`getAPI(1)` → `Repository`),**不自调 git CLI、不重造状态机** | `git.d.ts` 已暴露 commit/add/revert/diff/blame/log/stash/branch/merge/rebase 全套;GitHub PR 扩展即此模式 | -| **changelist 表达** | **自建 changelist registry**(仿 IDEA `ChangeListManager`),以 TreeView 渲染;**不**注册竞争性 SCM Provider | Track1:IDEA active 列表 + 跨列表行级归属,原生 SCM group 无法表达 | +| **changelist 表达** | **自建 changelist registry**(借鉴 JetBrains `ChangeListManager` 设计),以 TreeView 渲染;**不**注册竞争性 SCM Provider | Track1:IDEA active 列表 + 跨列表行级归属,原生 SCM group 无法表达 | | **视图容器** | 活动栏新建独立视图容器 `hyper-git`,承载 Changes/Commit/Log/Branches/Shelf/Stash;**不接管/不替代原生 Source Control 视图**(避免双胞胎冲突) | Track2:注册独立 SCM 会与原生 git 视图并列混淆 | | **Commit 编辑器** | WebviewView 自绘(多行 / 模板 / Conventional Commits 校验 / Amend / Author / sign-off) | 原生 `SourceControlInputBox` 仅 `value` 字段,Provider 已删除 | | **Log 提交图** | Webview 自绘 SVG graph + 消费 `Repository.log()` | `scmHistoryProvider` 为 proposed,上架不可用 | | **Diff 预览** | 复用 `vscode.diff` + `api.toGitUri(uri,'HEAD')`(零成本) | Track2 §5.5 | | **发布** | 双市场(Marketplace + OpenVSX) | Cursor/Windsurf 走 OpenVSX;AI 受众主战场 | -| **AI** | 现仅定义接缝 + Null 实现,实现延后 M5 | YAGNI + 复用 IDEA `CheckinHandler` 语义 | +| **AI** | 现仅定义接缝 + Null 实现,实现延后 M5 | YAGNI + 借鉴 JetBrains `CheckinHandler` 责任链语义 | **架构总览(Mermaid,深色模式高对比)**: @@ -105,7 +105,7 @@ hyper-git/ ├── engine/ # 【引擎层】纯领域逻辑,零 vscode 依赖 │ ├── model/ # FileChange / Changelist / Commit / Branch / StashEntry / ConflictHunk │ ├── diff/ # diff 解析 + 行级 patch(partial commit 基础) - │ ├── commit/ # CommitPipeline(Checkin hook 责任链,仿 IDEA CheckinHandler) + │ ├── commit/ # CommitPipeline(Checkin hook 责任链,借鉴 JetBrains CheckinHandler 设计) │ └── scm-mapping/ # Status(M/A/D/U/R/C) → decorations 映射(纯函数) ├── adapter/ # 【适配层】唯一接触 vscode API │ ├── git-repository.ts # GitRepositoryAdapter:封装 vscode.git Repository(add/commit/diff/log/stash/branch…) @@ -118,7 +118,7 @@ hyper-git/ ├── agent/ # 【代理层】AI 接缝(预留,Null 实现) │ ├── llm-provider.ts # ILlmProvider(模型来源抽象:vscodeLM/byok/openaiCompatible) │ ├── commit-message.ts # ICommitMessageProvider(生成 + Conventional Commits 校验) - │ ├── pre-commit.ts # IPreCommitInspector(对齐 IDEA beforeCheckin/CommitCheck) + │ ├── pre-commit.ts # IPreCommitInspector(借鉴 JetBrains beforeCheckin/CommitCheck 机制) │ ├── grouper.ts # IChangelistGrouper(语义分组) │ ├── conflict.ts # IConflictResolver(三方合并建议) │ └── chat-tools.ts # IChatToolRegistrar(M5 暴露 git 能力给 Agent) @@ -157,9 +157,9 @@ hyper-git/ --- -## 4. IDEA 功能复刻优先级矩阵 +## 4. 功能优先级矩阵 -> 基于 Track1 的 56 功能点,按价值×依赖×难度分入 P0(MVP)/P1(核心对齐)/P2(高级对齐)/P3(AI 增强)。完整 56 项明细见 [IDEA 功能复刻矩阵](../requirements/idea-feature-matrix.md)(后续验收的需求基线)。 +> 基于 Track1 的 56 功能点,按价值×依赖×难度分入 P0(MVP)/P1(核心功能)/P2(高级功能)/P3(AI 增强)。完整 56 项明细见 [功能矩阵](../requirements/idea-feature-matrix.md)(后续验收的需求基线)。 | 优先级 | 功能域 | 代表功能(来源 Track1 编号) | |---|---|---| @@ -168,9 +168,9 @@ hyper-git/ | **P1 核心** | Commit 检查流水线 | 提交前 Inspection 框架(#10,对接 VS Code Diagnostics)、Commit Checks 顺序闸门(#11)、CRLF/大文件预检(#12)、Conventional Commits 校验(#4,IDEA 无内置需自建 linter) | | **P1 核心** | Branches | 创建/检出/删除/重命名(#45/#46)、Merge/Rebase/Pull/Push/Fetch(#48)、Compare(#47) | | **P1 核心** | Stash + Diff | Stash apply/pop/drop(#29/#30)、Annotate(blame)(#37)、Show History(#38) | -| **P2 高级** | Partial / 行级提交 | 按代码块提交(#23)、按行提交(#24)、Move Lines to Changelist(#25)——最难,仿 `PartialChangesUtil` | +| **P2 高级** | Partial / 行级提交 | 按代码块提交(#23)、按行提交(#24)、Move Lines to Changelist(#25)——最难,参考 `PartialChangesUtil` 实现 | | **P2 高级** | Log 提交图 | graph 自绘(#39)、filter(#40)、cherry-pick/revert from log(#41/#42)、Undo Commit(#43) | -| **P2 高级** | Shelf | 忠实 shelve/unshelve with conflict(#27/#28,patch 存储+三方合并);MVP 先用 stash 近似 | +| **P2 高级** | Shelf | 完整 shelve/unshelve with conflict(#27/#28,patch 存储+三方合并);MVP 先用 stash 近似 | | **P3 AI 增强** | (M5,接缝现已埋) | AI 提交信息生成、AI 提交前审查、AI 语义分组、AI 冲突解决、AI release notes、Chat Tools 暴露 git 能力 | --- @@ -199,8 +199,8 @@ hyper-git/ - 验收:Log 图正确渲染拓扑;分支操作经真实 git 验证;blame 行级显示作者。 - 依赖:M1、M2。 -**M4 — Shelf + Partial/行级提交 + Stash UI(Parity 收口,0.6.0)** -- 交付:忠实 Shelf(patch 存储 + unshelve 三方合并)、行级 partial commit(仿 `PartialChangesUtil` + 行级 hunk staging)、Stash 完整 UI(apply/pop/drop/clear/keep-index)、Git Staging Area 模式开关。 +**M4 — Shelf + Partial/行级提交 + Stash UI(功能收口,0.6.0)** +- 交付:完整 Shelf(patch 存储 + unshelve 三方合并)、行级 partial commit(参考 `PartialChangesUtil` 实现 + 行级 hunk staging)、Stash 完整 UI(apply/pop/drop/clear/keep-index)、Git Staging Area 模式开关。 - 验收:shelve/unshelve with conflict 可解;单文件部分行可单独提交。 - 依赖:M2、M3。 @@ -213,13 +213,13 @@ hyper-git/ ## 6. AI 集成架构预留点(现建接缝,M5 实现) -> 对齐 IDEA `CheckinHandler` 生命周期(Track1 D 节:`beforeCheckin`/`CommitCheck.runCheck`/`includedChangesChanged`/`checkinSuccessful`/`checkinFailed`)。**只定义契约 + Null 实现,不引入 Copilot 依赖**(未启用 AI 用户零负担)。 +> 借鉴 JetBrains `CheckinHandler` 生命周期设计(Track1 D 节:`beforeCheckin`/`CommitCheck.runCheck`/`includedChangesChanged`/`checkinSuccessful`/`checkinFailed`)。**只定义契约 + Null 实现,不引入 Copilot 依赖**(未启用 AI 用户零负担)。 -| 接缝(Agent 层) | 对齐 IDEA | 为何现在抽 | +| 接缝(Agent 层) | 借鉴 JetBrains 机制 | 为何现在抽 | |---|---|---| | `ILlmProvider`(模型来源抽象) | — | **最关键**:未来切换 vscodeLM/byok/自带 key 的命脉;晚抽则所有 AI 调用散落、迁移成本爆炸 | | `ICommitMessageProvider` | (IDEA 无内置,插件有) | 提交信息是 commit 流水线核心产物,留接缝让"无 AI→LM→自带 key"平滑切换 | -| `IPreCommitInspector` | `beforeCheckin`/`CommitCheck.runCheck`(返回 COMMIT/CANCEL/DEFER,对齐 `ReturnResult`) | 复用 IDEA 20+ 年验证的 hook 闸门机制;AI 审查最佳挂载点 | +| `IPreCommitInspector` | `beforeCheckin`/`CommitCheck.runCheck`(返回 COMMIT/CANCEL/DEFER,参考 `ReturnResult`) | 借鉴 JetBrains 20+ 年验证的 hook 闸门机制设计;AI 审查最佳挂载点 | | `IChangelistGrouper` | (IDEA 无内置) | 写回 changelist 模型(回写工作流,差异化于内置 Copilot) | | `IConflictResolver` | (IDEA 无内置) | 必须 `prepareInvocation` 用户确认(VS Code 工具确认机制,安全红线) | @@ -259,7 +259,7 @@ hyper-git/ - **单元测试(Vitest,< 30s)**:`engine/model`、`engine/diff`(行级 patch)、`engine/scm-mapping`(Status→decorations)、`engine/commit`(hook 责任链顺序/阻断)、Conventional Commits linter 纯函数。 - **集成测试(@vscode/test-electron + Mocha,< 2min)**:`adapter/git-repository`(真实 fixture 仓库读 changes/commit/stash)、`adapter/changelist-registry`(持久化往返)、`adapter/webview`(postMessage 协议契约)、Commit 全链路(勾选→message→commit→验证 `git log`)。 - **手动回归清单**:多 changelist 新建/移动/删除/重启持久化;Amend;Commit and Push;Conventional Commits 拦截;Log 图过滤;分支 merge/rebase;shelve/unshelve with conflict;行级 partial commit。 -- **浏览器/编辑器验证**:按 AGENTS.md 浏览器验证协议——用户已认证 Chrome 主 profile 打开真实仓库,截图验证 UI 还原度(Commit 窗口 vs 图1/图2 对齐)。 +- **浏览器/编辑器验证**:按 AGENTS.md 浏览器验证协议——用户已认证 Chrome 主 profile 打开真实仓库,截图验证 Commit 窗口的 UI 渲染与交互完整性(对照设计参考图1/图2)。 - **发布前自证**:Diff 分析、测试覆盖、三平台 CI 绿、`.vsix` 在干净 VS Code + Cursor 实机安装回归。 --- diff --git a/docs/features/log-ci-status.md b/docs/features/log-ci-status.md new file mode 100644 index 0000000..f976421 --- /dev/null +++ b/docs/features/log-ci-status.md @@ -0,0 +1,77 @@ +# Log 视图 CI 状态(GitHub Actions / Commit Status) + +> 在 Log 视图(`hyperGit.log`,可视化提交图)每条提交上显示其 **CI 最终状态**:绿勾=通过、红叉=失败、 +> 运行中=黄色旋转;悬停图标以浮层 Tooltip 展示「各项检查 + 未通过原因 + 运行链接」。 +> 数据源为 GitHub(Checks API + Commit Status),按 origin 远程主机自动判定 github.com / GitHub Enterprise。 + +## 数据流 + +```mermaid +flowchart LR + subgraph Git["本地"] + A["git log → GraphRowVM"] --> B["图先渲染(CI 不阻塞)"] + end + subgraph WV["Webview(可见行懒加载)"] + B --> C["滚动收集未知 hash"] + C -->|"防抖 200ms"| D["log/requestCi"] + end + subgraph Host["Extension Host"] + D --> E["解析 origin 远程\nowner/repo/host"] + E --> F["vscode.authentication\n取 token(repo 范围)"] + F --> G["GraphQL 批量 ≤100 oid\nstatusCheckRollup"] + G --> H["按 oid 缓存\n终态永久 / pending 30s"] + end + H -->|"log/ciData"| I["webview 就地重绘图标"] + I --> J["悬停 → Tooltip 明细"] + J -->|"log/openExternal"| K["host 校验主机后\nopenExternal"] + style A fill:#1f6feb,color:#fff + style G fill:#238636,color:#fff + style H fill:#8957e5,color:#fff + style J fill:#d29922,color:#fff +``` + +## 认证与安全 + +- 复用 **VS Code 内置 GitHub 认证**(`vscode.authentication`),凭证由编辑器托管,**绝不经过 chat / 日志 / webview**。 +- 范围 `repo`:覆盖私有仓库的 Checks(Actions)+ Commit Status 读取(`repo:status` 不覆盖 Checks API)。 +- **静默优先**:加载时 `getSession({createIfNone:false})` 仅复用已有会话,**绝不自动弹窗**;仅当用户点击工具栏「登录 GitHub」 + 按钮时才以 `{createIfNone:true}` 触发原生授权 UI。未登录 → 显示登录提示、不渲染图标、不发请求。 +- **反 SSRF**:Tooltip 的跳转链接(`detailsUrl` / `targetUrl`,属「观察内容」)由 host 校验 `https` 且主机 ∈ {仓库主机、`*.github.com`} 后才 `openExternal`。 + +## 限流与性能 + +- **懒加载、仅取可见行**:webview 虚拟滚动只渲染 ~50 行,滚动时收集未知 hash(防抖 200ms)批量请求。1000 条提交永不触发 1000 次请求。 +- **批量 GraphQL**:单次最多 100 个 oid 的 `statusCheckRollup`(别名批量),并发上限 2。 +- **缓存**:终态(success/failure)整会话缓存(提交 CI 结果不可变);pending/unknown 30s TTL,运行中构建会逐步刷新为终态。 +- **限流冷却**:读取响应 `rateLimit{remaining,resetAt}` 与 `Retry-After`;剩余点数 <100 或 403 时进入冷却,期间只走缓存并给出一次性提示。 +- **降级**:未推送提交(远程无此 object)/ 无 CI 配置 → 不渲染图标;网络错误不缓存、下次滚动重试;断网/限流不崩溃、建图正常。 + +## 边界行为 + +| 场景 | 表现 | +|---|---| +| 未推送 / 本地提交 | 无图标(远程无对应 object → unknown) | +| 仓库无 CI 配置 | 无图标(rollup 为 null → unknown) | +| 非 GitHub 远程(GitLab 等) | 功能隐藏(零图标、零请求) | +| GitHub Enterprise | 按 origin 主机自动判定,使用 `github-enterprise` provider(需用户已配置 `github-enterprise.uri`) | +| 窄屏(.narrow 隐藏 author/date) | CI 图标例外保留可见 | + +## 配置 + +| 键 | 默认 | 说明 | +|---|---|---| +| `hyperGit.log.ci.enabled` | `true` | 总开关。关闭后零图标、零请求。 | +| `hyperGit.log.ci.remote` | `""` | 查询用的远程名;留空=自动(优先 `origin`),多远程可指定如 `upstream`。 | +| `hyperGit.log.ci.provider` | `auto` | `auto`(按主机判定)/ `github.com` / `github-enterprise`。 | + +## 实现 + +- 引擎层(纯逻辑,Vitest 可测):[`engine/ci/`](../../src/engine/ci) — `remote-parser.ts`(URL→坐标/端点)、`graphql-query.ts`(批量查询构造)、`rollup.ts`(状态归一化/聚合)、`model.ts`(响应解析)、`types.ts`。 +- 适配层(唯一触碰 vscode/网络):[`adapter/ci/`](../../src/adapter/ci) — `github-auth.ts`(认证)、`github-ci-service.ts`(缓存/批量/限流/降级/openExternal)。 +- 协议:[`shared/protocol.ts`](../../src/shared/protocol.ts) — `log/requestCi`、`log/ciData`、`log/ciMeta`、`log/openExternal`、`log/ciSignIn`。 +- 渲染:[`adapter/webview/log-webview.ts`](../../src/adapter/webview/log-webview.ts) — 内联 JS 的懒加载、图标槽位(提交行最右侧)、自定义 Tooltip 浮层。 + +## 验证 + +1. `pnpm run check-types` + `pnpm run lint` + `pnpm run test:unit`(含 `tests/unit/ci-*.test.ts`)全绿。 +2. Extension Development Host(F5)在 GitHub 仓库:未登录见「登录 GitHub」提示 → 点击原生授权 → 可见行右侧渐次出现图标;悬停红叉见明细 + 链接;点击打开 run;未推送/非 GitHub → 无图标;断网/限流不崩溃。 diff --git a/docs/milestones/implementation-status.md b/docs/milestones/implementation-status.md index df0cdb1..d31d0df 100644 --- a/docs/milestones/implementation-status.md +++ b/docs/milestones/implementation-status.md @@ -2,16 +2,16 @@ > Hyper Git VS Code 扩展的里程碑实施记录、API 限制发现、功能达成矩阵与 M5 AI 设计(留存)。 > 本文档随里程碑推进即时更新;调研与原始方案见 [工程实施方案](../architecture/engineering-plan.md) 与 [调研报告](../research/README.md)。 -> 最后更新:2026-06-29(Parity Recovery Batch 5-12 全量对齐 IDEA 完成)。 +> 最后更新:2026-06-29(Parity Recovery Batch 5-12 全量功能对齐完成)。 > -> **⚠️ 重大更新(2026-06-29,Batch 5-12)**:经 IDEA Git 功能复刻评审,发现「功能多数已实现,但被 Branches 视图渲染 bug + 工具栏未浮现 + 命令 bug 掩盖」。本批 Recovery **先解除可见痛感,再全量补齐**:新增 13 个 engine 纯逻辑模块 + 21 个命令,单测 64 → 166、命令 56 → 77。**§3 所列 vscode.git 稳定 API 限制(cherry-pick/revert/reset/branch rename/hunk 暂存/stash list/graph topology/shelf/author 等)现均已通过 `GitRepositoryService.execGit`(复用 `api.git.path` 同一 git 二进制)受控 CLI 通道解决**。详见 [CHANGELOG](../../CHANGELOG.md) [Unreleased]。 -> AI M5 暂不启动(5 个接缝保留 Null 实现,本轮专注 IDE 对齐)。 +> **⚠️ 重大更新(2026-06-29,Batch 5-12)**:经 Git 功能完备性评审,发现「功能多数已实现,但被 Branches 视图渲染 bug + 工具栏未浮现 + 命令 bug 掩盖」。本批 Recovery **先解除可见痛感,再全量补齐**:新增 13 个 engine 纯逻辑模块 + 21 个命令,单测 64 → 166、命令 56 → 77。**§3 所列 vscode.git 稳定 API 限制(cherry-pick/revert/reset/branch rename/hunk 暂存/stash list/graph topology/shelf/author 等)现均已通过 `GitRepositoryService.execGit`(复用 `api.git.path` 同一 git 二进制)受控 CLI 通道解决**。详见 [CHANGELOG](../../CHANGELOG.md) [Unreleased]。 +> AI M5 暂不启动(5 个接缝保留 Null 实现,本轮专注功能完备性)。 --- ## 0. 概览 -- **架构**:路径 B —— 消费内置 `vscode.git` 稳定 `Repository` API + 自建 changelist registry + 独立视图容器自绘 IDEA 风格 UI,不接管原生 Source Control 视图。 +- **架构**:路径 B —— 消费内置 `vscode.git` 稳定 `Repository` API + 自建 changelist registry + 独立视图容器自绘 UI,不接管原生 Source Control 视图。 - **分层**:`engine/`(纯逻辑,零 vscode 依赖)/ `adapter/`(唯一接触 vscode API)/ `agent/`(AI 接缝)/ `shared/`(契约)/ `infra/`。 - **质量基线**:TypeScript strict + ESLint9 + Vitest(engine 单测)+ @vscode/test-electron(adapter 集成)+ esbuild + 三平台 CI(Linux xvfb)。 - **验证**:单测 45/45、集成 3/3(含真实 git 提交闭环 + amend)、`vsce package` 产 vsix。 @@ -27,7 +27,7 @@ | M0-M2 审查修复 | 0.3.1 | [#5](https://github.com/ThreeFish-AI/hyper-git/pull/5) | 11 类正确性修复(订阅泄漏/仓库选取/indexChanges/commit 语义/push 警告/linter 等)+ 16 项测试补齐 | 单测 45/45 + 集成 3/3 | | M3 Log/Branches/Blame | 0.4.0 | [#6](https://github.com/ThreeFish-AI/hyper-git/pull/6) | Log TreeView(过滤+copy hash+Show History)、Branches TreeView(create/checkout/delete/merge/rebase)、Blame | 命令注册集成 | | M4 Stash/Shelf | 0.5.0 | [#7](https://github.com/ThreeFish-AI/hyper-git/pull/7) | Stash 视图(log refnames 枚举)+ create/apply/pop/drop;Shelf MVP(stash 近似) | 命令注册集成 | -| **Parity Recovery Batch 5-7** | — | commit `00b5ba7`/`2105445`/`9699ee9` | Branches 渲染修复 + 工具栏 Action 组 + 冲突兜底;Branches 对齐 IDEA(Favorites/Tags/ahead-behind);Rebase webview reword/edit/拖拽 1:1 复刻 | 单测 166/166 + 集成 3/3 | +| **Parity Recovery Batch 5-7** | — | commit `00b5ba7`/`2105445`/`9699ee9` | Branches 渲染修复 + 工具栏 Action 组 + 冲突兜底;Branches 视图增强(Favorites/Tags/ahead-behind);Rebase webview 支持 reword/edit/拖拽交互式编排 | 单测 166/166 + 集成 3/3 | | **Parity Recovery Batch 8-9** | — | commit `efd888c`/`ed01883` | Log 提交详情面板 + 高级过滤 + per-commit 操作;真实 SVG 提交拓扑图(解析 git --graph 字符粒度渲染 + 可点击 + 实时刷新) | 同上 | | **Parity Recovery Batch 10-12** | — | commit `2274d8a`/`fcb6ffa`/`acdf53c` | Push/Update/Merge 对话框 + fetch prune + Co-authored-by trailer;自绘 3-way Merge Editor(自实现 diff3);Stash 高级 + Patch + Reflog + Blame 行内注解 | 同上 | @@ -79,7 +79,7 @@ **5 个 AI 接缝**(agent/,依赖 engine 不依赖 adapter): - `ILlmProvider`:模型来源抽象(vscodeLM / byok-Ollama / openaiCompatible)。**最关键**——未来切换模型来源的命脉。 - `ICommitMessageProvider`:AI 提交信息生成(staged diff + 团队规范 → 流式 CC 合规 message)。 -- `IPreCommitInspector`:提交前 AI 代码审查(对齐 IDEA `CheckinHandler.beforeCheckin`,可阻断)。 +- `IPreCommitInspector`:提交前 AI 代码审查(参考 JetBrains `CheckinHandler.beforeCheckin` 责任链设计,可阻断)。 - `IChangelistGrouper`:变更语义分组(回写 changelist)。 - `IConflictResolver`:冲突解决(用户逐块确认)。 @@ -104,6 +104,6 @@ ## 7. 发布状态 -- **当前版本**:0.5.0(preview,内部里程碑)。 -- **首发候选**:v0.0.1-rc.1(package 版本 `0.0.1` + `--pre-release` + git tag `v0.0.1-rc.1`)。Marketplace 仅支持 `major.minor.patch`,预发布语义由 `--pre-release` 标记 + tag 体现。 +- **当前版本**:0.0.6(首个 MVP 正式版,对外首发;以「Hyper Git - Agentic Git」之名上架 Marketplace / OpenVSX)。 +- **首发历程**:经若干内部迭代与 `v0.0.1-rc.*` 预发布打磨后,以 `v0.0.5` 完成内部首发;因 Marketplace 上「Hyper Git」名称被误删不可用,遂将扩展更名为 **Hyper Git - Agentic Git**(`package.json` `name=hyper-git-agentic-git`),以 `v0.0.6`(git tag `v0.0.6`)重新上架。Marketplace 仅支持 `major.minor.patch`,预发布语义由 `--pre-release` 标记 + tag 体现。 - **发布前置**:publisher 账号(`threefish-ai`)、VSCE_PAT / OVSX_PAT secrets、PNG 图标。 diff --git a/docs/releases/README.md b/docs/releases/README.md new file mode 100644 index 0000000..df9ac29 --- /dev/null +++ b/docs/releases/README.md @@ -0,0 +1,17 @@ +# 发布说明(Release Notes) + +> 各正式版本的发布说明,面向用户、覆盖该版本的全部特性。 + +本目录是 GitHub Release 正文的**单一事实源**:CI 在推送 `v*` 标签时,由 `github-release` job 经 `body_path: docs/releases/${tag}.md` 取对应文件作为 Release 正文(详见 [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml))。因此**每个 `v*` 标签都必须先有同名 Release Note 文件**,否则发布会失败——这是有意的发布纪律闸门。 + +## 版本索引 + +| 版本 | 说明 | 日期 | +|---|---|---| +| [v0.0.6](./v0.0.6.md) | 首个 MVP 正式版(以「Hyper Git - Agentic Git」之名上架) | 2026-06-30 | + +## 相关文档 + +- [CHANGELOG](../../CHANGELOG.md) — 工程视角的逐版本变更记录(Added / Changed / Fixed)。 +- [README](../../README.md) — 项目说明、能力总览与开发指南。 +- [文档中心](../README.md) — 全部文档与调研资产索引。 diff --git a/docs/releases/v0.0.6.md b/docs/releases/v0.0.6.md new file mode 100644 index 0000000..801d835 --- /dev/null +++ b/docs/releases/v0.0.6.md @@ -0,0 +1,106 @@ +# Hyper Git - Agentic Git v0.0.6 — 首个 MVP 正式版 + +> 为 VS Code 带来统一的 **Git 变更管理** 与 **提交工作流**,并为未来 AI Agent 自主代理预留架构接缝。 + +这是本扩展的**首个对外正式版本**,在 VS Code Marketplace / OpenVSX 上以 **「Hyper Git - Agentic Git」** 之名发布。它把开发者高频依赖、却在 VS Code 原生 Source Control 中长期缺失的 Git 工作流**一次性补齐**:多 Changelist 变更组织、自绘提交面板、可视化提交图、Shelf、行级提交,并与原生 Source Control **平行共存、零冲突**。 + +--- + +## ✨ 亮点速览 + +- **多 Changelist 变更组织**:把工作区改动分组到命名列表,设活动列表为提交目标,重启自动恢复。 +- **自绘 Commit 提交面板**:勾选式提交面板,Conventional Commits 实时校验,Amend / Sign-off / 跳过 Hook,提交或提交并推送。 +- **可视化提交图(自绘 DAG)× CI 状态**:彩色泳道、分叉·合并连线、HEAD/分支/标签徽标;每条提交行右侧实时显示 **GitHub CI 最终状态**(绿勾/红叉/运行中),悬停查看各项检查、失败原因与跳转链接——告别 `git log --graph` 的粗糙 ASCII,也无需切到浏览器查构建结果。 +- **Branches 多选批量操作**:框选多个分支/标签,一次性删除、复制引用、收藏;已合并/未合并诚实分栏确认。 +- **独立 Shelf + 全量 Worktree**:基于 patch 的 Shelf(独立于 stash)与 Worktree 全生命周期管理(新建/打开/锁定/移动/删除)。 +- **行级与 Hunk 提交**:编辑器内 CodeLens「提交此 Hunk」、部分暂存、光标处暂存、Hunk 归属 Changelist。 +- **3-way Merge Editor**:自绘三栏冲突编辑器(OURS / RESULT / THEIRS),冲突时一键调起。 + +--- + +## 🧩 完整能力 + +### 变更与 Changelist +按 Changelist 分组的 Changes 树(状态色复用 `gitDecoration.*` 主题色);新建 / 重命名 / 删除 / 设活动 / 跨列表移动文件,`workspaceState` 持久化;单击打开原生 Diff(HEAD ↔ Working);丢弃改动、加入 `.gitignore`、显示文件历史。 + +### Commit 提交窗口 +多行消息编辑器 + Conventional Commits 实时校验(可配置开关);Amend、Signed-off-by、跳过 Git Hooks;勾选集即提交权威范围(仅提交选中的文件集合);最近消息一键复用;**提交** 与 **提交并推送** 双动作;提交流水线采用责任链设计(参考 JetBrains `CheckinHandler`:校验 → 暂存 → Hook 链 → 提交 → 可选推送)。 + +### Log 提交图与历史 +自绘 **Graph DAG** webview(彩色泳道 / 节点 / 连线 / 引用徽标,拓扑保序,虚拟化滚动,↑↓ 键导航);选中提交内联展开变更文件并打开单文件 Diff;**7 个可组合过滤器**——作者、路径、message(grep)、message(正则)、合并模式、日期、一键清除;per-commit 操作——复制 hash、Reset 到此、从此新建分支/标签、查看包含此提交的分支、Cherry-Pick、Revert。 + +#### 提交图 × CI 状态(GitHub Actions / Commit Status) +每条提交行最右侧显示其 **CI 最终状态**:绿勾=通过、红叉=失败、运行中=黄色旋转。悬停图标以浮层 Tooltip 展示「各项检查 + 未通过原因 + 运行链接」,让你**在提交图里一眼看出哪次提交挂了 CI**,无需跳转浏览器。 +- 复用 **VS Code 内置 GitHub 认证**(`vscode.authentication`),凭证由编辑器托管,**绝不经过 chat / 日志 / webview**;范围 `repo`,静默优先、仅点击「登录 GitHub」时才触发原生授权。 +- **性能与限流**:仅取可见行懒加载(虚拟滚动 ~50 行)、批量 GraphQL(单次 ≤100 oid)+ 限流冷却;终态整会话缓存、pending 30s 刷新。 +- **零打扰降级**:非 GitHub 远程(GitLab 等)零图标零请求;未推送/无 CI 配置 → 不渲染图标;断网/限流不崩溃、建图正常。支持 github.com 与 GitHub Enterprise(按 origin 主机自动判定)。 + +#### Checkpointer 过滤 +Log 视图新增 **Checkpointer 选项**,**默认剔除** AI 编码工具产生的自动快照(checkpoint)提交,让提交图回归「人写的人类提交」,避免被海量自动提交淹没;可在过滤器面板按需开启查看。 + +### Branches 分支管理 +四段分组(收藏 / 本地 / 远程 / 标签)+ ahead·behind·upstream 跟踪展示;新建 / 检出 / 删除 / 重命名 / 合并 / 变基 / 从选中新建并检出;收藏切换、与当前分支比较、任意两分支比较、复制引用、清理已合并分支;**多选批量**:批量删除分支/标签、批量复制引用、批量收藏(已合并/未合并风险诚实分栏确认)。 + +### 标签 Tags +新建(轻量/附注)、删除(支持多选)、检出(进入 detached HEAD)、从提交新建标签。 + +### Stash 与 Shelf +**Stash**:创建、保留已暂存创建、应用、Pop、删除、从 Stash 创建分支、清空全部,按真实 `stash@{n}` 索引操作。**Shelf**(基于 patch、独立于 git stash 的改动搁置机制):Shelve 暂存、静默 Unshelve、带 3-way 合并 Unshelve、删除。 + +### 远程操作 +Pull / Push / Fetch(无上游分支自动建立 `-u` 跟踪);**Push…**(normal / force-with-lease / force + 推送标签)、**Update Project…**(merge / rebase)、**Merge…**(ff-only / no-ff / squash + 自定义消息)三类对话框;冲突兜底引导与 **3-way Merge Editor**,以及冲突文件「采用 Ours / Theirs」。 + +### 历史编辑与高级操作 +Cherry-Pick、Revert、Reset HEAD(soft / mixed / hard / keep)、交互式 Rebase(webview:pick/squash/fixup/drop + reword + 拖拽重排)、撤销最近提交(soft)、删除提交(rebase)、Fixup(autosquash)、改写最新提交信息。 + +### 编辑器内能力 +**行内提交**(每个未暂存 Hunk 上方 CodeLens「提交此 Hunk」);部分暂存 / 取消暂存、光标处暂存、Hunk 归属 Changelist;**Blame 行内注解**(逐行作者/日期/hash + 悬浮详情)。 + +### 工具 +导出 / 应用 Patch、查看 Reflog、3-way Diff 概览(HEAD ↔ Staged ↔ Working)、Console 命令输出面板。 + +### 配置项 +- `hyperGit.commit.template`(提交模板)、`hyperGit.commit.conventional`(CC 校验开关)、`hyperGit.ai.enabled`(AI 能力开关,M5 预留)。 +- `hyperGit.log.ci.enabled` / `hyperGit.log.ci.remote` / `hyperGit.log.ci.provider`(提交图 CI 状态:总开关 / 查询远程 / 提供者 auto·github.com·github-enterprise)。 + +> 规模实证:**7 个视图 / 93 个命令 / 6 个配置项 / 280 个单元测试**,覆盖 Git 变更管理主线工作流。 + +--- + +## 🏗️ 架构 + +采用 **路径 B(消费 + 自绘)**,与原生 Source Control 平行共存、零冲突: + +- **消费** 内置 `vscode.git` 扩展导出的稳定 `Repository` API 作为 git 底座,不重造 git 状态机。 +- **受控 CLI 通道**:稳定 API 未覆盖的能力(cherry-pick / revert / reset / 分支重命名 / hunk 暂存 / stash 列表 / graph 拓扑 / shelf 等)经 `GitRepositoryService.execGit` 复用同一 git 二进制(`api.git.path`)实现。 +- **自绘视图** 承载完整的变更管理 UI;纯逻辑沉淀于 `engine/`(零 vscode 依赖、可单测)。 +- **AI 接缝**:预埋 5 个接口(`ILlmProvider` / `ICommitMessageProvider` / `IPreCommitInspector` / `IChangelistGrouper` / `IConflictResolver`,设计参考 JetBrains `CheckinHandler` 提交生命周期),M5 替换为真实实现,本版未启动。 + +质量基线:**280 个单元测试**(engine 纯逻辑)+ 集成测试(`@vscode/test-electron` + fixture 仓库),CI 三平台(Ubuntu / macOS / Windows)矩阵校验,lint / 类型 / 构建全程 GREEN。 + +--- + +## 📦 安装 + +- **手动安装(当前推荐)**:从 [Releases](https://github.com/ThreeFish-AI/hyper-git/releases) 下载 `hyper-git-agentic-git-0.0.6.vsix` → 命令面板执行 `Extensions: Install from VSIX`。 +- **OpenVSX**(Cursor / Windsurf / Gitpod / VSCodium):搜索 `Hyper Git - Agentic Git`。 +- **VS Code Marketplace**:搜索 `Hyper Git - Agentic Git`(发布上线后可用)。 + +**系统要求**:VS Code ≥ 1.85.0,且启用内置 Git 扩展(`vscode.git`,默认随附)。仅支持本地 git 仓库,不支持虚拟 / Web 工作区。 + +--- + +## ⚠️ 已知限制 + +- Commit 窗口的 Co-authored-by / Author 覆盖(`--author`)/ 撤销最近提交的**按钮 UI 接线**仍待补(engine `trailer` 已就绪,仅缺 webview 交互)。 +- Partial 多文件选择 UX、行级 split chunks(按选定行拆分提交)。 +- 目录 / folder diff(虚拟文档)、Submodules 管理。 +- M5 AI Agent:5 个接缝已预埋 Null 实现,本版未启动。 + +--- + +## 🙏 致谢与许可 + +图标字形改编自 [Tabler Icons](https://github.com/tabler/tabler-icons)(MIT)。完整工程变更见 [CHANGELOG](https://github.com/ThreeFish-AI/hyper-git/blob/master/CHANGELOG.md)。 + +许可证:[MIT](https://github.com/ThreeFish-AI/hyper-git/blob/master/LICENSE)。 diff --git a/docs/requirements/idea-feature-matrix.md b/docs/requirements/idea-feature-matrix.md index 462b269..3d6fb94 100644 --- a/docs/requirements/idea-feature-matrix.md +++ b/docs/requirements/idea-feature-matrix.md @@ -1,6 +1,6 @@ # IntelliJ IDEA 社区版 Git/Commit 模块 调研报告 -> 调研目标:产出 IDEA「Git 工具窗口 + Commit 提交窗口」的【完整功能清单 + 关键源码锚点】,作为 VS Code 插件复刻的需求规约(Spec)基线。 +> 调研目标:产出 IDEA「Git 工具窗口 + Commit 提交窗口」的【完整功能清单 + 关键源码锚点】,作为本扩展 Git 变更管理与提交工作流的功能需求规约(Spec)基线(参考 IDEA 等成熟实现)。 > 仓库:[JetBrains/intellij-community](https://github.com/JetBrains/intellij-community)(master 分支,2025–2026 年版本) > 官方文档:[IntelliJ IDEA Help 2026.1](https://www.jetbrains.com/help/idea/) > 调研时间:2026-06-27 @@ -44,7 +44,7 @@ ### 组 1:Commit 窗口(Commit / Shelf / Stash 标签页 + 提交流水线) -| # | 功能名 | 用户可见行为 | 触发入口 | 底层 git/IDEA 机制 | 源码锚点(类路径) | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层 git/IDEA 机制 | 源码锚点(类路径) | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 1 | Commit 工具窗口(竖向,Alt+0) | 左侧竖向变更列表 + 提交信息区 + Diff 预览;非模态 | `Alt+0` / `Ctrl+K` | 平台 `CommitDialog/CommitToolWindow`,git4idea `GitCheckinEnvironment` | `vcs-impl/.../changes/ui/CommitChangeListDialog.java`、`DefaultCommitChangeListDialog.kt`;git4idea `checkin/GitCheckinEnvironment.kt` | 高 | 部分(SCM 面板,无竖向 commit 工具窗口形态) | | 2 | Commit Message 模板 | 默认填充 commit message(来自 `.git/COMMIT_TEMPLATE` / `commit.template` / merge message) | 打开 commit 窗口自动填充 | `git config commit.template`;EP `com.intellij.vcs.commitMessageProvider` | `commit/GitTemplateCommitMessageProvider.kt`;`vcs-api/.../changes/ui/CommitMessageProvider.java`;`checkin/GitCheckinEnvironment.getDefaultMessageFor`(merge message) | 低 | 无原生(需插件/git config) | @@ -63,7 +63,7 @@ ### 组 2:Local Changes 变更列表(多 changelist 模型) -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 15 | 多 changelist | 同时维护多个命名变更列表 | Commit 窗口左侧树 | `ChangeListManager` + `LocalChangeList` 模型 | `vcs-api/.../changes/ChangeListManager.java`、`LocalChangeList`(接口);`vcs-impl/.../changes/ChangeListManagerImpl.java`、`ChangeListWorker.java` | 高 | **无**(VS Code SCM 仅多 group,非命名 changelist) | | 16 | Active changelist | 设置默认活动列表;新改动落入此列表 | `Ctrl+Space` / 右键 Set Active | `getDefaultChangeList/setDefaultChangeList`;命令 `SetDefault` | `ChangeListManager`;`changes/local/SetDefault.java` | 中 | **无**(无 active 概念) | @@ -75,7 +75,7 @@ ### 组 3:Partial / Selective / 按行(line-level)提交 -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 22 | 选择性勾选文件提交 | 勾选/取消文件,未勾选保留 | 复选框 | changelist 内子集提交 | `vcs-impl/.../changes/ui/CommitDialogChangesBrowser.java`;`GitCheckinEnvironment.commit(changes)` 接受子集 | 低 | 是(SCM 文件勾选) | | 23 | 按代码块(chunk)提交 | Diff 中勾选 chunk 提交,其余保留 | Diff 区勾选 | `PartialLocalLineStatusTracker` + `PartialCommitHelper` | `vcs-impl/.../impl/PartialChangesUtil.kt`(`getPartialTracker`/`processPartialChanges`);`vcs-api/.../vcs/ex/PartialCommitHelper`;`GitCheckinEnvironment.addPartialChangesToIndex` | **高** | 部分(git staging + chunk staging,VS Code 1.70+ 支持 staging selected lines) | @@ -85,7 +85,7 @@ ### 组 4:Shelf 与 Stash -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 27 | Shelve changes(IDEA patch) | 暂存选定改动为 IDEA patch;可选择部分文件 | 右键 Shelve Changes / Shelve Silently(`Ctrl+Shift+H`) | `ShelveChangesManager`(patch 文件存储) | `vcs-impl/.../changes/shelf/ShelveChangesManager.java`、`ShelveChangesAction.kt`、`ShelveChangesCommitExecutor.java` | 中 | **无**(需 git stash 替代) | | 28 | Unshelve silently / with conflict | 还原 shelf;静默或弹冲突解决 | `Ctrl+Shift+U` / Unshelve Silently(`Ctrl+Alt+U`)/ 拖拽 | `ShelvedChangesViewManager` + 3-way merge(冲突) | `shelf/UnshelveWithDialogAction.java`、`ShelvedChangesViewManager.java`、`RestoreShelvedChange.java` | 中 | 无 | @@ -98,7 +98,7 @@ ### 组 5:Diff(对比) -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 35 | 与 HEAD/分支/本地对比 | Diff Viewer 对比本地 vs HEAD / 任意分支 / 本地版本 | Diff 按钮(`Ctrl+D`)/ Compare HEAD, Staged and Local | `DiffProvider`/`GitDiffProvider` | `git4idea/diff/`;`ui/GitShowDiffWithBranchPanel.kt`;`branch/GitCompareBranchesUi.kt` | 中 | 是(editor diff) | | 36 | Compare HEAD/Staged/Local 三方 | 三窗 Diff(repo / 中央可编辑 staging / local) | 右键 Compare HEAD, Staged and Local Versions | staging area interactive staging | 官方文档「Stage changes interactively」;`checkin/GitIndexUtil`(`listStaged`/`listTree`) | 中 | 部分 | @@ -107,7 +107,7 @@ ### 组 6:Log 提交图 -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 39 | 提交图(graph) | 分支拓扑图、彩色节点 | Git 工具窗口 Log tab(`Alt+9`) | `platform/vcs-log`(VcsLog data + UI) | `platform/vcs-log/`(`VcsLogUi`、graph 渲染);git4idea `log/` | **高** | 无原生(需 GitGraph 等插件) | | 40 | Search / filter(author/path/date/branch/regex) | 按 author、path、date、branch、正则过滤 | Log toolbar 过滤 | VcsLog filter 体系 | `platform/vcs-log/`(filter providers);官方文档 [Log Tab](https://www.jetbrains.com/help/idea/log-tab.html) | 中 | 部分(无原生图形 log 过滤) | @@ -118,7 +118,7 @@ ### 组 7:Branches -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 45 | Create / Checkout branch | 新建并检出 / 检出现有 / checkout-as-new | VCS widget / Branches pane | `git checkout -b/-` | `branch/GitCreateBranchOperation.kt`、`GitCheckoutOperation.java`、`GitCheckoutNewBranchOperation.java`;`actions/branch/GitCheckoutAsNewBranch.kt` | 低 | 是(命令面板) | | 46 | Delete / Rename branch | 删除本地/远程分支/标签、重命名 | 右键 Delete/Rename | `git branch -d/-D`/`-m`;`git push origin --delete` | `branch/GitDeleteBranchOperation.java`、`GitDeleteRemoteBranchOperation.java`、`GitRenameBranchOperation.java`、`GitDeleteTagOperation.java` | 低 | 部分 | @@ -129,7 +129,7 @@ ### 组 8:右键 / 内联操作 -| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 复刻难度 | VS Code 原生 | +| # | 功能名 | 用户可见行为 | 触发入口 | 底层机制 | 源码锚点 | 实现难度 | VS Code 原生 | |---|---|---|---|---|---|---|---| | 51 | Revert / Rollback | 回滚未提交改动(`RollbackEnvironment`) | 右键 Rollback / `RollbackChangesDialog` | `git checkout --` / reset | `rollback/GitRollbackEnvironment.java`;`vcs-impl/.../changes/ui/RollbackChangesDialog.kt`、`RollbackWorker.java` | 中 | 是(Discard Changes) | | 52 | Reset HEAD(mixed/soft/hard/keep) | Git Reset 对话框选择模式 | 右键 / `GitResetHead` action | `git reset --soft/--mixed/--hard/--keep` | `actions/GitResetHead.java`;`ui/GitResetDialog.java` | 低 | 无原生(命令式) | @@ -153,7 +153,7 @@ | 提交范围 | 提交所选 changelist(或其中勾选子集) | 提交整个 Staged group | | 自动绑定分支 | **无原生 changelist↔branch 自动绑定**;需 Tasks 上下文(`ActiveChangeListTracker`) | 无 | -> **复刻映射建议**:VS Code SCM 的 `ResourceGroup` 难以 1:1 表达「active + 跨文件行级归属」,建议在插件层自建「changelist registry」(仿 `ChangeListManager` + `ChangeListChange`),将 SCM group 作为渲染层,并通过 `LineStatusTracker`-like 行级 tracker 支撑 partial commit。这是 IDEA 模型对 VS Code 最大的差异与复刻难点。 +> **实现映射建议**:VS Code SCM 的 `ResourceGroup` 难以直接表达「active + 跨文件行级归属」,建议在插件层自建「changelist registry」(参考 `ChangeListManager` + `ChangeListChange` 设计),将 SCM group 作为渲染层,并通过 `LineStatusTracker`-like 行级 tracker 支撑 partial commit。这是 IDEA 模型与 VS Code 原生 SCM 模型最大的差异点与实现难点。 --- @@ -169,7 +169,7 @@ | Before-Commit 设置项(Settings 页) | `UnnamedConfigurable getBeforeCheckinSettings()` | `Settings \| VCS \| Commit` 配置页 | 持久化设置 | 中:AI 规则配置 | | After-Commit 配置面板 | `RefreshableOnComponent getAfterCheckinConfigurationPanel(Disposable)` | 构建 After Commit 选项面板 | 注入部署等选项 | 低 | | **Before check-in(核心闸门)** | `ReturnResult beforeCheckin(CommitExecutor, PairConsumer)` / `beforeCheckin()` | 提交按钮按下后、真正 commit 前 | `COMMIT`/`CANCEL`/`CLOSE_WINDOW` 可阻断提交 | **高**:AI 代码审查/质量闸门,可阻断不良提交 | -| **CommitCheck(现代协程闸门)** | `suspend CommitProblem? runCheck(CommitInfo)` / `runGitCheck(commitInfo, changes)` | beforeCheckin 的协程演进版;`commitInfo.isVcsCommit` 时执行 | 返回 `CommitProblem`(含 `showModalSolution`) | **高**:AI 异步审查最佳挂载点(仿 `GitCheckinHandler`) | +| **CommitCheck(现代协程闸门)** | `suspend CommitProblem? runCheck(CommitInfo)` / `runGitCheck(commitInfo, changes)` | beforeCheckin 的协程演进版;`commitInfo.isVcsCommit` 时执行 | 返回 `CommitProblem`(含 `showModalSolution`) | **高**:AI 异步审查最佳挂载点(参考 JetBrains `GitCheckinHandler` 设计) | | CommitCheck 执行顺序 | `CommitCheck.ExecutionOrder getExecutionOrder()` | 排序多个 CommitCheck | `EARLY`/`DEFAULT`/`LATE` | 中:控制 AI 检查与其他检查顺序 | | CommitCheck 启用开关 | `boolean isEnabled()` | 决定是否运行 | 布尔 | 中 | | **Successful 回调** | `void checkinSuccessful()`(`@RequiresEdt`) | 提交成功后 | 通知/后续动作 | 高:AI 提交摘要、自动生成 PR 描述 | @@ -180,7 +180,7 @@ > **CommitContext 关键字段**(贯穿整个流水线,承载 commit 选项):`commitToAmend`、`isSkipHooks`、`commitAuthor`、`commitAuthorDate`、`isSignOffCommit`、`isPushAfterCommit`、`isCommitRenamesSeparately`、`commitWithoutChangesRoots`。源码:`GitCheckinEnvironment.updateState()` / `doCommit()`。 > -> **AI 接入建议**:实现一个 `CommitCheck`(EARLY 顺序)挂载 AI 审查 + 实现 `includedChangesChanged()` 动态生成 commit message + 实现 `checkinSuccessful()` 触发 AI 后处理,可完整复用 IDEA 提交流水线的「设计-实现-验证」闭环,无需重写 commit 引擎。 +> **AI 接入建议**:实现一个 `CommitCheck`(EARLY 顺序)挂载 AI 审查 + 实现 `includedChangesChanged()` 动态生成 commit message + 实现 `checkinSuccessful()` 触发 AI 后处理,可借鉴 JetBrains CheckinHandler 责任链的「设计-实现-验证」闭环,无需重写 commit 引擎。 --- @@ -209,7 +209,7 @@ --- ## F. 待核实 / 不确定项 -1. **LocalChangeList 接口/实现类的精确文件路径**:zread 多次返回「文件不存在」(API 类可能位于非直觉路径或为生成/移动类),但其方法语义已通过 `ChangeListManager.java`(大量引用)+ `PartialChangesUtil.kt`(`LocalChangeList` import)+ `ChangeListWorker.java`(实现侧)交叉证实存在。**建议**复刻阶段直接以 `ChangeListManager` API 为契约蓝本。 -2. **Conventional Commits 内置校验**:WebSearch 与文档检索均未发现 IDEA 内置 CC 强校验类;结论为「IDEA 无原生 CC 校验,依赖 commit message 规则/第三方插件」,需在复刻时自行实现 CC linter。 +1. **LocalChangeList 接口/实现类的精确文件路径**:zread 多次返回「文件不存在」(API 类可能位于非直觉路径或为生成/移动类),但其方法语义已通过 `ChangeListManager.java`(大量引用)+ `PartialChangesUtil.kt`(`LocalChangeList` import)+ `ChangeListWorker.java`(实现侧)交叉证实存在。**建议**实现阶段直接以 `ChangeListManager` API 为契约蓝本。 +2. **Conventional Commits 内置校验**:WebSearch 与文档检索均未发现 IDEA 内置 CC 强校验类;结论为「IDEA 无原生 CC 校验,依赖 commit message 规则/第三方插件」,需在实现时自行构建 CC linter。 3. **Changelist 自动绑定分支的具体类**:`ActiveChangeListTracker.kt` 存在,但「changelist↔branch」自动绑定疑似走 Tasks 上下文模块(非 git4idea),未定位到确切绑定类。 4. **分支级 Pull/Push/Fetch 的精确 action 类**:`actions/branch/GitPullBranchAction.kt` 等结构已确认存在,但内部委托链(→ `GitBranchWorker`/`GitFetch`)未逐行核实。 diff --git a/docs/research/02-vscode-scm-integration.md b/docs/research/02-vscode-scm-integration.md index cf738cd..851ce34 100644 --- a/docs/research/02-vscode-scm-integration.md +++ b/docs/research/02-vscode-scm-integration.md @@ -1,6 +1,6 @@ # Track2 调研报告:VS Code SCM API 与 vscode.git 导出 API 集成路径 -> 调研对象:在 VS Code 中复刻 IDEA 风格 Git 工具窗口(多 changelist + Commit 窗口)的最佳集成路径。 +> 调研对象:在 VS Code 中实现统一 Git 变更管理面板(多分组列表 + 自绘 Commit 提交面板)的最佳集成路径。 > 证据基线:microsoft/vscode 源码(`extensions/git/src/api/git.d.ts`、`api1.ts`、`vscode.d.ts`)、官方 SCM provider 指南、GitHub Issue 追踪记录、同类扩展(GitLens、vscode-pull-request-github)实践。 > 检索时间:2026 年 6 月。所有关键事实附 GitHub 文件路径或官方文档 URL;存疑项标注「待核实」。 @@ -10,7 +10,7 @@ | 决策项 | 结论 | |---|---| -| **推荐路径** | **路径 B(纯消费 vscode.git 导出 API + 自建 TreeView/WebviewView 渲染 IDEA UI),原生 Source Control 视图保持不动** | +| **推荐路径** | **路径 B(纯消费 vscode.git 导出 API + 自建 TreeView/WebviewView 渲染自绘 UI),原生 Source Control 视图保持不动** | | **changelist 模型** | VS Code SCM 是「分组(group)」模型,不是 IDEA 的「多 changelist」。多 changelist 用**自建 TreeView** 表达最忠实;若想借用原生视图,可用「每个 changelist 一个 `SourceControlResourceGroup`」近似但语义有损 | | **Commit 窗口 UI** | 放在 **Secondary Side Bar 的 WebviewView**(自建视图容器),自带 Commit/Shelf/Stash 标签页 + Commit Message 编辑器;**不依赖也不替代**原生 `SourceControlInputBox`(稳定字段太弱,且 `SourceControlInputBoxValueProvider` 已被官方删除) | | **提交图(Log)** | 原生 Source Control Graph 的 `scmHistoryProvider` **仍是 proposed API**(截至 2025-05 无 stable 时间表),不能稳定复用。Log 提交图须自建 TreeView + 消费 `Repository.log()` | @@ -86,7 +86,7 @@ createResourceGroup(id, label) → { id, label, resourceStates, hideWhenEmpty, . | `iconPath?: string \| Uri \| {light, dark}` | 自定义图标 | | `source?: string` | 来源标注 | -> IDEA 的 M/A/D/U/Renamed/Copied 完全可由 `letter` + `color` 组合复刻(git 扩展就是这么做的,见其 `Resource` 类的 decorations 计算)。 +> M/A/D/U/Renamed/Copied 等文件状态色完全可由 `letter` + `color` 组合实现(git 扩展就是这么做的,见其 `Resource` 类的 decorations 计算)。 ### 1.5 QuickDiff(编辑器 gutter 内联 diff) @@ -105,7 +105,7 @@ quickDiffProvider?: QuickDiffProvider // provideOriginalResource(uri) → 原 ### 1.7 SCM 菜单贡献点(自定义二级菜单/inline 按钮) -来源:[官方指南](https://code.visualstudio.com/api/extension-guides/scm-provider#source-control-view)。**全部稳定**,且能力足够复刻 IDEA 文件右键菜单: +来源:[官方指南](https://code.visualstudio.com/api/extension-guides/scm-provider#source-control-view)。**全部稳定**,且能力足够支撑文件右键菜单: | 菜单 id | 作用位置 | 可放 `inline`(行内按钮) | |---|---|---| @@ -125,11 +125,11 @@ quickDiffProvider?: QuickDiffProvider // provideOriginalResource(uri) → 原 `SourceControlHistoryItem` / `SourceControlHistoryItemChange` / `scmHistoryProvider` **至今仍是 proposed API**。VS Code 团队成员 lszomoru 2025-05-13 在 issue [#185269](https://github.com/microsoft/vscode/issues/185269) 明确:「计划 finalize 但无时间表」。定义文件:[vscode.proposed.scmHistoryProvider.d.ts](https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts)。 -> 第三方扩展在 Marketplace 发布时使用 proposed API 受限(需特批)。因此 **IDEA 的 Log 提交图不能依赖原生 Source Control Graph,必须自建 TreeView**。 +> 第三方扩展在 Marketplace 发布时使用 proposed API 受限(需特批)。因此 **Log 可视化提交图不能依赖原生 Source Control Graph,必须自建 TreeView**。 ### 1.9 Multi-Diff 编辑器 — proposed -`scmMultiDiffSource`(多文件并排 diff 审查)是 proposed,跟踪 issue [#179000](https://github.com/microsoft/vscode/issues/179000),`vscode.changes` 命令标注「experimental, subject to change」。IDEA 风格的「提交前多文件 diff 预览」短期须自建 Webview 或逐文件 diff。 +`scmMultiDiffSource`(多文件并排 diff 审查)是 proposed,跟踪 issue [#179000](https://github.com/microsoft/vscode/issues/179000),`vscode.changes` 命令标注「experimental, subject to change」。「提交前多文件 diff 预览」短期须自建 Webview 或逐文件 diff。 --- @@ -272,7 +272,7 @@ git 本身**没有 changelist** 概念,只有 stage(index)。IDEA 的 changelist - **物理层**:所有变更仍来自 `Repository.state.workingTreeChanges`(单一事实源)。 - **逻辑层**:Adapter 维护一个**本地 changelist 分配表**(`Map`),持久化到 workspace state(`context.workspaceState`)或 `.idea` 风格本地文件。 -- **提交语义**:「Commit 某 changelist」= `add(该 changelist 的 paths)` + `commit(msg)` + (可选)保留其余未 stage。这正向复刻 IDEA「selective commit」。 +- **提交语义**:「Commit 某 changelist」= `add(该 changelist 的 paths)` + `commit(msg)` + (可选)保留其余未 stage。这即对应「selective commit(选择性提交)」语义。 - **Default changelist**:即 active changelist,未显式分配的变更自动落入。 > 注意:此设计下,changelist 是**纯客户端逻辑分组**,不写 git 元数据(IDEA 的 changelist 也仅存于 `.idea/workspace.xml`,同理)。这与 git 的 stage 是两套正交机制,需在 UI 上明确区分(可提供「stage = 临时索引」「changelist = 持久分组」的认知锚点)。 @@ -309,17 +309,17 @@ git 本身**没有 changelist** 概念,只有 stage(index)。IDEA 的 changelist > GitLens 正是此模式:在 Source Control 活动栏挂自定义视图(来源:[gitkraken/vscode-gitlens issue #213](https://github.com/gitkraken/vscode-gitlens/issues/213) 讨论其视图置于 SCM 面板)。 -### 5.2 IDEA 顶部 Commit/Shelf/Stash 标签页 → VS Code 表达 +### 5.2 顶部 Commit/Shelf/Stash 标签页 → VS Code 表达 两种实现,择一: - **方案 1(推荐):平铺视图节点**。上图 `sofia-git` 容器下并列 Changes/Commit/Log/Shelf/Stash 五个 view,用户点击切换(等价标签页)。 -- **方案 2:单 WebviewView 内自绘 Tabs**。一个 `sofia.main` webview 内用前端框架渲染 IDEA 风格 Tab 栏 + 各面板内容。自由度最高但失去原生 a11y/快捷键集成。 +- **方案 2:单 WebviewView 内自绘 Tabs**。一个 `sofia.main` webview 内用前端框架渲染自绘 Tab 栏 + 各面板内容。自由度最高但失去原生 a11y/快捷键集成。 ### 5.3 Commit Message 编辑器(替代原生 inputBox) - 用 `WebviewView` 渲染一个**多行 Monaco-like 编辑器**(可用 ` -
+ +
- - - +
+ Advanced Options + + + +
- - + +
-
+
- -`; - } - - private static renderSvg(rows: readonly GraphRow[], padded: readonly string[], graphWidth: number, height: number): string { - const parts: string[] = []; - parts.push(``); - for (let r = 0; r < rows.length; r++) { - const y = r * ROW_H; - const graph = padded[r] ?? ''; - for (let c = 0; c < graph.length; c++) { - const ch = graph[c]; - const kind = classifyGraphChar(ch); - if (kind === 'blank') { - continue; - } - const x = c * CHAR_W; - const cx = x + CHAR_W / 2; - const color = LANE_COLORS[Math.floor(c / 2) % LANE_COLORS.length]; - switch (kind) { - case 'node': { - const row = rows[r]; - const dataHash = row?.hash ? ` data-hash="${escapeHtml(row.hash)}"` : ''; - parts.push(``); - break; - } - case 'vert': - parts.push(``); - break; - case 'slash': - parts.push(``); - break; - case 'backslash': - parts.push(``); - break; - case 'underscore': - parts.push(``); - break; - } - } - // 文本(hash + refs + subject) - const row = rows[r]; - if (row?.hash) { - const tx = graphWidth + 8; - const ty = y + ROW_H * 0.68; - const shortHash = escapeHtml(row.hash.slice(0, 7)); - const refSpan = row.decorate ? `${escapeHtml(row.decorate)}` : ''; - const subj = row.subject ? `${escapeHtml(row.subject.slice(0, 80))}` : ''; - parts.push(`${shortHash}${refSpan}${subj}`); - } - } - parts.push(''); - return parts.join('\n'); - } -} - -function escapeHtml(s: string): string { - return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} diff --git a/src/adapter/webview/log-webview.ts b/src/adapter/webview/log-webview.ts new file mode 100644 index 0000000..9b6fcb9 --- /dev/null +++ b/src/adapter/webview/log-webview.ts @@ -0,0 +1,979 @@ +import * as crypto from 'crypto'; +import * as vscode from 'vscode'; +import type { GitRepositoryService } from '../git-repository-service'; +import { parseNameStatus, statusLabel } from '../../engine/log/commit-files'; +import { applyClientFilters, toClientFilter, type LogFilter } from '../../engine/log/log-filter'; +import { DEFAULT_LANE_PALETTE } from '../../engine/log/graph-color'; +import { computeGraphLayout, maxLanes } from '../../engine/log/graph-layout'; +import { getBaseStyles } from './shared-styles'; +import { parseLogLines } from '../../engine/log/log-line'; +import { buildLogArgs, type LogScope } from '../../engine/log/log-query'; +import type { GitHubCiService } from '../ci/github-ci-service'; +import type { + CiMetaVM, + CiStatusVM, + GraphRowVM, + LogCommitFileItem, + LogGraphState, + LogHostToWebviewMessage, + LogWebviewToHostMessage, + RefChip, +} from '../../shared/protocol'; + +const errMsg = (e: unknown): string => (e instanceof Error ? e.message : String(e)); + +/** 单页拉取的 commit 数(滚动触底增量加载下一页)。 */ +const PAGE = 1000; + +/** per-commit 操作 → 既有命令 id(webview 右键菜单 → host 重调用,handler 仅需 hash)。 */ +const COMMIT_MENU: ReadonlyArray<{ readonly label: string; readonly command: string }> = [ + { label: 'Copy Hash', command: 'hyperGit.copyCommitHash' }, + { label: 'Cherry-Pick Commit', command: 'hyperGit.cherryPick' }, + { label: 'Revert Commit', command: 'hyperGit.revertCommit' }, + { label: 'Drop Commit', command: 'hyperGit.dropCommit' }, + { label: 'Fixup Commit', command: 'hyperGit.fixupCommit' }, + { label: 'Create Branch from Commit…', command: 'hyperGit.createBranchFromCommit' }, + { label: 'Create Tag from Commit…', command: 'hyperGit.createTagFromCommit' }, + { label: 'Show Branches Containing Commit', command: 'hyperGit.showContainingBranches' }, + { label: 'Reset Current Branch to Here…', command: 'hyperGit.resetToHere' }, +]; + +/** 引用标签查询的 for-each-ref 格式(full objectname 供精确匹配;与 parseChips 字段顺序对应)。 */ +const CHIP_REF_FORMAT = '%(objectname)%00%(refname)%00%(refname:short)%00%(HEAD)'; + +// ─── 命令参数类型(webview 迁移后,命令仍以 LogNode 为参数类型)────────────────── + +export interface LogCommitNode { + readonly kind: 'commit'; + readonly commit: { readonly hash: string; readonly message: string; readonly parents: readonly string[] }; +} +export interface LogFileNode { + readonly kind: 'file'; + readonly hash: string; +} +export type LogNode = LogCommitNode | LogFileNode; + +/** + * Log 视图控制契约:4 个命令注册器按此接口(而非具体 Provider 类)引用, + * 使 TreeView→Webview 迁移对注册器零行为改动,并便于未来替换实现。 + */ +export interface LogFilterControl extends vscode.Disposable { + setFilter(filter: LogFilter): void; + clearFilter(): void; + getFilter(): LogFilter; + refresh(): void; +} + +/** 一页图数据。 */ +interface GraphPage { + readonly rows: readonly GraphRowVM[]; + readonly maxLanes: number; + readonly hasMore: boolean; +} + +/** + * Log 视图(WebviewView):可视化提交图(DAG)。 + * + * 自计算 lane 布局(engine/log/graph-layout)→ 渲染彩色泳道;host 侧单次 `git log --topo-order` + * 取数 + `for-each-ref` 取引用标签;webview 端虚拟化 SVG 行 + 文本列。保留全部既有交互: + * 7 个过滤命令(经 {@link LogFilterControl})、9 个 per-commit 操作(右键 → host 重调用)、 + * 选中提交查看变更文件、All/Current 范围切换、滚动增量加载、实时刷新。 + */ +export class LogWebviewProvider implements vscode.WebviewViewProvider, LogFilterControl { + public static readonly viewType = 'hyperGit.log'; + + private view?: vscode.WebviewView; + private filter: LogFilter = {}; + private scope: LogScope = 'all'; + private refreshTimer: ReturnType | undefined; + private readonly disposables: vscode.Disposable[] = []; + + constructor(private readonly service: GitRepositoryService, private readonly ciService: GitHubCiService) { + // 兜底实时刷新:git 状态变化(commit/checkout 等)防抖重拉首页。 + let t: ReturnType | undefined; + this.disposables.push( + this.service.onDidChange(() => { + clearTimeout(t); + t = setTimeout(() => this.refresh(), 400); + }), + ); + } + + setFilter(filter: LogFilter): void { + this.filter = filter; + this.refresh(); + } + + clearFilter(): void { + this.filter = {}; + this.refresh(); + } + + getFilter(): LogFilter { + return this.filter; + } + + refresh(): void { + clearTimeout(this.refreshTimer); + this.refreshTimer = setTimeout(() => { + void this.pushState(); + }, 300); + } + + resolveWebviewView(view: vscode.WebviewView): void { + this.view = view; + view.webview.options = { enableScripts: true, localResourceRoots: [] }; + view.webview.html = this.renderHtml(); + const msgSub = view.webview.onDidReceiveMessage((msg) => this.onMessage(msg as LogWebviewToHostMessage)); + view.onDidDispose(() => { + msgSub.dispose(); + this.view = undefined; + }); + } + + dispose(): void { + clearTimeout(this.refreshTimer); + this.disposables.forEach((d) => d.dispose()); + } + + // ─── Host ↔ Webview 消息 ──────────────────────────────────────────────────── + + private onMessage(msg: LogWebviewToHostMessage): void { + switch (msg.type) { + case 'log/requestState': + void this.pushState(); + break; + case 'log/retry': + void this.pushState(); + break; + case 'log/loadMore': + void this.loadMore(msg.payload.cursor); + break; + case 'log/selectCommit': + void this.sendCommitFiles(msg.payload.hash); + break; + case 'log/openFile': + void vscode.commands.executeCommand( + 'hyperGit.openCommitFileDiff', + msg.payload.hash, + msg.payload.path, + msg.payload.hasParent, + ); + break; + case 'log/setScope': + this.scope = msg.payload.scope; + void this.pushState(); + break; + case 'log/commitAction': + if (msg.payload.op === 'menu') { + void this.handleCommitMenu(msg.payload.hash); + } + break; + case 'log/requestCi': + void this.handleRequestCi(msg.payload.hashes); + break; + case 'log/openExternal': + void this.ciService.openExternal(msg.payload.url); + break; + case 'log/ciSignIn': + void this.handleCiSignIn(); + break; + } + } + + private post(message: LogHostToWebviewMessage): void { + this.view?.webview.postMessage(message); + } + + // ─── 数据拉取 ─────────────────────────────────────────────────────────────── + + private async pushState(): Promise { + if (!this.view) { + return; + } + this.post({ type: 'log/busy', payload: { busy: true } }); + const page = await this.fetchPage(0); + if (!page) { + this.post({ type: 'log/busy', payload: { busy: false } }); + return; + } + const state: LogGraphState = { + rows: page.rows, + maxLanes: page.maxLanes, + hasMore: page.hasMore, + scope: this.scope, + repoRoot: this.service.repoRoot ?? '', + }; + this.post({ type: 'log/graphData', payload: state }); + // CI 元信息异步随附(不阻塞建图):远程为 GitHub 才启用,未授权则提示登录。 + void this.pushCiMeta(); + } + + /** 推送 CI 能力/授权态(status() 廉价:复用缓存会话)。失败静默回退为不可用。 */ + private async pushCiMeta(): Promise { + if (!this.view) { + return; + } + let meta: CiMetaVM; + try { + const s = await this.ciService.status(); + meta = { available: s.available, needsSignIn: s.needsAuth, error: s.error }; + } catch { + meta = { available: false, needsSignIn: false }; + } + if (this.view) { + this.post({ type: 'log/ciMeta', payload: meta }); + } + } + + /** 懒加载可见行 CI(webview 滚动按需请求),取数后守卫 view 仍存在再回填。 */ + private async handleRequestCi(hashes: readonly string[]): Promise { + if (hashes.length === 0) { + return; + } + const map = await this.ciService.getStatuses(hashes); + if (!this.view || map.size === 0) { + return; + } + const rec: Record = {}; + for (const [hash, vm] of map) { + rec[hash] = vm; + } + this.post({ type: 'log/ciData', payload: { map: rec } }); + } + + /** 用户点击「登录 GitHub 查看 CI」:走原生授权,完成后刷新 CI 元信息。 */ + private async handleCiSignIn(): Promise { + await this.ciService.signIn(); + await this.pushCiMeta(); + } + + private async loadMore(cursor: number): Promise { + const page = await this.fetchPage(cursor); + if (!page || page.rows.length === 0) { + this.post({ type: 'log/busy', payload: { busy: false } }); + return; + } + this.post({ + type: 'log/appendData', + payload: { rows: page.rows, maxLanes: page.maxLanes, hasMore: page.hasMore }, + }); + } + + private async fetchPage(skip: number): Promise { + const repo = this.service.repo; + if (!repo) { + return undefined; + } + try { + const out = await this.service.execGit(['log', ...buildLogArgs(this.filter, this.scope, { maxCount: PAGE, skip })]); + const raws = parseLogLines(out); + if (raws.length === 0) { + return { rows: [], maxLanes: 0, hasMore: false }; + } + // 客户端过滤(mergeMode / date / regex / checkpoint),message 近似取 subject。 + // keepCheckpoint 由 scope 驱动:仅 Checkpointer 视图保留 checkpoint 自动提交,All/Current 剔除。 + const filterable = raws.map((r) => ({ + message: r.subject, + authorDate: r.authorDate ? new Date(r.authorDate) : undefined, + parents: r.parents, + hash: r.hash, + raw: r, + })); + const survived = applyClientFilters(filterable, { ...toClientFilter(this.filter), keepCheckpoint: this.scope === 'checkpointer' }); + const layout = computeGraphLayout(survived.map((s) => ({ hash: s.hash, parents: s.parents }))); + const hashSet = new Set(survived.map((s) => s.hash)); + const chips = await this.fetchChips(hashSet); + const rows: GraphRowVM[] = survived.map((s, i) => ({ + hash: s.raw.hash, + shortHash: s.raw.hash.slice(0, 7), + parents: s.raw.parents, + isMerge: s.raw.parents.length > 1, + subject: s.raw.subject, + authorName: s.raw.authorName, + authorDate: s.raw.authorDate, + chips: chips.get(s.raw.hash) ?? [], + layout: layout[i], + })); + return { rows, maxLanes: maxLanes(layout), hasMore: raws.length === PAGE }; + } catch (e) { + // 失败时以 webview 内错误态呈现(带 Retry),而非模态弹窗——用户可即时重试。 + this.post({ type: 'log/error', payload: { message: errMsg(e) } }); + return undefined; + } + } + + /** 取引用标签:for-each-ref(full hash 精确匹配)+ repo.state.HEAD 标注当前分支 / detached HEAD。 */ + private async fetchChips(hashes: Set): Promise> { + const map = new Map(); + const headCommit = this.service.repo?.state.HEAD?.commit; + const detached = headCommit && !this.service.repo?.state.HEAD?.name; + try { + const out = await this.service.execGit(['for-each-ref', `--format=${CHIP_REF_FORMAT}`, 'refs/heads', 'refs/remotes', 'refs/tags']); + for (const line of out.split('\n')) { + if (line.length === 0) { + continue; + } + const [hash, refname, shortName, headMark] = line.split('\x00'); + if (!hash || !refname || !hashes.has(hash)) { + continue; + } + const kind: RefChip['kind'] = refname.startsWith('refs/tags/') + ? 'tag' + : refname.startsWith('refs/remotes/') + ? 'remoteBranch' + : 'localBranch'; + const isHeadTarget = headMark === '*' || hash === headCommit; + this.pushChip(map, hash, { name: shortName, kind, isHeadTarget }); + } + } catch { + // 引用标签为增强信息,失败不影响图主体。 + } + if (detached && headCommit && hashes.has(headCommit)) { + this.pushChip(map, headCommit, { name: 'HEAD', kind: 'head' }); + } + // 排序:head → local → remote → tag(稳定)。 + const order: Record = { head: 0, localBranch: 1, remoteBranch: 2, tag: 3 }; + for (const list of map.values()) { + list.sort((a, b) => order[a.kind] - order[b.kind]); + } + return map; + } + + private pushChip(map: Map, hash: string, chip: RefChip): void { + const list = map.get(hash); + if (list) { + list.push(chip); + } else { + map.set(hash, [chip]); + } + } + + private async sendCommitFiles(hash: string): Promise { + const repo = this.service.repo; + if (!repo) { + return; + } + try { + // 复用 Log 既有逻辑:diff-tree 取变更文件。 + const out = await this.service.execGit(['diff-tree', '--no-commit-id', '--name-status', '-r', '--root', hash]); + const changes = parseNameStatus(out); + const files: LogCommitFileItem[] = changes.map((c) => ({ + status: c.status, + statusLabel: statusLabel(c.status), + path: c.oldPath ? `${c.oldPath} → ${c.path}` : c.path, + oldPath: c.oldPath, + themeColor: fileIconColor(c.status), + })); + this.post({ type: 'log/commitFiles', payload: { hash, files } }); + } catch { + this.post({ type: 'log/commitFiles', payload: { hash, files: [] } }); + } + } + + private async handleCommitMenu(hash: string): Promise { + const nodeLike: LogCommitNode = { kind: 'commit', commit: { hash, message: '', parents: [] } }; + const items = COMMIT_MENU.map((m) => ({ label: m.label, command: m.command })); + const pick = await vscode.window.showQuickPick(items, { placeHolder: `Commit ${hash.slice(0, 7)}` }); + if (!pick) { + return; + } + await vscode.commands.executeCommand(pick.command, nodeLike); + } + + // ─── HTML 渲染 ────────────────────────────────────────────────────────────── + + private renderHtml(): string { + const nonce = crypto.randomBytes(16).toString('base64'); + const laneFallback = JSON.stringify(DEFAULT_LANE_PALETTE); + const csp = ['default-src \'none\'', 'style-src \'unsafe-inline\'', `script-src 'nonce-${nonce}'`].join('; '); + return ` + + + + + + + +
+ + + + + + + +
+
+
+
No Commits
No commits match the current scope or filter.
+ +
Loading…
+
+
+ + + +`; + } +} + +/** 变更文件状态 → gitDecoration 主题色 id(与原 log-tree 的 fileIconColor 语义一致)。 */ +function fileIconColor(status: string): string { + if (status.startsWith('A')) { + return 'gitDecoration.addedResourceForeground'; + } + if (status.startsWith('D')) { + return 'gitDecoration.deletedResourceForeground'; + } + if (status.startsWith('R') || status.startsWith('C')) { + return 'gitDecoration.renamedResourceForeground'; + } + return 'gitDecoration.modifiedResourceForeground'; +} diff --git a/src/adapter/webview/merge-editor.ts b/src/adapter/webview/merge-editor.ts index 5c3d0bd..9eff82c 100644 --- a/src/adapter/webview/merge-editor.ts +++ b/src/adapter/webview/merge-editor.ts @@ -5,11 +5,12 @@ import * as vscode from 'vscode'; import type { GitRepositoryService } from '../git-repository-service'; import { diff3, type MergeHunk } from '../../engine/merge/diff3'; import { parseConflictState } from '../../engine/git-state/conflict-detector'; +import { getBaseStyles } from './shared-styles'; const errMsg = (e: unknown): string => (e instanceof Error ? e.message : String(e)); /** - * 自绘 3-way Merge Editor(WebviewPanel)—— 复刻 IDEA 冲突解决编辑器。 + * 自绘 3-way Merge Editor(WebviewPanel)—— 自绘 3-way 冲突解决编辑器。 * * 数据:经 `git show :1/:2/:3:` 取 base/ours/theirs,diff3 产出 hunks。 * UI:stable 段只读展示;conflict 段三栏(OURS | RESULT 可编辑 | THEIRS),Accept 按钮填 RESULT。 @@ -19,7 +20,7 @@ export class MergeEditorWebview { static async openForFile(service: GitRepositoryService, filePath: string): Promise { const repo = service.repo; if (!repo) { - void vscode.window.showWarningMessage('未找到 Git 仓库'); + void vscode.window.showWarningMessage('No Git repository found'); return; } let base: string; @@ -33,7 +34,7 @@ export class MergeEditorWebview { service.execGit(['show', `:3:${filePath}`]), ]); } catch (e) { - void vscode.window.showErrorMessage(`读取冲突阶段失败(文件可能无冲突):${errMsg(e)}`); + void vscode.window.showErrorMessage(`Failed to read conflict stages (file may have no conflicts): ${errMsg(e)}`); return; } const hunks = diff3(splitLines(base), splitLines(ours), splitLines(theirs)); @@ -42,7 +43,7 @@ export class MergeEditorWebview { // 无冲突:直接取合并结果写回 + add const merged = hunks.flatMap((h) => (h.kind === 'stable' ? h.content : [])); await MergeEditorWebview.saveResult(service, filePath, merged.join('\n') + '\n'); - void vscode.window.showInformationMessage(`「${filePath}」无冲突,已自动合并并标记已解决`); + void vscode.window.showInformationMessage(`"${filePath}" has no conflicts; auto-merged and marked resolved`); return; } @@ -70,11 +71,11 @@ export class MergeEditorWebview { } if (/^<<<<<<< /m.test(content) || /^======= *$/m.test(content) || /^>>>>>>> /m.test(content)) { const ok = await vscode.window.showWarningMessage( - '结果仍含冲突标记(<<<<<<< / ======= / >>>>>>>),强制保存将把带标记的内容标记为已解决。继续?', + 'The result still contains conflict markers (<<<<<<< / ======= / >>>>>>>). Force-saving will mark the file as resolved with the markers still in it. Continue?', { modal: true }, - '强制保存', + 'Force Save', ); - if (ok !== '强制保存') { + if (ok !== 'Force Save') { return; } } @@ -82,38 +83,47 @@ export class MergeEditorWebview { try { await fs.promises.writeFile(abs, content, 'utf8'); await service.execGit(['add', '--', filePath]); - void vscode.window.showInformationMessage(`「${filePath}」已保存并标记为已解决`); + void vscode.window.showInformationMessage(`"${filePath}" saved and marked resolved`); } catch (e) { - void vscode.window.showErrorMessage(`保存失败:${errMsg(e)}`); + void vscode.window.showErrorMessage(`Failed to save: ${errMsg(e)}`); } } private static renderHtml(filePath: string, hunks: readonly MergeHunk[], conflicts: number): string { const nonce = crypto.randomBytes(16).toString('base64'); - const dataJson = escapeHtml(JSON.stringify(hunks)); + // JSON 注入 破出。 + // 不可用 escapeHtml——其产出 " 在 diff --git a/src/adapter/webview/shared-styles.ts b/src/adapter/webview/shared-styles.ts new file mode 100644 index 0000000..aec77dc --- /dev/null +++ b/src/adapter/webview/shared-styles.ts @@ -0,0 +1,85 @@ +/** + * Webview 共享设计 Token 与基础组件类(单一事实源)。 + * + * 4 个自绘 Webview(Commit / Log / Merge / Rebase)共享同一套: + * - 设计 Token(spacing/radius,对齐 VS Code 语义); + * - 基础组件类(`.hg-btn` / `.hg-btn--secondary` / `.hg-btn--sm` / `.hg-input` / `.hg-row`) + * 统一交互态(hover / active / focus-visible / disabled),消除各 Webview 各自硬编码导致的 + * 「按钮无 hover」「`:last-child` 脆弱选择器」「无 focus ring」等熵增。 + * + * 设计原则:纯字符串、零 vscode 依赖(可单测);每个 Webview 在 `