git rebase -i is not that scary

git rebase -i is not that scary

git rebase -i 其实并不可怕

One of the most shocking things I’ve encountered in my early career as a junior dev is the fear surrounding git rebase -i. Even amongst some of my co-workers, who are in many ways, magnitudes smarter than I am, it seems to be a pattern in a lot of devs to be afraid of this command. 在我初入职场担任初级开发人员时,最让我震惊的事情之一就是大家对 git rebase -i 的恐惧。即使是在我的一些同事中——他们在许多方面都比我聪明得多——很多开发者似乎都对这个命令心存畏惧。

What it actually does when you run: git rebase -i HEAD~4 git does something delightfully mundane: it opens a text file. If you are working out of the terminal and haven’t fiddled around with your git settings, git config --global core.editor "_YOUR-EDITOR_" is a good way to allay your fears of conquering vim. 当你运行 git rebase -i HEAD~4 时,Git 其实只是做了一件非常平凡的事情:它打开了一个文本文件。如果你是在终端中工作,且还没有折腾过 Git 设置,那么使用 git config --global core.editor "_YOUR-EDITOR_" 是一个消除你对征服 Vim 恐惧的好方法。

pick a1b2c3d Add user model
pick e4f5g6h Fix typo in user model
pick i7j8k9l Add login endpoint
pick m0n1o2p WIP debugging login

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash" but discard this commit's message
# d, drop = remove commit

A worthy thing to note here for new rebasers is that this is a plan, not an action. Nothing has technically happened yet, aside from the fact a rebase has begun. If you change your mind, you just run git rebase --abort and either restart or give up (ideally the former). 对于刚接触 rebase 的人来说,值得注意的一点是:这是一个“计划”,而不是“动作”。除了 rebase 已经开始之外,实际上什么都还没发生。如果你改变了主意,只需运行 git rebase --abort,然后选择重新开始或放弃(最好是前者)。

Each line is an instruction, in which a replaying of the commit you target is commenced. You may mix and match these instructions to your liking. 每一行都是一条指令,它会启动对目标提交的重放。你可以根据自己的喜好混合搭配这些指令。

r a1b2c3d Add user model
pick e4f5g6h Fix typo in user model
pick i7j8k9l Add login endpoint
d m0n1o2p WIP debugging login

What happened here:

  1. In this example, git is going to ‘pause’ on the first commit, allowing you to reword its message.
  2. d or a drop deletes the WIP debugging commit entirely. This same result can be achieved by simply deleting the line entirely. The middle two commits were untouched, despite us mutating the surrounding commits. The end result is 3 total commits instead of 4. 这里发生了什么:
  3. 在这个例子中,Git 会在第一个提交处“暂停”,允许你修改提交信息。
  4. ddrop 会彻底删除那个 WIP 调试提交。直接删除该行也能达到同样的效果。 尽管我们修改了周围的提交,但中间的两个提交保持不变。最终结果是总共 3 个提交,而不是 4 个。

Honestly, that’s it. It’s not a big fucking deal. If you can internalize what that example does, interactive rebasing becomes a tool that will make your life easier, not harder. 老实说,就是这样。这根本没什么大不了的。如果你能内化这个例子的操作,交互式变基(interactive rebasing)就会成为让你生活更轻松,而不是更困难的工具。

There’s a common thing among people as well that love to undermine the purpose of interactive rebasing. Without getting into those arguments, I will just openly say: in my opinion, if you don’t care about a clean branch history (a tiny fraction of the improvement rebasing provides) than I am more inclined to be skeptical of how serious you are about your software. 人们中有一种常见的倾向,喜欢贬低交互式变基的作用。我不打算卷入那些争论,我只想直白地说:在我看来,如果你不在乎整洁的分支历史(这只是变基所带来的改进中微不足道的一部分),那么我更倾向于怀疑你对软件开发的认真程度。

