我为Jujutsu 版本控制系统贡献了一个小功能,我之前曾写过相关内容。
在 Git 中运行diff --stat
时,它会以修改文件列表的形式显示更改摘要,并统计每个修改文件的新增和删除行数。对于二进制文件,Git 会以字节大小显示差异。以下是我修改.dll
文件的示例提交:
commit 9649ab9bf70c92a1ebe2ac39b4d2ef86b1de37b9 Author: Evan Martin <[email protected]> Date: Thu Oct 17 11:56:28 2024 -0700 dinput: more stubs win32/dll/dinput.dll | Bin 2560 -> 3584 bytes win32/src/winapi/dinput/builtin.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++---- win32/src/winapi/dinput/dinput.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 6 deletions(-)
Jujutsu 具有相同的功能,但它不处理二进制文件:它只会计算文件中 0x0a 字节的数量,这没什么用。所以commit 9649ab9bf70c92a1ebe2ac39b4d2ef86b1de37b9 Author: Evan Martin <[email protected]> Date: Thu Oct 17 11:56:28 2024 -0700 dinput: more stubs win32/dll/dinput.dll | Bin 2560 -> 3584 bytes win32/src/winapi/dinput/builtin.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++---- win32/src/winapi/dinput/dinput.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 6 deletions(-)
这是一个非常小的功能,但结果却比我预想的更微妙,主要原因如下:上面的输出大小是为了让每行都适合终端宽度,这意味着如果文件名太长,它会截断文件名,并且还会缩放右侧的图表以适应终端宽度。最终你需要仔细测量所有相关文本,并注意舍入和下溢零(例如,如果终端太窄,根本无法容纳文件名)。
以下是一些小注释。
输出略有不同:Git 显示2560 -> 3584 bytes
,但在 PR 中讨论了是显示纯字节数还是以美观的方式打印数字之后,我确信diff --stat
输出中的其他行仅显示了变化的幅度,而不是前后变化。所以我的输出看起来像(binary) +1024 bytes
。这意味着你无法区分增长的文件和完全添加或删除的文件,但对于文本文件来说,情况已经如此,而且在我使用 Git 的这些年里,这从未困扰过我。
预期测试:我从Jane Street 的这篇博客文章中了解到了“预期测试”。文章里说这听起来是个很棒的功能,但它只适用于 OCaml,所以我从未尝试过。我很高兴地发现 Jujutsu 通过Insta使用了它们,Insta 是一个提供类似功能的 Rust 库。
在针对我的修改进行的测试中,它运行jj diff --stat
并断言输出结果如下。Insta 的妙处在于,我不需要手动更新这段文本;相反,它可以运行测试并以交互方式逐步查看输出结果的差异,并且对于我接受的更改,它会自动将其重新插入到代码中。
let output = work_dir . run_jj ([ "diff" , "--stat" ]); // Rightmost display column ->| insta :: assert_snapshot! ( output , @ r " binary_added.png | (binary) +12 bytes binary_modified.png | (binary) ...fied_to_text.png | (binary) -8 bytes binary_removed.png | (binary) -16 bytes ...y_valid_utf8.png | (binary) +3 bytes 5 files changed, 0 insertions(+), 0 deletions(-) [EOF] " );
(预期测试的思想比文本命令输出更深刻!阅读原始博客文章了解更多信息。)let output = work_dir . run_jj ([ "diff" , "--stat" ]); // Rightmost display column ->| insta :: assert_snapshot! ( output , @ r " binary_added.png | (binary) +12 bytes binary_modified.png | (binary) ...fied_to_text.png | (binary) -8 bytes binary_removed.png | (binary) -16 bytes ...y_valid_utf8.png | (binary) +3 bytes 5 files changed, 0 insertions(+), 0 deletions(-) [EOF] " );
彩色输出:在生成文本输出时,Jujutsu 会使用诸如added
或binary
之类的关键字标记子字符串,然后将其输入到外部系统,该系统会为这些语义类别分配颜色。这是一种巧妙的机制,可以在不同命令之间保持颜色一致,同时允许自定义。特别是,如果您自定义了其他命令(例如log
的输出,您将与这些命令进行交互。
Rust 构建输出非常庞大:这是我第一次尝试 Jujutsu,但在我研究这个东西的大约两个月里,我的target/
目录(包含 Rust 构建输出)已经增长到超过 25GB。天哪。我想这可能是各种库的中间输出,而这些库的版本本身在这段时间内也发生了变化?
附言:我其实有两个月没动笔了!我动笔了几个小时,就忘了,几周后才捡起来,然后又重复了好几次。
双倍宽度字符:文件名可以是 Unicode,即使在终端中,某些 Unicode 字符(尤其是中文)也应该占据两列。这意味着要测量文件名的宽度并正确地将其省略...
您不仅需要 Unicode 字符处理,还需要关于哪些代码点是双倍宽度的数据表。
该代码已经全部实现,我没有触及它,但我主要注意到,即使是像“缩短文件名以使文本在终端上对齐”这样非常基本的事情,如果你尝试彻底地做到这一点,它也会很快变成一个完整的项目。
提交权限:在提交了几个 PR 之后,他们授予了我合并自己修改的权限。对于一个第一次贡献的人来说,这真是太棒了!我估计仓库已经设置了拒绝强制推送,这样就算我把事情搞砸了,他们总能修复的。
未来工作:在撰写这篇博客文章时,我更仔细地查看了输出,并发现还需要进行更多的调整。
原文: https://neugierig.org/software/blog/2025/08/jj-binary-stat.html