dsh-github 实现分析

August 18, 2026 · View on GitHub

1. 目标与约束

dsh-github 的目标不是重新实现一个 GitHub 客户端,而是在 DeepSeek Harness 中提供接近 VS Code Source Control 的本地 Git 工作流,并把 GitHub 相关动作收敛为浏览器交接。

插件只使用当前 Workspace 中的本地 Git 仓库、.git/config、Git remote、SSH agent、SSH key 和 HTTPS credential helper。插件不使用 gh CLI、GitHub REST/GraphQL API、Octokit、Token 或 OAuth,也不暴露任意 shell 执行能力。

所有写操作都必须由用户在面板中明确触发。插件不会自动 stash、discard、merge、rebase、force push 或 prune remote refs。

2. 参考实现与可迁移经验

VS Code Git

VS Code Git 的核心经验是把 Repository 状态、当前操作状态和 Source Control 资源分开管理:index、working tree、untracked files 和 conflicts 是不同的展示来源,同一个文件可以同时出现在 staged 和 working-tree 分组中。

本插件采用相同的状态分离方式,直接保留 Git porcelain status 的 index 与 worktree 两列,而不是将文件压缩成一个 modified 标记。这样可以正确决定每个文件当前应该显示 StageUnstage、冲突解决后的 Stage,还是 diff 查看入口。

VS Code 的另一个重要经验是操作状态必须可见。面板维护一个当前 operation,运行期间禁用相互冲突的按钮,操作结束后重新读取仓库状态;Commit 成功但后续 Push 失败时,不能回滚或隐藏已经创建的本地 commit。

GitLens

GitLens 将仓库导航、分支导航和浏览器链接作为相互独立的功能。插件沿用功能划分的思路,但把改动、提交、分支、同步和浏览器链接收敛进会话视图环里的 Source Control tab(与对话/轨迹并列,主结构内左右分栏,绑定当前会话工作区):改动/提交区 + 懒加载的 COMMITS、BRANCHES、REMOTES、TAGS、STASHES 区段。

GitHub 页面是本地 Git 状态的浏览器入口,而不是第二套远程仓库数据源。仓库、分支、commit、文件、compare 和已经 Fetch 的 PR ref 都由本地 remote 与 ref 推导,打开后交由 GitHub 网页完成 PR 创建、评论和其他 GitHub 专属操作。

isomorphic-git

isomorphic-git 对 HEAD、index 和 worktree 的分离说明了为什么不能只读取当前文件内容:Source Control 需要知道文件在 index 中的状态、工作区中的状态以及是否存在冲突或 rename 来源路径。

本插件没有引入 isomorphic-git 运行时依赖,而是使用系统 Git 的稳定输出格式读取这些状态,减少依赖和自维护代码。

3. 当前实现结构

src/contract.ts       Typert invocation、输入约束和返回值 wire schema
src/types.ts          Host 与 Client 共用的 Git 状态类型
src/runtime.ts        本地 Git 命令、状态解析、操作和 GitHub URL 推导
src/client/remote.ts  Client 到 Host Remote 的适配
src/client/view.tsx    会话视图环里的 Source Control tab:注册 conversation.view 槽位、绑定当前会话 cwd(byId[sessionId].cwd)、随 tab 挂载/卸载
src/client/panel.tsx   tab 主体内的 SCM 面板:左分组/提交区、右 diff 查看器、懒加载区段与操作状态
src/client/diff.tsx   side-by-side / inline diff 查看器
src/client/confirm.tsx
                      内联确认弹窗(破坏性操作)
src/client/styles.ts  SCM 面板与 diff 的样式
src/client/locales.ts zh/en 词条(DshGithubKey)

Host 侧只有一个 Git 命令入口:固定调用 execFile('git', args),所有路径通过仓库根目录解析并校验,所有写入操作完成后重新读取状态。Client 侧只通过 Typert Remote 调用 Host,不直接访问文件系统或凭据。

视觉层(src/client/styles.ts)全部走 --dsw-* 主题变量(含 --ds-font-family-code 等),仅用中性浅色回退值,无 VS Code 深色硬编码;详见 docs/UI_DESIGN.md

4. 状态模型

插件把一个文件的状态表示为两个 Git 状态列:

来源含义面板动作
index已暂存版本相对 HEAD 的状态查看 staged diff、Unstage
worktree工作区版本相对 index 的状态查看 working diff、Stage
??未跟踪文件查看受限内容、Stage
U未解决冲突查看冲突状态、解决后 Stage
R / Crename / copy 及其来源路径显示来源路径并保留文件链接

仓库级状态同时包含当前分支、upstream、ahead/behind、fetch remote、push remote、HEAD SHA 和可用浏览器链接。没有明确 remote 配置时,插件返回空链接或明确错误,不从多个 remote 中猜测一个目标。

本次新增 mergeState: 'merge' | 'rebase' | null:Host 在 getStatus 里通过 git rev-parse --git-path 定位 git dir,用 fs 检查 MERGE_HEAD(merge)与 rebase-merge/rebase-apply(rebase)是否存在,据此驱动面板上的 continue/abort 动作。

