Skip to content

Releases: alanhaskey/dsh-gitmate

GitMate 1.15.0

Choose a tag to compare

@alanhaskey alanhaskey released this 04 Nov 02:04

图片和 PDF 现在可以直接在面板里看。

之前为什么看不到

图片和 PDF 没有补丁可读 —— git 只报告 binary —— 所以点开这类文件原来只有一句 binary file; contents omitted,等于没有内容。

现在

点开这类文件时,面板直接读出工作区里的当前文件来显示:

支持 png jpg jpeg gif webp avif bmp ico svg pdf
显示方式 图片用 <img>(SVG 也走 <img>,脚本不会执行),PDF 用 <embed>
变更行 照旧在预览下方显示,两者不冲突
上限 单文件 6 MB(base64 之后更大,不该把一个 40 MB 的截图塞进面板)

只看工作区那一份。 已提交的那份要经 shell 取回,二进制会被当文本损坏——所以只暂存、或已删除的文件会明说没有可预览的内容,而不是显示坏图。

blob: URL 在替换和卸载时都会 revoke —— 一个 object URL 会一直持有整个文件。

写测试时抓到一个真 bug

仓库内的绝对路径被误判为越界。

git rev-parse 报的是解析后的根(/private/var/…),而拼出来的路径是 /var/… —— /var 是指向 /private/var 的符号链接,于是词法比较判定"逃出仓库",一个完全合法的路径被拒。

现在两边都先 realpath 再比较。这个 bug 只在符号链接系统上出现(macOS 的 /var、/tmp 都是),光看代码不会发现。

验证

node test/smoke.mjs     # 647 项断言

新增 13 项:PNG 的 MIME 与字节往返一致(不是"返回了什么",是"解回来还是原文件")、PDF/SVG 类型、文本被拒、越界被拒、缺失文件与目录的处理、6 MB 上限(含体积提示)、面板路由可达,以及渲染层断言预览标题确实出现在屏幕上。

GitMate 1.14.0

Choose a tag to compare

@alanhaskey alanhaskey released this 23 Oct 09:28

支持一个目录下的多个仓库。

症状

工作区根目录常常只是"一堆项目的容器" —— 它自己不是仓库,下面有 alpha/、beta/。这种目录下"是不是仓库"对每一个项目都答"不是",于是面板只会提示初始化,看不见下面已经有的仓库。

现在

打开这样的目录,面板会先找仓库:限深 3 层、跳过 node_modules / vendor / dist / build 等、最多 40 个,列出名字和相对路径让你选。选中之后整个面板就操作那一个 —— 状态、提交、分支、历史、忽略规则、AI 审阅全部一起跟随。

顶部会出现一个仓库标签,点它回到列表。

几个刻意的地方:

情况 行为
根目录本身就是仓库 只列出它自己,不把里面的子模块当成可选项
.git 是文件(worktree / 子模块指针) 也算一个仓库根
找到仓库后 不再往下钻
根目录下一个仓库都没有 才显示原来的初始化卡片(多一个"在根目录新建"的次要选项)

安全边界

请求可以带 repo 指定用哪个仓库,但只接受会话目录之内的路径:

  • /etc → 忽略,回落到会话目录
  • ../.. 爬出去 → 忽略,回落到会话目录

这两条有测试。工具体也一样 —— Agent 传一个子目录,git rev-parse 自己会找到仓库根。

验证

node test/smoke.mjs     # 634 项断言

新增 15 项:发现(worktree 指针 / 跳过规则 / 深度边界 / 根目录是仓库时只列自己)、路由列出、根目录不是仓库、指定一个后按它作答、外面的路径和爬出去的路径被忽略、以及一次真实操作确实落在选中的仓库里。渲染守卫也新增了"多个仓库"这条界面路径,断言屏幕上出现的是仓库名字而不是"这里还不是仓库"。

顺带修的测试缺陷

渲染测试原本用硬编码的下标取面板的状态槽 —— 这次插入两个 state 就让所有下标指错了槽位,而测试只是"没抛错"。现在改成按名字取(slotOf),名字写错会直接抛。

GitMate 1.13.0

