关于 Rebase 容易被坑的那些事
Table of Contents
Git 操作是程序员必须掌握的基本技能。刚毕业那会,大家还都使用 svn 作为版本管理工具。
但近些年大家基本都改用 git 了,甚至在我们项目内部 UI 切图都开始使用 git 管理了。
感谢 Linus Torvalds 的伟大发明!
一般情况下,我们常用的命令无非是:
$ git init
$ git checkout
$ git add
$ git pull
$ git push
$ git merge
在说 rebase 前,先重点强调一下 黄金法则:
rebase 作为一个进阶命令,常常与 merge 放在一起比较。
rebase 与 merge #
$ git checkout feature
$ git merge main
// or
$ git merge feature main
将 main 分支合并到 feature:
这样在 feature 分支上会产生一个新的 commit,这是一个 merge commit。
而对于 rebase:
$ git checkout feature
$ git rebase main
可以看到 feature 分支所有 commit 都被放到了 main 前面,整个 history 形成了一条直线。
值得注意的是,feature 的所有 commit 都被重建了,它们的 hash 已经不是原来的那个了!
所以,rebase 相对于 merge 来说,优点是:
- 时间线更漂亮,符号强迫症和整洁癖的喜好
为了做到这个,它对 安全性 (safety) 和 可追溯性 (traceability) 做了折衷1,如果不遵循 rebase 黄金法则,将带来灾难!
rebase 的缺点很明显:
- 不当的使用容易造成严重后果
- 难以追溯历史,比如上面的 feature 分支在 rebase 之后,无法知道是什么时候从 main 切出进行的修改。无法知道什么时候合入的 main 分支。
git pull #
首先,git pull 是两个动作的合并,即 git fetch + git merge FETCH_HEAD
比如 git pull origin master,首先拉取 origin/master,再将本地分支与 origin/master 执行 merge 操作。
如果你本地有一个 commit,但是没有提交到远程;同时你的同事在同一个分支上提交了代码。你进行 fetch 的时候会发现,本地分支既 ahead 又 behind:
- 如果这个时候执行 merge 操作:
Merge made by the ‘recursive’ strategy.
会产生一个 merge 节点!
- 如果执行的是 rebase 呢?
会将本地的 commit 重建,并放到最上面。(可以看到 commit id 不一样了)
然后 push 之后,时间线就成了一条直线!
所以,git pull 这个操作,大部分情况来说,使用 –rebase 更合适!
我们可以设置 git pull 默认使用 rebase 选项:
# 仅设置 master 分支生效
git config branch.master.rebase true
# 对所有 tracking 的 branch 生效
git config branch.autosetuprebase always
# 对所有 pull 操作生效
git config pull.rebase true
以上仅对当前目录的 git 生效,如果要全局生效,记得加上
--global
选项。
# 手动编辑更方便!
git config --global --edit
rebase 的禁忌 #
再次复习一下 黄金法则:
如果你和同事公用了一个 feature 分支,而你使用 rebase 同步主干。很有可能弄丢同事的代码!
我们来看下是怎么出现的:
- 首先我们从 master 切出一个 feature 分支:
这个时候有两个同事同时在这个分支上开发,相安无事。
- 某天,有个同事说,主干上有一些更新,我们要不要同步一下到 feature 分支:
- “好啊,好啊”,那么怎么同步呢?要不要试试新学的
rebase
命令 ?!
3.1. 你的同事一边说“好啊”,一边在自己本地的 feature 上提交了好几次,并 push 到了远程!
你并不知道,这样你本地的 feature 分支并没有完全包含同事的提交,与此同时,你开始了可怕的 rebase 操作:
这时,你发现提交不上去:
- 于是,头脑一热,你决定大力出奇迹,
--force
一把:
这时,你同事更新一下代码,发现!“我的代码怎么没了??!!”
怎么办? #
有办法补救嘛?有!
让你的同事使用 git reflog
:
找到丢失的 commit,通过 git cherry-pick [commit-id]
提交到 feature 分支即可!
但是!我们还是不要随便使用 --force
来制造这种凶险事件了。
看下面这个场景:
前面介绍过,超前是本地分支有三个变更,落后是远程分支有四个变更没有同步过来。
如果我们强行 push 就会丢失远程的 commit,所以我们试一下 --force-with-lease
参数:
没有区别,还是提交上去了!
在这种明知道本地落后,仍然强行提交的情况下,--force-with-lease
的作用与 --force
是一样的!
正常的做法是:
git pull –rebase 更新本地分支
而 git push --force-with-lease
能够解决的是,在 rebase-push
过程中,有其他人提交到该分支时的,这次提交操作会被拒绝。相对来说更安全一点。
所以,总得来说,还是黄金法则:
多人协作分支,同步主干,请使用 merge !!