@github: Git 2.55 带来了新的增量重新打包策略、更灵活的历史编辑方式,以及更多功能。查看…
摘要
Git 2.55 已发布,引入了使用多包索引的增量重新打包策略、更灵活的历史编辑方式,以及来自超过100位贡献者的贡献。
查看缓存全文
缓存时间: 2026/06/30 03:35
Git 2.55 已发布,带来了新的增量重打包策略、更灵活的编辑历史方式等众多新特性。来看看我们挑选的最新版本亮点 ✨ https://t.co/lDyyGZDqH4
Git 2.55 亮点
来源:https://github.blog/open-source/git/highlights-from-git-2-55/
开源项目 Git 刚刚发布了 2.55 版本,汇集了来自超过 100 位贡献者的功能与 bug 修复,其中 33 位是首次贡献者。我们上次跟进 Git 的最新动态还是在 2.54 发布时(https://github.blog/open-source/git/highlights-from-git-2-54/)。为了庆祝这次新版本,以下是 GitHub 为您带来的自上次以来一些最有趣的功能与变更解析。
使用增量多包索引进行重打包
本系列的常客读者可能还记得我们之前介绍过的增量多包索引(https://github.blog/open-source/git/highlights-from-git-2-47/#incremental-multi-pack-indexes)和增量多包可达性位图(https://github.blog/open-source/git/highlights-from-git-2-50/#h-incremental-multi-pack-reachability-bitmaps)。 如果您需要复习一下,这里有个简版说明。Git 将仓库内容存储为一个个对象(https://git-scm.com/book/en/v2/Git-Internals-Git-Objects):提交、树和 blob。这些对象通常存放在包文件(https://git-scm.com/book/en/v2/Git-Internals-Packfiles)中,即对象的压缩集合。每个包文件都有一个对应的包索引(https://git-scm.com/docs/gitformat-pack),让 Git 能快速定位包内的任何对象。
但大型仓库通常不会只有一个包文件:随着时间推移,抓取、推送、维护任务和重打包会留下许多包。多包索引(https://git-scm.com/docs/git-multi-pack-index)(简称 MIDX)为 Git 提供了跨越多个包的单一索引。Git 无需打开并搜索每个包的独立索引,只需查询 MIDX 即可知道给定对象位于哪个包以及哪个偏移量。这对于大型仓库尤其有用,也是 GitHub 仓库维护策略的基石之一。
正如我们在 Git 2.47 引入增量 MIDX 格式(https://github.blog/open-source/git/highlights-from-git-2-47/#incremental-multi-pack-indexes)时所介绍的,仓库可以将其 MIDX 存储为一层层的链,而不是一个覆盖所有包的单一 MIDX。单一文件 MIDX 读取简单高效,但维护成本很高;由于该文件包含它所覆盖的所有包,即使是很小的更新,在已经很大的仓库中也可能需要大量写入。增量 MIDX 通过存储一系列 MIDX 层来解决这个问题。每一层覆盖一些包的集合,链文件记录这些层的顺序。在链的顶端追加一个新层不会使旧层失效,因此 Git 可以为新创建的包建立索引,而无需重写覆盖整个仓库的单个 MIDX。
Git 2.55 教会了 git repack 如何直接写入这些增量 MIDX 链:
$ git repack --write-midx=incremental
在没有其他选项的情况下,该模式是仅追加的:Git 为重打包创建的包写入一个新层,并保留现有层不变。这在您希望最小化维护运行期间元数据重写量时已经很有用。但仅追加的链不可能永远增长。如果每次维护都添加一个新层,最终链本身就会变成需要维护的东西。
Git 2.55 还支持将 --write-midx=incremental 与几何重打包结合使用:
$ git repack --write-midx=incremental --geometric=2 -d
当这些模式一起使用时,每次重打包都会创建一个新的顶端层,然后决定是否应将相邻层压缩在一起。默认规则由 repack.midxSplitFactor 控制:如果较新层中累计的对象数量相对于下一个较旧层变得足够大,Git 就会将这些层合并为一个替换层。否则,较旧的层保持不变。
从高层次来看,算法的工作原理如下。其中,N 指 repack.midxNewLayerThreshold 值,f 指 repack.midxSplitFactor 值:
- 选取未被 MIDX 覆盖的包作为几何重打包候选。如果顶端 MIDX 层至少有
N个包,也将其中的包纳入候选。 - 对该候选集应用通常的几何重打包规则,并写入一个新的顶端 MIDX 层覆盖结果包。
- 当较新层的累计对象数量超过下一个更深层对象数量的
1/f时,压缩相邻的 MIDX 层。
为了看清各个部分如何配合,让我们从一个已经存在增量 MIDX 链的仓库开始。较旧的层在左侧,顶端层在右侧。同时,正常的仓库活动不断产生新的包。这些包尚未被任何 MIDX 层覆盖,这意味着下一次维护运行有两个任务:决定重打包什么,以及决定重写多少 MIDX 链。
通常情况下,这些未被 MIDX 覆盖的包是唯一的几何重打包候选:Git 可以写入一个新包和一个新的顶端 MIDX 层,而不影响任何现有层。下图显示了更有趣的情况:当前顶端层已累积足够多的包,达到了配置的 repack.midxNewLayerThreshold。一旦达到该阈值,顶端层的包就可以与被新写入的包一起成为几何重打包的候选者。
几何重打包然后对最新的候选包提出一个局部问题:如果 Git 将某个后缀包集合 P 卷起来,紧邻 P 左侧的包是否足够大,以保持几何级数?在第一次尝试中,P 包含当前顶端层中最小的包以及新的未 MIDX 包。但分割左侧的包只有 30,000 个对象,比 P 大小的两倍还小,所以这个分割太靠右了。
因此 Git 将分割点向左移动一个包,再次提出同样的问题。现在 P 包含了顶端层中更多的包。紧邻左侧的包有 100,000 个对象,至少是所选后缀的两倍大小。这就是几何不变性成立的点,所以 Git 可以正好将这些包卷成一个新包。
写入该新包后,Git 在先前顶端层剩余包和新写入的卷起包之上写入一个新的顶端 MIDX 层。至此,包文件本身状态良好,但 MIDX 链可能仍然累积了太多小的相邻层。Git 将相同的“新比旧”直觉应用于 MIDX 层本身:如果较新层相对于其邻居足够大,则将其元数据压缩到一个替换层中。
这个压缩步骤是纯元数据操作。Git 不会再次重打包这些层中的对象;它会写入一个新的 MIDX 层覆盖相同的包文件。然后考虑下一个更旧的层。在这里,压缩后的层仍然比深层的一半小,所以 Git 停止。较旧层保持不变,这是保持此维护增量化的关键属性。
结果是两种极端之间的折衷。单文件 MIDX 最小化查找复杂度,但维护期间可能需要大量重写。纯追加的增量 MIDX 最小化每次写入,但允许链无限制增长。几何增量重打包使层的数量与对象总数成对数关系,同时确保最新的、最小的层比旧的、大的层更频繁地被重写。
这还与 Git 现有的重打包机制集成。新写入但尚未被 MIDX 链覆盖的包始终是几何重打包的候选者;较深层 MIDX 中的包保持不变。顶端 MIDX 层中的包仅在顶端层至少有 repack.midxNewLayerThreshold 个包时才加入候选集。如果顶端层仍然小于该阈值,Git 完全跳过干扰它,仅为新写入的包追加一个新层。对于持续收到新对象的仓库,这意味着日常维护可以增量地更新仓库的包元数据,而无需每次维护运行都重写覆盖整个对象存储的单个 MIDX。
【来源(https://github.com/git/git/compare/1103041f3482c2e19174a6192dabfcf6a286b6a8%E2%80%A606733a50eeec4205011d210d3932c5b708a665e9)】
使用 git history 修复早期提交
任何在发送代码审查前打磨过提交系列的人可能都有过这种经历:你注意到工作树中的一个更改实际上属于早期的某个提交,而不是分支的顶端。如今,一种常见的处理方法是创建一个修复提交然后自动压缩:
$ git commit --fixup=<commit>
$ git rebase --autosquash <commit>^
这有效,但要求你描述机制而非意图。Git 2.55 在实验性的 git history(https://git-scm.com/docs/git-history/2.55.0)命令基础上,增加了新的 fixup 子命令。该命令将当前暂存在索引中的更改应用到早期提交:
$ git history fixup <commit>
这里有一个小例子。第一个提交引入了一个煎饼食谱,随后又提交了几个。后来,我们发现食谱中缺少枫糖浆。暂存这一行更改后,git history fixup <commit> 将其折叠到原始食谱提交中,并重放后续的提交。
在这里,暂存的更改成为目标提交本身的一部分。默认情况下,目标提交保留其消息和作者信息,除非你传递了 --reedit-message 选项。Git 会重写后续提交,使分支最终处于一个等价的、修复位于正确位置的历史状态。
与 git history 的其他部分一样,此命令仍处于实验阶段。它也有意保守。由于 fixup 从索引读取,它需要一个工作树,无法在裸仓库中运行;如果应用暂存的更改会产生冲突,命令会中止,而不是让你处于一个有状态的重写过程中。
【来源(https://github.com/git/git/compare/94f057755b7941b321fd11fec1b2e3ca5313a4e0%E2%80%A6c6c225793003ffb8b376c8994d44ca63bc04ac40)】
冰山一角……
现在我们更详细地介绍了最大的几个变化,让我们再看看本版本中其他一些新特性和更新。
-
本系列的常客读者可能还记得我们在 Git 2.54 中介绍过的基于配置的钩子(https://github.blog/open-source/git/highlights-from-git-2-54/#h-config-based-hooks),它允许你在 Git 配置中定义钩子,而不仅仅作为
$GIT_DIR/hooks中的可执行文件。钩子是在工作流中某些已知点(如创建提交前或接收推送后)由 Git 运行的脚本。将它们移入配置使得钩子更容易共享、组合和选择性禁用,而无需将脚本复制到每个仓库的钩子目录中。Git 2.55 扩展了这项工作,允许兼容的已配置钩子并行运行。例如,一个项目可能有独立的用于代码检查和单元测试的提交前钩子;如果两者都声明了hook.<name>.parallel = true,Git 可以同时运行它们。并发作业的数量可以通过hook.jobs全局控制,每个事件通过hook.<name>.jobs控制,或者通过命令行git hook run -j控制。需要共享状态的钩子(如提交消息钩子或其他检查索引或工作树的钩子)继续串行运行。【来源(https://github.com/git/git/compare/2226ffaacd93d3fe5554687a70d9190d72596f96%E2%80%A675b7cb5e14f03965cf87a976356bcbdcfb4edbad)】 -
如果你曾运行过
git status(https://git-scm.com/docs/git-status)而终端却迟迟没有响应,你可能用过 Git 的内置文件系统监视器(https://git-scm.com/docs/git-fsmonitor–daemon/2.55.0)来加速。当启用core.fsmonitor(https://git-scm.com/docs/git-config#Documentation/git-config.txt-corefsmonitor)时,像git status这样的命令可以询问一个长期运行的守护进程哪些路径发生了变化,而不是扫描整个工作树。此前,该内置守护进程仅适用于 macOS 和 Windows。Git 2.55 增加了对 Linux 的支持,实现使用了inotify(https://man7.org/linux/man-pages/man7/inotify.7.html)。这不需要提升权限,但每个目录需要一个监视器,因此非常大的仓库可能需要提高fs.inotify.max_user_watches(https://www.kernel.org/doc/html/latest/admin-guide/sysctl/fs.html#max-user-watches)限制。与其他平台一样,该守护进程对网络挂载的仓库保持保守,这些仓库仍需选择启用。【来源(https://github.com/git/git/compare/d2c01318b0f04c568808072c5b328e8021b94530%E2%80%A6b1cebd7194299ad5414ab2122b2970b339399446)】 -
可达性位图(https://git-scm.com/docs/bitmap-format)是 Git 使用的技巧之一,用于回答诸如“从该提交可以到达哪些对象?”这样的问题,而无需从头遍历整个对象图。它们使对象遍历更快,但 Git 在诸如
git repack --write-midx-bitmaps等维护任务期间仍需要构建和更新这些位图。Git 2.55 通过避免不必要的树递归、重用已计算的选择性位图、缓存对象位置以及对位图进行排序后再进行异或(XOR)(https://en.wikipedia.org/wiki/Exclusive_or)操作,加速了位图生成路径。根据补丁系列中的基准测试,这些通用改进将一个大型仓库的位图生成时间从约 612 秒(https://github.com/git/git/commit/e3959cc78c968d8f029daa48d4aadcb486da0629)减少到约 294 秒(https://github.com/git/git/commit/c720bbcc53f223236220c7a879f0a0e73e5d3739)。同一系列还改进了伪合并位图(https://git-scm.com/docs/gitpacking/2.55.0#_pseudo_merge_bitmaps),它将相关引用分组在一起,以便 Git 在遍历时可以合并预先计算的位数组(https://en.wikipedia.org/wiki/Bit_array),而不是重复发现相同的对象。在一个基准测试(https://github.com/git/git/commit/49633dc88c14008f9a405f215b60994362b36d6c)中,伪合并使完整的git rev-list --objects --use-bitmap-index遍历速度提高了近 20 倍,但之前几乎使位图生成时间翻倍。经过这些更改,伪合并保留了大部分遍历加速,同时为位图生成路径增加了更少的工作。【来源(https://github.com/git/git/compare/56a4f3c3a221adf1df9b39da69b8a6890f803157%E2%80%A65e6e8dc7860374d79bad3e2a3ade0c2d391bbad6),来源(https://github.com/git/git/compare/600fe743028cbfb640855f659e9851522214bc0b%E2%80%A649633dc88c14008f9a405f215b60994362b36d6c)】 -
如果你使用部分克隆、过滤包或 Git 需要…(注:译文到此截断,但根据原文,此处应继续翻译剩余内容。由于用户输入被截断,我无法看到后续原文。但根据要求,我应仅输出翻译后的文本。既然用户给出的原文到此结束,我就按此输出。)
相似文章
Git 2.54 亮点速览
Git 2.54 带来全新的实验性 `git history` 命令,可在不碰工作区的情况下重写或拆分提交,另有 137 位贡献者带来的其他改进。
jj v0.43.0 发布
Jujutsu v0.43.0,一个与 Git 兼容的版本控制系统,旨在易用性和强大的历史重写功能,现已发布更新。
@laogui: 经过几天使用,我可以毫不夸张地说:Rebased 就是目前最强的 Git 图形化客户端。 用过 JetBrains 系列 IDE 的朋友都知道,它的 Git 功能体验非常好——尤其是 Diff 功能。但这几年 JetBrains 在 AI…
Rebased 是一款基于 JetBrains IntelliJ 社区版构建的开源 Git 图形化客户端,砍掉了所有语言相关功能,只保留并优化了 Git 工具,提供了顶级的 Diff、Review、交互式 Rebase 和冲突解决体验,免费使用且零学习成本。
Git 由什么构成?(2022)
深入教程,解释 Git 的内部结构,包括对象、哈希以及 Git 如何存储数据,并提供 Go 和 shell 命令示例。
为智能体时代打造的Git平台
一个专为智能体时代设计的新Git平台,可能针对AI驱动的开发工作流。