Choose a tag to compare

@alanhaskey alanhaskey released this 22 Oct 02:26

这一版不改功能,只让包可以直接发布到 npm —— 因为新机器从市场安装时报了:

git 无法在无人值守的情况下完成 SSH 认证。如果你的 SSH key 设了密码,请用 ssh-agent(ssh-add)…

这不是插件的 bug,是那个网络的 bug

我读了市场(dshmarket)的安装代码,路径是:

installTargetFor(entry)
  → entry.npm           (若有)
  → entry.tarball       (若有)
  → github:owner/repo   ← 你的条目走这条

而 github: 由 pnpm 解析成 codeload 的 HTTPS tarball,SSH 只是 HTTPS 失败之后的兜底。所以在那台机器上是 codeload 先失败,才落到 SSH,又被市场的 BatchMode=yes 快速失败 —— 报的是这个组合。

佐证:这个包没有安装脚本、没有依赖,装的时候只是解一个 tarball;那条 tarball URL 从别的网络访问是 200 / 162 KB。所以插件内容不可能是原因。

手边的绕法

# 走 github.com 的 HTTPS,而不是 codeload(不同的网络路径)
dsh plugin --profile desktop add "git+https://github.com/alanhaskey/dsh-gitmate.git#v1.13.0"

或者按市场自己的建议配 ssh-agent / GIT_SSH_COMMAND。

长期修法:发布到 npm(本次已备好)

市场的条目格式里不允许手写 npm: 字段(contributing 明说会被校验拒绝)—— 它从 registry 自动采集映射,所以发布是唯一途径。发布之后安装走 registry,不再碰 GitHub 与 SSH。

已确认就绪:

项 状态
repository / homepage / bugs 已指向本仓库(市场关联的硬要求)✓
npm 包名 dsh-gitmate registry 上是空的 ✓
prepublishOnly 已加:发布前先跑 617 项断言 ✓
npm pack --dry-run 16 个文件、117.6 kB,只有 lib/、locale/、icon、cordis.patch.yml、两个 README、LICENSE ✓

发布本身需要账号凭证,命令是:

cd /path/to/dsh-gitmate
npm login
npm publish          # 有 2FA 时加 --otp=XXXXXX

发布后市场会自动把条目关联到 npm,之后所有安装都从 registry 走。

GitMate 1.12.1

Choose a tag to compare

@alanhaskey alanhaskey released this 20 Oct 14:02

上一版改完就发了,我一次都没看过它长什么样 —— 结果基本没生效。这次先写了个渲染器,用同一套几何生成 SVG 转成 PNG 看了一眼,才看到真问题。

一、每行之间固定留一条缝

每行的图形画在一个 24px 高的固定盒子里,而一行实际高得多(展开提交时更高)—— 线条只覆盖每行顶部 24px。

上一版我用"把线画出盒子 5px"来补 ✗ 真实缝隙有十几到上百像素,5px 毫无意义。

现在:竖直段是钉在行底的元素(top + bottom:0),行多高它就有多长;曲线只出现在节点上下的一小段里。行高再怎么变都接得上。

(渲染器里把首行拉到 130px、其余 26px 验证过 —— 线笔直穿过整个展开区。)

二、收敛的泳道从不释放

一个提交可以同时是两条泳道的父提交(merge 的两边都指向它)。buildGraph 只释放被认领的那条,其余永远等一个已经发出的提交,在图上拖着一条没有提交的长线 —— 这才是"悬空"最直观的来源。

现在: 认领之后把其余等价泳道一并释放,它们在这一行收进节点(画 merge 曲线)。

顺带

  • 根提交不再把自己的线画到行底
  • 线条分三档粗细:本提交自己的线最重、分支曲线次之、路过的泳道最轻

验证

node test/smoke.mjs     # 617 项断言

测试重写为新的不变量:

  • 收敛的泳道被释放、最后一行 after 为空
  • 每条 bottom:'end' 的竖线对应的泳道确实延续到下一行("跑过死泳道"即为错)
  • 每行继承的泳道必须从 top:0 起(否则出接缝)
  • 根提交停在节点、分支从节点下方起笔并由曲线接上
  • 真仓库 merge 后:有曲线、无游离线、无接缝

