Git rebase -i 其实没那么可怕
Git rebase -I is not that scary

原始链接: https://cachebag.sh/journal/interactive-rebasing/

许多开发者对 `git rebase -i`(交互式变基)心存畏惧,但它其实是一个强大且安全的工具,可用于整理提交历史。当你运行 `git rebase -i` 时,Git 会打开一个列出你近期提交的文本文件,允许你对它们进行重排、修改说明、压缩或删除。 至关重要的是,交互式变基是一个“计划”而非即时操作。在保存文件之前,一切都不会最终确定,你随时可以通过 `git rebase --abort` 中止操作。此外,变基是安全的,因为它创建的是新提交而非销毁旧提交。如果你操作失误,可以使用 Git 的 `reflog` 追踪并撤销更改,或者在开始前先创建一个备份分支。 虽然偶尔会出现冲突,但在变基过程中解决冲突通常比合并(merge)更容易,因为你可以逐个提交地处理它们。掌握交互式变基后,你就能够保持整洁、专业的提交历史。对于任何开发者来说,这是一项必备技能,只要你在自己的功能分支上负责任地使用,并利用 `git push --force-with-lease` 来确保安全即可。

近期 Hacker News 上一篇题为“Git rebase -i 并没有那么可怕”的讨论引发了关注,社区共识认为交互式变基(interactive rebasing)是开发者必备且强大的工具。参与者强调,掌握变基对于维护整洁且可控的版本历史至关重要。 主要结论包括: * **实用性:** 用户经常依赖变基来执行压缩提交和编辑历史数据等任务。 * **自定义:** 进阶用户的一个常见技巧是创建自定义 Git 别名来简化重复性命令,例如在重置作者日期的同时修正提交。 * **比较:** 尽管像 `jj` (Jujutsu) 这样较新的工具为编辑提交历史提供了更安全的替代方案,但许多开发者仍然偏爱 `git rebase -i` 所提供的直接、基于文本的控制方式。 最终,该讨论帖将 `git rebase` 定义为一项基本技能,一些人将其在版本控制中的作用比作 Excel 的多功能性。共识非常明确:与其畏惧变基,开发者更应将其作为一种获得工作流细粒度控制的方式来拥抱它。
相关文章

原文

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.

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.

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).

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

# 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

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.

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.

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.

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..

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.

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. 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.

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.

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).

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.

联系我们 contact @ memedata.com