位于 composer 工具栏的分支标记:切换、新建、刷新工作区分支;提交时可勾选部分改动文件,提交信息由当前 Session 的模型根据 diff 生成;并可推送当前分支。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:hunan36/dsh-git-flow
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
English | 中文
DeepSeek Harness 工作区 Git 插件:在输入框工具行放一个分支胶囊,点开能看分支、切分支、右键检出/合并/变基/拿来/推送/重命名/删除分支、新建分支,并在面板里勾选文件提交 —— 提交信息由当前会话的模型根据改动自动生成。
输入框工具行: [⑂ feature/login 3] ← 点击(旁边有 Feature 类型徽标)
├ 搜索分支
├ 本地分支 · 2 ✓ feature/login
│ main
│ ↳ 右键:检出 · 合并 · 变基 · 拿来
│ 推送 · 重命名 · 删除 · 复制分支名
├ 新建分支…
├ 提交… (有改动时可用)
├ 刷新状态
├ 启动 Git Flow 分支…
└ 完成 Git Flow 分支… (在 flow 分支上且工作区干净时可用)
安装
两种方式,任选其一。
方式一:插件页(GUI)
界面左侧「插件」→ 右上「添加插件」→ 「包名或地址」填:
@hunan36/dsh-git-flow
点「安装」,装好后在「已安装」里启用(立即启用)。插件页会在它自己所在的 profile(通常是 web)里执行 pnpm add 并把本包写进 dsh.profile.bundles,两条都不用你手动做。
也可以填 GitHub 地址 https://github.com/hunan36/dsh-git-flow,但 pnpm 会把依赖锁到当时的那个 commit,之后不会自动追新。走 npm 包名才能比较版本、更新到最新。
装完重启一次 dsh web(宿主半的改动不会热更新;前端 bundle 会在重启后重新拉取)。
方式二:命令行(推荐用独立 profile)
# 1. 用一个独立 profile,别动现有 web profile
dsh --profile gitflow-web --from-default-profile web
# 2. 装本插件 —— 给 npm 包名,或给 GitHub 地址 / 本地克隆目录的绝对路径
dsh plugin --profile gitflow-web add @hunan36/dsh-git-flow
# dsh plugin --profile gitflow-web add https://github.com/hunan36/dsh-git-flow
# dsh plugin --profile gitflow-web add "$PWD"
# 3. 插件页「立即启用」,或直接在 profile 的 package.json 里加 bundle
# dsh.profile.bundles += "@hunan36/dsh-git-flow"
# 由于本包声明了 dsh.bundle.patch,启用后它的 cordis.patch.yml 会自动挂载 GitFlow 服务
# 4. 起服务
dsh --profile gitflow-web
headless profile 也能装,但没有 webServer,/api/dsh-git-flow/* 不会注册,浏览器半也不存在 —— 不会报错。
更新
DSH 不为第三方插件提供自动更新,需要手动执行一次:
pnpm --dir ~/.dsh/profiles/web add @hunan36/dsh-git-flow@latest
然后重启 dsh web。宿主半(服务与 API 路由)的改动必须重启才生效;浏览器半在重启后会重新拉取。
查当前版本与最新版本:
npm view @hunan36/dsh-git-flow version
装 GitHub 地址的用户不会被自动追新(pnpm 已锁到具体 commit),更新时要显式重装:
pnpm --dir ~/.dsh/profiles/web add github:hunan36/dsh-git-flow#main
机器上没有 pnpm
dsh 的插件管理(插件页的「安装」和 dsh plugin …)底层调用 pnpm,机器上没有时会直接失败:
dsh: pnpm was not found; install pnpm and make it available on PATH.
装一个就好(Node 自带 npm):
npm install -g pnpm # 装最新的 10.x
# 或者:corepack enable && corepack prepare pnpm@10 --activate
注意别让 Corepack 用它的默认版本 —— corepack enable 之后直接跑拿到的是 pnpm 8.15.7,会踩下面那个 ERR_PNPM_ADDING_TO_ROOT。
不想装 pnpm 也有两条路,插件本身不依赖它(包内 dependencies 为空):
- 用 Node 自带的 npm:
cd ~/.dsh/profiles/<profile> && npm install https://github.com/hunan36/dsh-git-flow - 直接拷贝:把整个
dsh-git-flow目录(含lib/)拷进<profile>/node_modules/,适用于离线或纯手工环境
这两种方式都必须再补一步:把 @hunan36/dsh-git-flow 加进该 profile package.json 的 dsh.profile.bundles,然后重启 dsh web —— 插件页和 dsh plugin add 会自动做这一步,手工装则要自己写,否则包在 node_modules 里也不会被加载。
让插件页改用 npm(机器上只有 npm)
dsh 的插件页默认执行 pnpm add <地址>,但插件管理器的命令是可配置的 —— 在 profile 的 cordis.patch.yml 里改成 npm,插件页就会用 npm 安装:
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- id: plugin-manager
config:
pnpmCommand: npm
之后按方式一正常操作(填地址 → 安装 → 立即启用)即可。命令行的 dsh plugin … add 不吃这个配置,仍然需要 pnpm。
三点提醒:
- 这属于 dsh 的内部配置项,随版本可能变动;机器上能装 pnpm 时,优先装 pnpm。
- dsh 传给它的参数是
add <spec>/remove <name>/view <spec> … --json,npm 都支持;但 pnpm 专有语义(-w、onlyBuiltDependencies、lockfile)在 npm 下不存在,同一个 profile 别混用两种管理器。 - 界面与日志里那条命令仍会显示成
pnpm add …(dsh 的日志标题是写死的),判断实际用的是谁,看 profile 目录里留的是package-lock.json还是pnpm-lock.yaml。
安装报错:ERR_PNPM_ADDING_TO_ROOT
dsh 的 profile 目录本身就是一个 pnpm workspace(pnpm-workspace.yaml 里 packages: [.]),而 pnpm 8 会把 pnpm add 当成「往 workspace 根加依赖」直接拒绝。profile 的 package.json 没有 packageManager 字段时,Node 自带的 Corepack 会自动补一个(常见是 pnpm@8.15.7),于是安装卡在这个检查上 —— 它发生在 pnpm 解析本插件之前,与本插件无关。
四种走法任选其一:
插件页(GUI)安装:那里的命令是
pnpm add <地址>,加不了-w—— 先做第 2 步写.npmrc,再点对话框里的「重试」即可(pnpm 8 下实测通过)。让这个 profile 接受根依赖,之后照常按文档安装:
echo 'ignore-workspace-root-check=true' >> ~/.dsh/profiles/gitflow-web/.npmrc命令行安装时透传
-w(dsh 会把多余参数原样转发给 pnpm):dsh plugin --profile gitflow-web add -w https://github.com/hunan36/dsh-git-flow.git把这个 profile 切到 pnpm 10(本插件实测版本):在 profile 目录执行
corepack use pnpm@10,或把 Corepack 补进package.json的packageManager改成pnpm@10.22.0。
注意第一次失败后,Corepack 已经把 packageManager: pnpm@8.15.7+sha512… 写进了该 profile 的 package.json,之后这个 profile 的所有插件操作都会用 pnpm 8 —— 用第 4 步把它改掉最省事。
前置版本:Node ^22.19.0 || >=24.0.0、pnpm 10;本仓库已用 packageManager: pnpm@10.22.0 声明。
配置
cordis.patch.yml 里可改:
| 键 | 默认 | 说明 |
|---|---|---|
timeoutMs |
15000 | 只读/本地 git 命令超时 |
pushTimeoutMs |
120000 | git push 超时(等远端) |
messageMaxDiffBytes |
65536 | 喂给模型的 diff 字节上限,超出截断 |
messageLanguage |
en |
提交信息默认语言,zh / en |
productionBranch |
main |
生产分支名;老仓库用 master 时改这里 |
developBranch |
develop |
集成分支名 |
featurePrefix |
feature/ |
功能分支前缀 |
releasePrefix |
release/ |
发布分支前缀 |
hotfixPrefix |
hotfix/ |
热修复分支前缀 |
tagPrefix |
v |
发布/热修复打完合并后打的标签前缀 |
提交面板的「生成提交信息」旁有 English | 中文 切换,它覆盖本次调用的 messageLanguage,并把选择记在 localStorage——
不选就一直默认英文。
合并分支
在分支行上点右键(或在行聚焦时按键盘菜单键 / Shift+F10)会弹出全套分支操作。本节只讲检出与合并,变基、拿来、推送、
重命名、删除、复制见 分支操作。
- 检出 —— 与左键点击相同,脏工作区时同样先弹「强制切换」确认。
- 合并到当前分支 —— 对当前分支执行
git merge --no-edit <分支>,不推送。
能快进就快进;分叉了则生成一个带 git 默认信息的普通合并提交,toast 会说明是哪一种。右键「仅在远端」的行时,直接合并它的 origin/… 引用,不会创建本地分支。
冲突不会被 abort:冲突标记、索引和 MERGE_HEAD 都留在工作区,胶囊随即开始显示冲突文件数。解决冲突后自己提交完成合并 —— 可以在终端里提交,或让会话执行。提交面板只做部分提交(git commit --only),git 在合并进行中会拒绝。若本地改动会被覆盖,合并会以 git/dirty-worktree 失败,工作区原样不动。把当前分支合并到自己、以及在游离 HEAD 上合并,都会被拒绝。
分支操作
同一个右键菜单覆盖 SourceTree 里本地分支的其余操作。除变基外,它们都作用于选中的那个分支(变基永远是把当前分支重放到选中的分支上):
| 动作 | 实际执行的命令 |
|---|---|
将当前变更变基到 <分支> |
git rebase <分支> —— 当前分支的提交被重放到它之上,提交历史会被改写 |
拿来 <分支> |
对该分支所属远端执行 git fetch <remote>;「仅在远端」行就 fetch 它自己的远端 |
推送到 <上游>(已跟踪) |
对该分支执行 git push <remote> <分支>:<跟踪的分支> |
| 推送到… | 选择仓库和目标分支:git push --set-upstream <remote> <分支>:<目标分支> |
| 重命名… | 本地分支 git branch -m,上游配置随之迁移 |
删除 <分支> |
git branch -d;分支未完全合并时先弹二次确认,再以 -D 重试 |
| 复制分支名字 | 把分支名写入剪贴板 |
「仅在远端」行提供检出、合并、变基、拿来和复制(合并/变基/拿来都用它的 origin/… 引用),重命名、删除、推送因没有本地分支而禁用。
当前分支不能被删除、重复检出、合并到自己、变基到自己。
变基冲突会停在 git 停下的地方:rebase 保持进行中、胶囊显示冲突文件,用户在终端执行 git rebase --continue(或 git rebase --abort)
结束——插件不会替你 abort,也不会强制 continue。fetch 和 push 是菜单里仅有的两个网络动作,且只在你点击时执行。
Git Flow
胶囊菜单最下方的「启动 Git Flow 分支」「完成 Git Flow 分支」按标准 git flow 走:功能与发布分支从 develop 切出,
热修复从生产分支切出;完成后 --no-ff 合并回去。
| 类型 | 从哪切出 | 完成后合并到 | 打标签 + 改版本号 |
|---|---|---|---|
feature/ |
develop |
develop |
否 |
release/ |
develop |
生产分支 + develop |
是 |
hotfix/ |
生产分支 | 生产分支 + develop |
是 |
完成顺序:合并 → 打标签 → 提交 package.json 的 version → 推送分支与标签 → 删除本地分支。
- 仓库还没有
develop时,「启动」会引导一键从生产分支(或当前分支)创建。 - 合并、打标签、改版本号这些完成后不会自动回滚;推送失败会明确提示「本地已完成,但推送失败」,本地成果保留。
- 能提前拦下的失败都在任何写操作之前检查:工作区不干净、标签已存在、目标分支不存在。
- 合并冲突时保留冲突现场让你手动解决,不自动
--abort丢弃改动。 - 仓库根目录没有
package.json(非 Node 项目)时自动跳过改版本号这一步。
安全边界
- 浏览器只发
sessionId,仓库目录由宿主从会话的工作区解析,页面无法指定任意路径执行 git。 - 参数永远是数组,从不拼 shell 字符串;
stdin: 'ignore',GIT_TERMINAL_PROMPT=0,不会卡在凭据提示上。 push永不带--force/--force-with-lease,只推当前分支的上游(无上游时--set-upstream <remote> HEAD:refs/heads/<branch>)。- 提交只
git add面板里勾选的文件;一个都没勾返回git/no-files-selected,绝不隐式git add -A。 - 合并只对当前分支执行
git merge --no-edit <分支名>,分支名来自浏览器;不传--no-ff/--force/-X,不推送,也绝不自动 abort 冲突合并。 - 每个分支操作只执行一条固定的 git 子命令:
git rebase <分支>、git fetch <remote>、git push <remote> <分支>:refs/heads/<目标>、git branch -m、git branch -d|-D。remote 名必须出现在git remote列表里;fetch 从不 prune / reset;当前分支永远不可删除;-D强制删除只在「未合并」警告之后再确认一次才会发出。 - 脏工作区切分支默认失败(
git/dirty-worktree),只有用户在二次确认框里点「强制切换」才带--force。 - 放弃更改只在确认框里点了「放弃更改」才执行:已跟踪路径用
git restore --source=HEAD --staged --worktree回到 HEAD,未跟踪路径用git clean -fd删除(不带-x,被忽略的文件不动);有冲突的文件直接拒绝,不做猜测。 workspaceRegistry用ctx.get('workspaceRegistry')惰性读取,而不是写进static inject:headless 之类的 profile 没有它, 插件仍然激活(不会留下 "entry did not activate" 警告),此时任何 git 调用返回git/not-a-repository。- 没装 git、不是仓库、超时、以及每一次拒绝都走结构化错误码(
git/not-installed/git/not-a-repository/git/timeout/ …), UI 显示对应文案;非仓库或无 git 时胶囊直接不渲染。
结构
src/
├── index.ts 宿主半入口(默认导出 GitFlow 服务)
├── service.ts GitFlow:status / branches / checkout / createBranch / merge / rebase / fetch / push / pushBranch /
│ renameBranch / deleteBranch / remotes / commit / discard / pull / generateMessage
│ + gitflowState / startFlow / initDevelop / finishFlow
├── git.ts GitRunner:subprocess + 净化 env + 超时 + porcelain v2 解析
├── routes.ts /api/dsh-git-flow/*(仅挂载在有 webServer 的 profile)
├── message.ts 提交信息:ctx.llm.stream 走会话自己的 provider/model,失败退模板
├── contract.ts 宿主↔浏览器共享的纯类型
└── client/
├── index.ts slots.inject('conversation.input.left') + locale 注册
├── BranchChip.tsx 状态、菜单、弹窗、toast 的容器
├── BranchMenu.tsx 分支胶囊 + 锚定菜单
├── BranchActionsDialog.tsx 变基确认 / 重命名 / 删除(强制)/ 推送到指定仓库的弹窗
├── CommitDialog.tsx 提交面板 / 新建分支 / 强制切换与放弃更改的确认
├── GitFlowDialog.tsx 启动 flow 分支 / 引导创建 develop / 完成 flow 分支
├── api.ts fetch 包装 + 错误码到文案
├── styles.ts 弹层宽度、文件行省略所需的少量 CSS(唯一需要真 CSS 的地方)
└── locales.ts zh/en 字典(en 按 zh 收窄,缺键即编译错误)
构建
pnpm install
pnpm build # tsc -> lib/types/**,tsdown -> lib/index.js + lib/client.js
lib/ 随仓库一起提交(不在 .gitignore 里),包内也不再有 prepare 脚本 —— 这样从 git 地址安装时不需要构建、不需要 devDependencies,也不需要把包加进 pnpm 的 onlyBuiltDependencies 白名单:
- pnpm 10.34+ 会拦截 git 依赖的
prepare(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED),而白名单必须写成dsh-git-flow@<地址>#<commit sha>这种带 sha 的精确串,上游每次提交都会失效; - 因此改成「产物入库、装上即用」:
dsh plugin add <地址>直接拿lib/,实测 pnpm 10.34.5 下 263ms 装完。
改完 src/ 之后请先 pnpm build 再提交,把 lib/ 一起带上(否则别人装到的是旧产物)。
产物契约:
lib/index.js:Node ESM,被 cordis loader 直接 import。lib/client.js:CJS 工厂包成window.__ModuleLoader__.load({ id: "@hunan36/dsh-git-flow", factory: (require) => … })(id 取package.json的name);react、react/jsx-runtime、@deepseek-ai/dsh-client-ui-primitives保持 external,由页面静态模块表提供。
兼容性
针对 @deepseek-ai/dsh@0.1.6-alpha.2 实测开发,peer/devDependencies 锁在该版本。dsh 尚在 alpha,
conversation.input.left、ctx.webServer.register、ctx.slots.inject 等契约可能在小版本间漂移;
升级 dsh 后请重跑构建并确认 lib/client.js 首行仍是 window.__ModuleLoader__.load({ id: "@hunan36/dsh-git-flow"。
已知边界
- AI 提交信息要求会话已经有过一轮对话(
request/header里记了 provider/model)。没有路由时返回git/no-route,面板提示先发一轮消息;模型失败或输出不合规时用模板兜底(英文chore: update N files/ 中文chore: 更新 N 个文件),且会明确告知。 - 提交信息默认英文。只有两个入口能改:面板里的语言切换(按浏览器记住)和
messageLanguage配置;模板兜底跟随同一个选择。 - 远端分支只在本地已有
refs/remotes/**时才可见(插件不做后台 fetch);「刷新状态」不触发网络。 - 冲突文件不可勾选,需要先在对话里解决冲突。
git switch --force会丢弃全部本地改动(不只是冲突的那些),确认框里的文案就是这么写的;只想丢掉某一个文件的改动,用提交面板里该文件的「放弃更改」。- 切到一个不存在且无同名远端的引用时,git 自己会失败,条目按
git/failed上报 git 原文(这种情况只能来自过期的 UI 状态,正常菜单里列的都是真实存在的分支)。
验证记录(0.1.6-alpha.2)
独立 DSH_HOME + profile gitflow-web(bundles 追加 @hunan36/dsh-git-flow)实测:胶囊文本 == git branch --show-current;
弹层行数 == 本地分支 + 仅在远端;搜索过滤;切分支后胶囊与工作区文件同步;脏工作区切换被拦下并弹二次确认(--force 才丢弃改动);
非法分支名报错、合法名创建并切换;勾选单个文件生成 AI 信息(feat: …)并只提交该文件(git log -1 --name-only 验证);
提交并推送后 git ls-remote 出现该分支且建立 upstream;非 git 工作区会话里胶囊不渲染;headless profile 启动无警告、无路由。
链接
同类插件
zhu1090093659/dsh-web#packages/dsh-git-graph★ 9959
输入框上方提供 Git 分支选择器,并把分支泳道与提交历史画成图谱,沿着时间线找到任意变更。
Akimiya-z/codex-guard#dsh★ 132
在 DeepSeek Harness 内做提交前的 Pull Request 卫生检查:扫描当前改动中的 TODO 残留、硬编码密钥与非规范提交信息。
Cerbur/clutch-dsh#clutch-dsh-worktree★ 38
为 DSH Web UI 增加按 Git Worktree 组织 Session 的视角,同时继续由 DSH 管理原始 Project 和 Session 数据。
xbzbing/dsh-git-panel★ 33
DSH Web GUI 里的 IDE 风格 Git 面板:常驻标签页含三栏总览(分支/标签列表、可搜索的提交历史图、含变更文件树与提交信息的提交详情)与变更记录页(统计条、带逐文件暂存/取消/丢弃的未提交列表、含 amend 的提交框);并排差异带懒加载 highlight.js 语法高亮、块间上下文按需展开与词级行内高亮,图片文件以变更前/后双图对照;另在输入框提供 zsh 风格分支标记。所有 git 命令以 argv 数组经宿主 subprocess 执行、cwd 锁定仓库根。
DamonKoy/dsh-web-ui#dsh-git-graph★ 30
dsh web GUI 会话头部栏的 Git 分支选择器与提交图。
PerryLink/dsh-github★ 28
官方级 GitHub CI 集成:composite action.yml、轮询 PR 评审机器人(幂等行内评论 + status-check 门禁)以及 PR/issue 工具,所有写入走人工审批门。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。