把"释放收敛泳道"那几行删掉,三条断言立刻报错 —— 与渲染器里看到的一致。

GitMate 1.12.0

Choose a tag to compare

@alanhaskey alanhaskey released this 20 Oct 13:24

提交图(左侧的树)重画。两个具体缺陷,都不是"审美"问题:

一、上下接不上

每一行是一个 只有 24px 高的 SVG,而这一行还有 4px 上下内边距 + 1px 下边框。线条画到 SVG 边缘就停了 —— 两行之间固定留一条 9px 的缝。

现在穿过某行的泳道一律画到行外(从 -5 到 29),盖住这一行自己的内边距和边框,下一行再从 -5 接上。相邻两行因此是重叠的,而不是相接的。

二、多分支时是悬空竖线

某个提交新开出的父分支(merge 的第二父),原来只画了一条从行中到行底的竖线,和节点之间没有任何连接 —— 看起来就是断的。

现在:

情况 画法
泳道穿过此行 竖直,画到行外
此行新开出的父分支 从节点出发画曲线(branch)
泳道在此结束 从行首曲线收进节点(merge)

顺带:线条分层次

原来是所有线一样灰一样粗 —— 多分支就是一片栅栏。现在同一色相三档:

  • 本提交自己的线最重(border-l2,1.8px)
  • 分支 / 汇聚曲线次之(label-secondary 62%)
  • 只是路过的泳道最轻(38%,1.3px)

全部用已发布的主题 token 调制,不引入新颜色。

架构:几何搬到 Host

新增纯函数 graphSegments(row),history 端点把每行的 {kind, lane, from, to, d} 随数据下发,面板只负责画。

这样"相邻行是否接上"就从只能靠看变成了可以断言的数字。

验证

node test/smoke.mjs     # 616 项断言

新增 13 项。核心是一条属性断言:遍历每对相邻行,凡是在两行都出现的泳道,上一行必须画到 ROW_HEIGHT+pad、下一行必须从 -pad 起。

还包括:真仓库里做出一次 merge,断言历史里确实有 branch 段、且每条泳道都接得上。把 pad 改回 0,四条断言立刻报错。

顺带修了测试自己的两处问题:fixture 里 merge 的参数写成了 source(应为 branch),merge 静默失败而我从未断言它成功;以及断言辅助函数把 history 的行对象当成了段数组。

GitMate 1.11.2

Choose a tag to compare

@alanhaskey alanhaskey released this 14 Oct 05:19

上一版修了"历史按钮该禁",但设置点进去看不到 —— 这个版本修的是后半截。

症状

面板显示「这里还不是 Git 仓库」,设置按钮可以点 ✓,但页面没变:初始化卡片还挡在那儿。

原因

主区域的组装顺序是:

不是仓库  →  渲染初始化卡片
是仓库    →  按 view 渲染(设置 / 历史 / 改动…)

于是设置视图在非仓库下永远不渲染 —— 按钮亮了,页面没变。

修法

设置渲染在初始化卡片之上。 理由是设置属于插件而不是仓库,而且 .gitignore 方案在仓库存在之前就有用(gitignorePlan 本来就是照着纯目录设计的,不是照着仓库)。

验证

node test/smoke.mjs     # 604 项断言

这次的断言不看源码,看屏幕:渲染守卫现在会收集渲染出来的文本节点,然后断言:

  • 「不是仓库」→ 屏幕上有初始化卡片的那句话 ✓
  • 「不是仓库 + 请求设置」→ 屏幕上有设置内容,且没有那句话 ✓

把旧组装放回去,断言立刻报:

FAIL and asking for settings shows settings, not the initialise card
  — "notRepo stashes 1 wip now stashPop stashApply notRepo notRepoHint init"

为什么这个 bug 上次没被测出来:之前的断言只检查"渲染不抛错",而初始化卡片确实渲染得好好的——错的是它和谁一起渲染。收集屏幕文本之后,这类"点击没反应"的问题第一次变得可测。

