Git history 命令值得更多关注

Hacker News Top 工具

摘要

文章重点介绍了新的 `git history` 命令及其 `fixup`、`reword` 和 `split` 子命令,这些命令提供了原子化且感知分支的提交历史编辑功能,带来了类似 jj 的益处,而无需切换版本控制系统。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/14 04:18

# git history 命令值得更多关注 来源:https://lalitm.com/post/git-history/ 在 git 上并行处理大量变更时可能会很痛苦。你最终会在分支和提交之间周旋,并运行可怕的 `rebase -i` 命令,这些命令只要你稍有不慎,就会让你的仓库处于半损坏状态。`jj`(https://github.com/jj-vcs/jj),一个 git 的替代品,最近被广泛讨论(1(https://news.ycombinator.com/item?id=48692083)、2(https://lobste.rs/s/beqyuc/evan_s_jujutsu_tutorial)、3(https://lobste.rs/s/fg3sgh/jj_tui_terminal_user_interface_jujutsu)、4(https://lobste.rs/s/27dxjg/petty_reason_we_didn_t_end_up_using_jj)),并且经常被宣传为解决方案。虽然我非常认同 `jj` 试图解决的*问题*,但它的解决方式并没有完全打动我。在过去的一年半里,每隔 3 个月,我都会尝试使用它几天,真心想把它融入我的工作流程,但最终都放弃了,回到了 git。1(https://lalitm.com/post/git-history/#fn:1) 这就是 `git history` 的用武之地。它是一个**实验性**的命令(https://git-scm.com/docs/git-history),在两个版本中推出:2.54(https://github.com/git/git/blob/master/Documentation/RelNotes/2.54.0.adoc)(4 月,`reword` 和 `split` 子命令)和 2.55(https://github.com/git/git/blob/master/Documentation/RelNotes/2.55.0.adoc)(6 月,`fixup` 子命令)。它在每个发布日都引起了一阵关注,但据我所知,此后社区讨论并不多。这很可惜,因为在我看来,它已经实现了人们为 `jj` 吹捧的多个好处,而无需切换你的整个工作流程。而且酷的是,它是核心 git 发行版的一部分,所以你无需安装任何东西就可以尝试。它有三个子命令:`fixup`、`reword` 和 `split`。 ## fixup `git history fixup`(https://git-scm.com/docs/git-history#Documentation/git-history.txt-fixupcommit)修复一个存在问题的旧提交,然后自动变基*所有*你的分支以匹配。你像往常一样用 `git add` 暂存修复,然后运行 `git history fixup <commit>` 将这些暂存的更改合并到目标提交中。这就像 `git commit --fixup` 加上自动压缩变基,但额外神奇之处在于它*也*会更新任何包含该提交的其他分支。最后这一点比 `git rebase --update-refs` 更进一步,后者只会移动位于你正在主动变基范围内的引用。而 `git history` 会查找并重写*每个*从该提交派生出来的本地分支(同时也有一个选项可以将其限制为仅当前分支)。另一方面,它在存在合并提交的情况下*不*工作,这对于某些 git 用法来说可能会是一个阻碍。 以下是实际工作方式: *之前*,为一个针对 `B` 的修复已完成暂存: 提交 A、B(有 bug)、C(在 feat-1 上)和 D(在 feat-2 上)排成一行,暂存的修复位于顶部提交 D 之上。 *执行 `git history fixup B` 之后*: 修复后:A 不变;B(现已修复)、C 和 D 被重写,具有新的哈希值,分支指针 feat-1 和 feat-2 随之移动。 *`B*` 是 `B` 合并了修复后的结果。重写一个提交会赋予它一个新的哈希值,因此 `C` 和 `D` 会自动在顶部重建为 `C*` 和 `D*`,并且 `feat-1` 和 `feat-2` 分支指针也随之移动。* 最重要的特性,也是所有三个命令共有的,就是它是原子性的:它永远不会让你的仓库处于半损坏状态。它通过拒绝任何可能产生冲突的操作来实现这一点。需要明确的是,这严格来说*不如* `jj` 强大。`jj` 将冲突视为一等公民(https://docs.jj-vcs.dev/latest/conflicts/),因此它可以在变基过程中携带冲突状态,让你稍后解决。`git history` 目前还不能做到这一点,*但是*文档(https://git-scm.com/docs/git-history#_limitations)留下了可能性: > “这个限制是有意设计的,因为历史重写本意不是有状态的操作。一旦(如果)Git 学会了如何处理一等冲突,这个限制就可以被解除。” 所以基本上,这个限制未来可能会改变;很期待看到它是否会发生! ## reword `git history reword`(https://git-scm.com/docs/git-history#Documentation/git-history.txt-rewordcommit)更新一个旧提交的提交信息,并自动变基它上面所有的提交。当你在迭代过程中设计发生变化时,返回去修改提交信息非常有用。 `git history reword <commit>` 会用该提交的现有信息打开你的编辑器。你编辑它,保存,然后栈的其余部分会被重建在上面,分支也随之移动。这与 fixup 完全一样,但针对的是提交信息而不是树内容。因为它只修改信息,`reword`(像后面的 `split` 一样)根本不会触及你的索引或工作树;它纯粹在提交图上操作。所以两者都允许你重写某个你没有检出的分支上的提交,而不会打扰你正在做的事情。 *之前*: 提交 A、B(带有信息 'fix bug')和 C(在 feat-1 上)。 *执行 `git history reword B` 之后*: 重写信息后:A 不变;B 被重写,信息改为 'fix null deref in parser',C 在顶部被重写。 *只有 `B` 的信息改变了,但这仍然赋予了它一个新的哈希值,所以 `C` 在顶部重建为 `C*`,并且 `feat-1` 随之移动。* ## split `git history split`(https://git-scm.com/docs/git-history#Documentation/git-history.txt-splitcommit--pathspec)将一个提交拆分成两个,交互式地选择你关心哪些部分。它相当于 `git add -p`,但无需在 `git rebase` 中做体操。我发现这是三个中最专门化的,但当我需要它时,它就变得无价了。具体来说,`git history split <commit>` 会带你进入一个针对该提交差异的逐块提示。你保留的代码块构成第一个提交,其余部分归入第二个提交。 *之前*,`B` 捆绑了两个不相关的更改: 提交 B 捆绑了两个不相关的更改,位于 A 和 C 之间,在 feat-1 上。 *执行 `git history split B` 之后*: 拆分后:A 不变;B 被拆分为 B1 和 B2,C 在顶部被重写为 C*。 *`B` 变成 `B1` 和 `B2`,并且 `C` 在这两个之上重建为 `C*`。* ## 结论 从使用 `jj` 的人数来看,我确实认为还存在一些我尚未实现的关键思维方式转变。而且需要明确的是,`git history` 并没有完全弥补差距:`jj` 仍然提供了一个带有简易撤销功能的操作日志(https://docs.jj-vcs.dev/latest/operation-log/),将你的工作副本建模为一个提交(https://docs.jj-vcs.dev/latest/working-copy/),并且可以在变基过程中携带冲突(https://docs.jj-vcs.dev/latest/conflicts/),这些都不是这个命令试图做到的。但就目前而言,`git history` 在采用吸引人们转向 `jj` 的许多要素方面迈出了*巨大*的一步,而且它已经存在于我每天使用的工具中了。并且文档的编写方式让我对即将到来的版本中会有更多改进充满希望!

相似文章

Git 2.54 亮点速览

Lobsters Hottest

Git 2.54 带来全新的实验性 `git history` 命令,可在不碰工作区的情况下重写或拆分提交,另有 137 位贡献者带来的其他改进。

git-absorb:自动的git commit --fixup

Lobsters Hottest

git-absorb自动为暂存的更改创建fixup提交,实现了与hg absorb类似的功能。它与git的autosquash集成,简化了应用代码审查反馈的过程,无需手动查找提交的SHA。