区段数据按需加载:COMMITS、BRANCHES、REMOTES、TAGS、STASHES 首次展开时才请求对应列表,之后随每次 status 刷新一并刷新(仅已展开的区段)。历史搜索、commit 详情、stash diff 等详情级查询单独按需发起。

Git 命令输出在 Host 内存维护一个环形 buffer(默认 50 条),记录每次 git 命令的 {command, args(脱敏), ok, output(裁剪到 4KB 并脱敏), at},由 getOutput() 返回并在面板底部 GIT OUTPUT 区渲染。

对于没有首个 commit 的 unborn repository,插件使用 Git 当前可用的 index/worktree 状态,不假设 HEAD 已经存在。

5. 操作设计

所有命令使用 execFile('git', args)(argv-only,禁止任何 shell 拼接),环境统一加 GIT_EDITOR=trueGIT_TERMINAL_PROMPT=0。写操作执行前校验路径/分支名(复用 validateFilePath / check-ref-format --branch),执行后重读真实状态。破坏性操作(下表标 ⚠️)在 UI 里走内联确认弹窗(自建 React modal,不用 window.confirm)。

改动与提交

操作命令说明
Stage / Unstagegit add -- / 有 HEAD 时 git restore --staged --,unborn 用 git rm --cached现有实现不变
Stage All / Unstage Allgit add --all / 恢复整个 index
Discard file ⚠️未跟踪:确认 ??fs.rm;已跟踪:git restore -- <path>返回新 status
Discard All ⚠️git restore -- .includeUntracked=true 追加 git clean -fd弹窗明示影响范围
Commitgit commit -m <msg>message 在 wire 层校验非空且有上限
Commit (Amend)git commit --amend -m <msg>UI 勾选 Amend
Undo Last Commit ⚠️git reset --soft HEAD~1保留改动在 index
Resolve conflict ⚠️git checkout --ours/--theirs -- <path>both:解析 <<<<<<< / ======= / >>>>>>> 标记、保留双方内容写回

同步