(写这条断言时我先用 notRepo 判断卡片,但 hero 里的分支位置在非仓库时也显示 notRepo,太粗 —— 改用只在卡片里出现的 notRepoHint。测试数据也有两处欠说明,一并修了。)

GitMate 1.11.1

Choose a tag to compare

@alanhaskey alanhaskey released this 14 Oct 05:09

不是仓库时,顶上的按钮该禁的禁、该留的留。

修的问题

面板明明显示「这里还不是 Git 仓库」,上面的分支图表(历史)按钮却还能点 —— 点进去只有一个空视图。

同步按钮早就有 || !status?.isRepo 的守卫,历史按钮漏了。

改动

按钮 不是仓库时
同步 禁用(原本就对)
历史(分支图表) 现在禁用
设置 刻意保持可点

设置不加守卫是有意的:面板不按预期工作时,人第一个想去的就是设置 —— 把它一起禁掉,等于在最需要的时候锁门。

顺带:仓库在面板开着时消失(切换了文件夹、.git 被删掉)会让视图退回改动列表,而不是让历史按钮亮着一个显示不出来的页面。

验证

node test/smoke.mjs     # 601 项断言

新增 3 项断言:历史按钮的 disabled 必须包含 status?.isRepo、设置按钮的 disabled 必须仍是那个单纯的 busy 判断(这条是防回归的关键 —— 它保证后人不会"顺手统一"把设置也禁掉)、以及退回逻辑存在。

渲染守卫新增两条路径:「不是仓库 + 请求历史视图」和「不是仓库 + 请求设置视图」——前者正是这个 bug 的状态。

GitMate 1.11.0

Choose a tag to compare

@alanhaskey alanhaskey released this 14 Oct 02:29

跨平台收尾。先说清查结论:真正跟平台相关的只有两处,都在 Host 侧,其余全部与平台无关。

清查结果

项 状态
命令引号 PowerShell 单引号翻倍 / POSIX 闭合-转义-重开,按 process.platform 选择 ✓
环境变量 走 shell 请求的 env 字段,而不是 POSIX 的 VAR=value 前缀 ✓
路径 全部 node:path ✓
写文件 只写操作的那个仓库里的 .gitignore;lib/ 之外不写任何东西 ✓
执行位 不写任何需要执行位的文件 ✓
git init -b <branch>,与平台无关 ✓
沙箱识别 DSH 那段拒绝文本三平台一致,所以识别逻辑本来就通用 ✓

这次补的两处

1. 沙箱起不来时的建议,按平台给。

原来一律说"Windows 上最常见的原因是 %TEMP% 不可写"——对 Linux 用户是错的指引。现在:

  • Linux → 缺 bubblewrap,或内核不支持 Landlock
  • macOS → sandbox-exec 不可用
  • Windows → %TEMP% 不可写
  • 其它平台 → 中性建议

(createHostText 接受 platform 参数,可注入,所以四个分支在一台 macOS 上就能全部测到。)

2. git 装不上时提示去确认 PATH —— 容器或刚装好的系统上这是最常见的原因。

验证

node test/smoke.mjs     # 598 项断言

新增 13 项,其中最关键的一组是恶意文件名端到端:往仓库里放一个同时含空格、单引号、$、分号的文件名,然后 stage → status → commit → diff 全走一遍。

这是引号唯一会以**"数据丢失"**而不是"报错"形式出问题的地方,而且三个平台共用同一条代码路径——所以它值得一个端到端断言,而不是只测 quoteFor 的字符串输出。

另外四个平台分支各自的建议、以及"每个分支都保留原始原因"都有断言。

GitMate 1.10.2

Choose a tag to compare

@alanhaskey alanhaskey released this 14 Oct 01:38

这一版不是修那个 Windows 问题的 —— 那台机器上的根因(DSH 自己跑在 Low 完整性级别,什么都写不了)与插件无关,下面是核对结果和一个小改进。

先核对两件事

1. v1.10.1 标签是正确的。 它指向 96665ca9d9ad,该提交树里的 package.json 版本正是 1.10.1。所以 …#v1.10.1 拉到的是对的版本。

