The git history command

The git history command

Working with lots of changes in parallel on git can be painful. You end up juggling branches and commits, and running scary rebase -i commands that can leave your tree in a half-broken state if you so much as sneeze. 在 Git 中并行处理大量变更可能会非常痛苦。你最终会陷入在各个分支和提交之间来回切换的窘境,还要运行令人胆战心惊的 rebase -i 命令,稍有不慎就可能让你的代码树处于半损坏状态。

jj, an alternative to git, gets discussed a lot these days (1, 2, 3, 4) and is often pitched as a solution. While I’m very sold on the problems jj is trying to solve, the way it solves them hasn’t quite hit home with me. Every 3 months, for the last 1.5 years, I try it out for a few days, really trying to make it part of my workflow but eventually I give up and go back to git. jj 是 Git 的一个替代品,最近讨论度很高(1, 2, 3, 4),常被作为一种解决方案被推崇。虽然我非常认可 jj 试图解决的问题,但它解决这些问题的方式并没有完全打动我。在过去的一年半里,每隔三个月我都会尝试使用它几天,真心希望能将其融入我的工作流,但最终我还是放弃并回到了 Git。

That’s where git history comes in. It’s an experimental command that arrived across two releases, 2.54 (April, reword and split subcommands) and 2.55 (June, fixup subcommand). It got a flurry of attention on each release day, and then, as far as I can tell, not much community discussion since. Which is a shame, because IMO it already delivers several of the benefits people tout for jj without needing to switch your whole workflow. And the cool thing is that it’s part of the core git distribution, so you can try it without installing anything. 这就是 git history 命令的用武之地。这是一个实验性命令,在 2.54(4 月,引入 rewordsplit 子命令)和 2.55(6 月,引入 fixup 子命令)两个版本中陆续发布。它在每次发布当天都引起了不小的关注,但据我观察,此后社区讨论寥寥。这很可惜,因为在我看来,它已经提供了人们推崇 jj 的几个核心优势,且无需你更换整个工作流。最棒的是,它是 Git 核心发行版的一部分,所以你无需安装任何东西即可尝试。

There are three subcommands: fixup, reword and split. 它包含三个子命令:fixuprewordsplit

fixup

git history fixup fixes an old commit that has something wrong in it, then autorebases all your branches to match. git history fixup 可以修复旧提交中的错误,并自动变基(rebase)所有相关分支以保持同步。

You stage the fix as usual with git add, then run git history fixup <commit> to fold those staged changes into the target commit. It’s like a git commit --fixup plus an autosquash rebase but with the extra magic that it also updates any other branch which contained that commit. 你可以像往常一样使用 git add 暂存修复内容,然后运行 git history fixup <commit> 将这些暂存的变更合并到目标提交中。这类似于 git commit --fixup 加上 autosquash 变基,但它额外具备一个神奇功能:它还会更新所有包含该提交的其他分支。

That last part goes further than git rebase --update-refs, which only moves refs sitting inside the range you’re actively rebasing. git history instead finds and rewrites every local branch descended from the commit (while also having an option to limit it to only the current branch). On the other hand it does not work in the presence of merge commits which, for some usages of git, is going to be a dealbreaker. 最后这一点比 git rebase --update-refs 更进一步,后者仅移动你当前正在变基范围内的引用。而 git history 会查找并重写所有从该提交派生出的本地分支(同时也提供了一个仅限当前分支的选项)。另一方面,它在存在合并提交(merge commits)时无法工作,对于某些 Git 使用场景来说,这可能是个致命缺陷。

The most important property, common to all three commands, is that it’s atomic: it never leaves your tree in a half-broken state. It manages this by refusing any operation that could produce a conflict. 这三个命令共有的最重要特性是原子性:它绝不会让你的代码树处于半损坏状态。它通过拒绝任何可能产生冲突的操作来实现这一点。

To be clear, this is strictly less powerful than jj. jj treats conflicts as first class so it can carry a conflicted state through a rebase and let you sort it out later. git history doesn’t do this yet but the docs leave the door open: 需要明确的是,它的功能确实不如 jj 强大。jj 将冲突视为一等公民,因此它可以在变基过程中携带冲突状态,让你稍后再处理。git history 目前还做不到这一点,但文档中留下了可能性:

“This limitation is by design as history rewrites are not intended to be stateful operations. The limitation can be lifted once (if) Git learns about first-class conflicts.” “这种限制是刻意设计的,因为历史重写不应是状态化操作。一旦(如果)Git 支持了一等冲突,这一限制就可以解除。”

So basically, this limitation may change in the future; excited to see if it does! 所以基本上,这种限制在未来可能会改变;我很期待看到这一天的到来!

reword

git history reword updates the commit message on an old commit and automatically rebases everything on top. This is very useful for going back and fixing commit messages when the design shifts as you iterate. git history reword 可以更新旧提交的提交信息,并自动变基其上的所有内容。当你迭代过程中设计发生变化,需要回头修改提交信息时,这非常有用。

git history reword <commit> opens your editor with that commit’s existing message. You edit it, save, and the rest of the stack is rebuilt on top with the branches following along. It’s exactly like fixup but for commit messages instead of the tree contents. git history reword <commit> 会打开编辑器显示该提交现有的信息。你进行编辑并保存后,堆栈的其余部分会在其上重建,分支也会随之更新。它与 fixup 完全一样,只是针对的是提交信息而非代码内容。

Because it only changes a message, reword (like split later) never touches your index or working tree at all; it works purely on the commit graph. So both let you rewrite a commit on a branch you don’t have checked out without disturbing whatever you’re in the middle of. 因为它只修改信息,reword(以及稍后提到的 split)完全不会触碰你的索引或工作区;它纯粹在提交图上操作。因此,两者都允许你在未检出的分支上重写提交,而不会干扰你当前正在进行的工作。

split

git history split takes one commit and splits it into two, interactively picking what you care about from each. It’s the equivalent of git add -p, but without needing gymnastics with git rebase. I’ve found this to be the most specialized of the three, but invaluable when I need it. git history split 将一个提交拆分为两个,交互式地挑选你关心的内容。它相当于 git add -p,但无需进行复杂的 git rebase 操作。我发现这是三个命令中最专业的一个,但在需要时非常宝贵。

Specifically, git history split <commit> drops you into a hunk-by-hunk prompt over that commit’s diff. The hunks you keep make up the first commit, the rest fall into the second. 具体来说,git history split <commit> 会让你进入一个针对该提交差异的逐块(hunk-by-hunk)提示界面。你保留的块构成第一个提交,其余部分则进入第二个提交。

Conclusion

Judging by how many people are using jj, I do think there’s still some key mental shift which I’m not yet making. And to be clear, git history doesn’t close the full gap: jj still gives you an operation log with easy undo, models your working copy as a commit, and can carry conflicts through a rebase, none of which this is trying to do. 从使用 jj 的人数来看,我认为我确实还有一些关键的思维转变尚未完成。需要明确的是,git history 并不能完全弥补差距:jj 仍然为你提供带有轻松撤销功能的操作日志,将你的工作副本建模为提交,并能在变基过程中携带冲突,而这些都不是 git history 试图解决的问题。

But for now, git history is a big step forward in adopting many of the pieces that attract people to jj, and it’s already in the tool I use every day. And the way the documentation is written makes me hopeful that more improvements will be coming in upcoming releases! 但就目前而言,git history 在采纳吸引人们使用 jj 的诸多特性方面迈出了一大步,而且它已经内置在我每天使用的工具中。文档的编写方式也让我对未来版本中即将到来的更多改进充满希望!