操作命令说明
Fetchgit fetch <remote>all=true--allprune=true 追加 --prune
Pullgit pull --no-edit遵循本地 pull.rebase / ff 配置(替代旧 --ff-only
Push无 upstream 时 push -u <remote> <branch>仍然禁止 force
Syncpull + push 序列;无 upstream → Publish不自动处理冲突

分支

操作命令
Checkout / Creategit switch / git switch -c
Rename ⚠️git branch -m <old> <new>(新名过 check-ref-format)
Delete ⚠️git branch -d <name>(不提供 -D)
Merge into current ⚠️git merge --no-edit <branch>
Rebase onto ⚠️git rebase <branch>
Continue / Abort Mergegit merge --continue / git merge --abort
Continue / Abort Rebasegit rebase --continue / git rebase --abort
Checkout Commit ⚠️git checkout <sha>(detach)
Create Branch Fromgit switch -c <branch> <sha>

Remotes

操作命令
列表git remote -v 解析 [{name, fetchUrl, pushUrl}],URL 经脱敏
Addgit remote add <name> <url>(name 过 remote 规则校验)
Remove ⚠️git remote remove <name>

Tags

操作命令
列表git tag --list --sort=-creatordate --format=...
Creategit tag -a <name> -m <message>(message 空 → 轻量;可选 atRef)
Delete ⚠️git tag -d <name>
Push Tagsgit push <pushRemote> --tags

Stashes

操作命令
列表git stash list --format=...
Creategit stash push [-u] [--keep-index] [-m <message>]
Apply / Apply & Dropgit stash apply <ref> / git stash pop <ref>
Drop ⚠️git stash drop <ref>
Diffgit stash show -p --unified=3 <ref>(trimOutput 截断)

历史与输出

操作命令
列表git log --all --format=<format> --grep=<query> -i --author=<query> -i --max-count=<max>
详情git show --name-status --format=<format> -M <sha>
文件 diffgit show --unified=3 <sha> -- <path>(trimOutput 截断)
输出getOutput() 返回环形 buffer(见 §4)

commit message 在 wire 层校验为非空且有长度上限。所有写操作(stage/unstage/discard/commit/fetch/pull/push/sync/branch/merge/rebase/tag/remote/stash)都有独立的运行状态,并在成功或失败后刷新状态。Push 遵循当前分支的 branch.<name>.pushRemotebranch.<name>.remoteremote.pushDefault 配置;没有 upstream 时,首次 Push 使用 git push -u <remote> <branch>,已有 upstream 时使用普通 git push。切换远程跟踪分支前要求该 ref 已经由本地 Git Fetch 得到;插件不会为了猜测分支而隐式 Fetch,也不会在有未提交改动时自动 stash。

6. GitHub 链接推导

插件只识别 GitHub remote URL,包括常见的 SSH、HTTPS 和 Git 协议形式,并去除凭据和 .git 后缀。非 GitHub remote 仍然可以用于本地 Git 操作,但不会生成 GitHub 页面链接。

支持的浏览器入口包括:

  • repository:当前 remote 对应的仓库首页;
  • branch:当前分支或已知远程分支页面;
  • commit:当前 HEAD;
  • file:当前 commit 中的文件;
  • compare:当前分支相对默认分支或 upstream 的比较页面;
  • pull request:本地已经 Fetch 的 pull/<number>/headpull/<number>/mergepr/<number>/headpr/<number>/merge ref。

Fork 场景使用 fetch remote 作为 base repository,push remote 作为 head repository,生成 GitHub compare URL。创建 PR、评论、审查和合并仍由用户在浏览器中完成,避免本地插件复制 GitHub 权限和远程 API 逻辑。

7. 与 VS Code Source Control 的对应关系

VS Code 习惯dsh-github 对应功能
Source Control view会话视图环里的 Source Control tab(主结构内左右分栏,绑定当前会话工作区)
Staged Changes / Changes / Merge ChangesSTAGED CHANGES / CHANGES / MERGE CHANGES 分组(带数量 badge)
Stage / Unstage resource文件级 Stage / Unstage
Discard / Discard All Changes带内联确认的 Discard / Discard All
Commit message box + Amendcommit 输入框 + Amend 复选框
Commit / Commit & Push / Commit & SyncCommit 下拉(Commit / Commit & Push / Commit & Sync)
Undo Last CommitUndo Last Commit(reset --soft HEAD~1
Accept Current / Incoming / Both Changes冲突文件的 Accept Current / Incoming / Both
Sync Changes + ↑↓ 计数Sync/Publish 按钮(pull --no-edit + push)
Commits history / commit detailsCOMMITS 区段 + 详情与逐文件 diff
Branch picker / branchesBRANCHES 区段(checkout/rename/delete/merge/rebase)
Remotes viewREMOTES 区段(fetch/prune/add/remove)
Tags viewTAGS 区段(create/push/delete)
Stashes viewSTASHES 区段(apply/apply&drop/drop/diff)
Merge / Rebase continue & abortmergeState 驱动的 continue/abort 动作
Git output channelGIT OUTPUT 可折叠区(环形 buffer)
Open in Remote / repository browserGitHub 浏览器链接
auto refresh打开即刷新 + 每 3s 轮询 + focus/visibilitychange 立即刷新
operation progress面板 operation 状态和 live status

8. 有意不实现的功能

以下功能不属于本插件的本地 Git + 浏览器交接范围:

  • 通过 GitHub API 查询 issue、PR 列表、评论或 checks;
  • 通过 Token、OAuth 或 GitHub App 管理远程身份;
  • 在 Harness 内嵌 PR 创建、评论、审查或合并界面;
  • force push、reset --hard、interactive rebase、自动 commit/push;
  • blame / 行内 gutter 指示(面板没有编辑器,无法等价);
  • 多根 workspace(DSH 面板一次绑定一个 workspace);
  • 在本地未 Fetch 时远程猜测 PR 或分支;
  • 复制 gh 的认证、配置和命令语义。

显式的 discard、stash、merge、rebase(含 continue/abort)与 reset --soft(Undo Last Commit)已纳入范围,但都要求用户明确触发并走内联确认,绝不自动执行。

这些限制是为了保持插件的权限范围与 VS Code Git 本地工作流一致,并避免在 Harness 中维护第二套 GitHub 客户端。

9. 验证范围

当前实现通过以下验证:

  • TypeScript typecheck;
  • ESLint;
  • Vitest 单元测试:每个 Host 新方法都有用例(临时裸仓库 + 本地 bare remote 场景),破坏性操作的错误路径有断言;
  • production build;
  • git diff --check
  • 临时 Git 仓库中的 status、diff、stage/unstage、discard、commit/amend/undo-last-commit、conflict 三选一、fetch/pull/push/sync、branch rename/delete、merge/rebase continue-abort、remotes、tags、stashes、log/showCommit/showCommitDiff、unborn repository、PR ref 和 fork compare 场景;
  • 面板用例:分组折叠/计数、stage/unstage/discard 交互、Amend 提交、冲突 Accept 三选一、sync/publish 按钮状态、懒加载区段渲染、3s 轮询启动/停止、Esc/焦点圈闭;
  • 真实 Web Harness 重启后的 Source Control 视图 tab(与对话/轨迹并列、挂载/卸载与轮询启停、无 cwd 提示)、diff 查看器、各区段与 GIT OUTPUT、操作面板和浏览器 console/page error 检查。

10. 后续维护原则

继续扩展时,优先复用本地 Git 已有的 remote、ref 和 credential 配置;不要为了增加 GitHub 功能而引入远程 API、Token 或新的 Git 客户端抽象。

rc.6 的 Workspace 菜单兼容适配(legacy-menu.tsx)已于 v0.2 删除,入口改为注册进会话视图环(conversation.view)的 Source Control tab;壳层 details 列由官方工具详情占用、不可占用,因此 tab 是官方集成方式。除非出现明确的用户需求和可验证的本地 Git 使用场景,否则不增加 GitHub API、PR 管理或复杂的自动同步功能。