2. 插件在安装和激活时什么都不写、也不跑 git。 我查过 apply():没有 writeFileSync、没有 mkdirSync、没有任何 git 调用——它只注册工具和路由。dependencies: null,scripts 只有 test。所以"装上插件"这个动作本身不可能触发沙箱、也不可能导致进程变成 Low 完整性。

这一版改了什么

有一件事确实是我的:面板每 5 秒探测一次仓库,而每次探测都会重走一遍沙箱启动,每一次都以同样的方式失败。

而那台机器上的证据显示,失败的那次 provisioning 会让整次运行的写权限退化——也就是说反复触发不只是浪费,可能还在加重问题。

现在 status 带回 unavailable 时轮询就停下,错误卡片上的「重试」是唯一的再次尝试入口:由你决定什么时候重试,而不是面板每 5 秒替你决定一次。

(用了一个 ref 存这个标记,避免为了读它而把 interval 拆掉重建——那会让每次状态更新都重置计时器。)

验证

node test/smoke.mjs     # 585 项断言

新增 3 项:轮询确实在 unavailable 时返回、错误卡片带重试按钮、运行指示仍然存在。

升级路径(在 Low 完整性修好之前装不上任何东西)

dsh plugin add 连 profile 里的锁文件都创建不了(package.json.lock → EPERM),所以它失败在 pnpm 启动之前,与插件内容无关。顺序是:

  1. 彻底退出 DSH Desktop(含托盘图标,任务管理器确认没有残留进程),然后从开始菜单正常启动——不要从任何 agent/沙箱的终端里启动它;
  2. 在 DSH 里跑 whoami /groups,确认是 Medium Mandatory Level(现在是 Low);
  3. 在普通 PowerShell 里(不要在 DSH 内部)执行升级;
  4. 完整重启 DSH,再刷新页面。

第 1 步是前提:它没修好之前,装什么都装不上,面板的写操作也仍然会失败。

GitMate 1.10.1

Choose a tag to compare

@alanhaskey alanhaskey released this 14 Oct 01:29

修一个让插件在 Windows 上一安装就无法使用的问题。

症状

打开面板就是:

无法运行 git
could not run git here: sandbox mode "workspace-write" is requested but no sandbox backend is usable on this host … Runner failure: windows-acl-run: EPERM: operation not permitted, mkdtemp 'C:\Users\…\AppData\Local\Temp\dsh-XXXXXX'

根因:面板没有跟随会话的权限

DSH 的解析顺序是:

mode = 请求里的 mode ?? 该 session 的 sandbox/mode 覆盖 ?? 部署默认值

工具调用在 1.9.0 已经跟随会话了,但面板那条路径没有 ✗ ——它的每个请求都带着 sessionId,却没有拿它去解析策略,于是永远落到部署默认值 workspace-write。

而这台机器的 workspace-write 后端起不来(mkdtemp → EPERM,%TEMP% 不可写)——于是面板一个 git 命令都跑不了,而且用户在自己的会话里把权限设成什么都不管用 ✗

修法

面板的会话不是猜的:它就是这个会话的标签页,cwdForSession(sessionId) 一直用它解析仓库目录。现在同一个会话也用来解析权限,dispatcher 在开头选一次 core,所有端点自然沿用。

于是:把会话切到 danger-full-access(或修好 %TEMP%),面板立刻能用。

(这也修正了早先一份任务文档里的建议——它说"HTTP 请求没有 session,只能用部署默认值"。但面板的请求是带 sessionId 的,而且已经用它定位仓库;用同一个会话解析权限既不是猜测,也正是"策略属于调用方"该有的样子。)

验证

node test/smoke.mjs     # 583 项断言

新增 3 项:面板请求确实带上会话的策略、确实是从那个会话解析的、没选过模式的会话按文档的优先级回落到部署默认值。

顺带修了测试替身的一处失真:假的 session 对象没有 id,而真实的 Session 有 id,策略服务正是读它。补上之后断言立刻从 { mode: 'workspace-write' } 变成会话真正的 danger-full-access —— 也就是说,这个 bug 差点因为替身失真而测不出来。