“But what if I mess it up?” It is very hard to actually lose work here, for three reasons: “但如果我搞砸了怎么办?”在这里实际上很难丢失工作,原因有三:

  1. You can bail out at any time. As aforementioned with git rebase --abort your branch snaps back to exactly where it was before you started.

  2. 你可以随时退出。 如前所述,使用 git rebase --abort,你的分支会立刻恢复到开始之前的状态。

  3. Rebase doesn’t destroy commits — it makes new ones. This is the key mental model. Rebase doesn’t edit your old commits; it creates new ones and just moves your branch pointer to them. The old commits still exist in git’s object database, unreferenced but intact, for some amount of time before garbage collections comes in.

  4. Rebase 不会销毁提交——它会创建新的提交。 这是关键的心智模型。Rebase 不会编辑你的旧提交;它会创建新提交,并将分支指针移动到新提交上。旧的提交仍然存在于 Git 的对象数据库中,虽然不再被引用,但在垃圾回收机制介入之前,它们是完好无损的。

  5. The reflog remembers everything. This was especially important for me to understand when I wiped one of my co-workers branches history in my first internship thinking they wanted to drop previous commits they had made. Git keeps a journal of everywhere your branch has pointed: git reflog. Find the entry from before the rebase and restore it: git reset --hard HEAD@{4}. The entire rebase is undone.

  6. Reflog 记录了一切。 这对我来说非常重要,因为在第一次实习时,我曾以为同事想要删除之前的提交,结果误删了他们分支的历史记录。Git 会记录你分支指向过的每一个位置:git reflog。找到 rebase 之前的条目并恢复它:git reset --hard HEAD@{4}。整个 rebase 操作就被撤销了。

This safety net is always there, which means the worst realistic outcome of a botched rebase is a few minutes of you perusing through the reflog. And if even the reflog feels too daunting (though if you’ve made it this far into my post, I would hope you are competent enough to handle it), there’s of course, the low-tech insurance policy: git branch backup-before-rebase. Now the pre-rebase state has a name. If anything goes wrong, git reset --hard backup-before-rebase and you’re home safe. 这个安全网一直都在,这意味着一次失败的 rebase 最坏的结果,也不过是你花几分钟查看 reflog。如果连 reflog 都让你觉得望而生畏(虽然如果你能读到这里,我希望你已经有足够的能力处理它了),当然还有一种低科技的保险策略:git branch backup-before-rebase。现在 rebase 前的状态有了名字。如果出了问题,执行 git reset --hard backup-before-rebase,你就安全了。

Conflicts

冲突

Sometimes a replayed commit conflicts with an earlier change (usually when reordering, or when rebasing onto an updated main). Git stops and literally prints the instructions: resolve the conflict like you would a merge conflict, git add the files, then git rebase --continue. I think that this is probably what annoys people the most. But conflicts are not a new concept to git. In fact I would argue that in the context of interactive rebasing, conflicts are actually easier to resolve than in a merge. This is because you are only dealing with one commit at a time, and not the entire branch. 有时,重放的提交会与之前的更改发生冲突(通常是在重新排序或变基到更新后的 main 分支时)。Git 会停止并直接打印指令:像处理合并冲突一样解决冲突,执行 git add 文件,然后运行 git rebase --continue。我认为这可能是最让人烦恼的地方。但冲突对 Git 来说并不是什么新概念。事实上,我认为在交互式变基的语境下,冲突比合并时更容易解决。这是因为你一次只处理一个提交,而不是整个分支。

Obligatory warning

必要警告

As with literally anything in software, you should take great care and effort to understand what you are doing, especially with something destructive like rebasing. Yes, work can be recovered but that does not give you an excuse to be sloppy. A nice general rule of thumb I follow is allowing myself to rebase my own feature branches freely, before or during review. Pushing the result to your own remote branch with git push --force-with-lease is normal and fine (--force-with-lease refuses to push if someone else has pushed in the meantime, always prefer it over plain --force). 正如软件开发中的任何事情一样,你应该非常小心并努力理解你在做什么,尤其是像 rebase 这样具有破坏性的操作。是的,工作可以恢复,但这并不能成为你粗心大意的借口。我遵循的一个很好的经验法则是:允许自己在评审前或评审期间自由地 rebase 自己的功能分支。使用 git push --force-with-lease 将结果推送到你自己的远程分支是正常且没问题的(--force-with-lease 会在其他人在此期间推送了代码时拒绝推送,始终优先使用它,而不是普通的 --force)。

The takeaway

总结

I personally think that any developer worth their salt should at the least be able to walk through a very simple interactive rebase. This post is not really about when or why you should use it (though I am of the belief that it should be used liberally) it’s mostly to demystify the process and encourage you to try it out. Especially if you are a junior/beginner dev. 我个人认为,任何称职的开发者至少都应该能够完成一次非常简单的交互式变基。这篇文章并不是为了讨论你应该在何时或为何使用它(尽管我相信它应该被广泛使用),主要是为了揭开这个过程的神秘面纱,并鼓励你去尝试。特别是如果你是一名初级/入门开发者。