git rebase 使用
前言
在使用 Git 进行多人协作的过程中,导师曾提起过要学会使用 git rebase
进行代码的推送,因此我也是查找了一些信息,并在此进行简单整理,将 git rebase
的优点及使用场景等信息进行介绍。
merge 和 rebase 的区别
通常我们开发时,会从某个分支拉取一个新分支,如图中所示,在 C2 时期,从 master 分支中拉取一个新分支 experiment,后续 master 分支和 experiment 分支各自也都有了新的提交:
git merge
当我们觉得 experiment 分支的开发结束了,需要合入到 master 分支进行发布,通常会选择切到 master 分支,执行 git merge experiment
,将 experiment 分支 merge 到 master 分支之中,合并后的结果如下:
合并时会生成 C5 节点,C5 节点是 C3 和 C4 的内容合并,并且一般来讲它的 commit 信息都是 Merge branch 'experiment' into 'master'
这种,甚至 master 和 experiment 各自分支上都有很多次提交节点,那么最终合并出来的节点里,会糅合很多很多不同开发者的提交。
对于最终的 master 来讲,前面每个提交节点 commit 信息、提交人很明确、提交内容都很明确,但是做了一次 merge 操作,生成的 C5 节点又把前面一堆节点信息包含了一遍,后续如果对 master 分支做记录回溯或者回滚时,麻烦就大了。
git rebase
其实,还有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做变基。 你可以使用 git rebase
命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。具体什么是 rebase,可以通过下图来理解,在 experiment 分支执行 git rebase master
后,C4 的修改将会变基到 C3 上:
它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。
rebase 后,现在 experiment 和 master 的所有提交节点又成了一个简单的链式结构,在 master 分支里执行 git merge experiment
,此时就是简单的快进合并了:
1 | git checkout master |
还有一种情况,从 experiment 分支又拉取了一个 other 分支,master、experiment、other 三个分支分别进行不同的提交,othe 分支想早于 experiment 分支合入 master,可以使用 git rebase --onto master experiment other
命令,直接将 other 分支变基到 master,之后将 other 合并到 master 里时,也是快进合并,单链表结构。
孰优孰劣
有一种观点认为,仓库的提交历史即是 记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用_谎言_掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。
另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。
变基的风险
使用 rebase 命令必须遵守一条准则:不要对在你的仓库外有副本的分支执行变基!
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用
git rebase
命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
常用场景
拉取远端更新
git pull
=git fetch
+git merge
,拉取到远端分支的更新,merge 到本地分支里,如果本地和远端分别有不同的提交节点,那么 merge 时会生成一个新的合并节点:
git pull --rebase
=git fetch
+git rebase
,拉取到远端分支的更新,采用 rebase 模式,将自己本地分支的提交节点变基到远端分支最新的节点上,保持链式结构。
可以将 git pull 配置成默认 rebase 模式,就不需要每次加 --rebase
了。
日常开发
模式一:
1 | 当前是 develop 分支 |
模式二:不拉取新分支,就在 develop 分支开发,往 develop 分支提交。这个很简单,就是时不时以 rebase 模式更新一下代码,经常保持自己的提交代码指向该分支远端的最新节点:
1 | git checkout develop |
如果基于 develop 切出来一个新分支 develop_feature,并推送到了远端生成一个 origin/develop_feature 分支,想用merge request
进行合入,也是时不时在本地执行一下模式一里那个重复的更新步骤,在本地变基到 develop 分支的最新提交节点上,并 push 到远端 origin/develop_feature 分支上,那么远端的 origin/develop_feature 分支一直是跟 origin/develop 在一条链上,审核人点击同意合入时,也是快进合并,不会生成新的 merge 节点
其他
git rebase
还可以进行多次提交的合一,比如多次提交其实就是对于一个问题的修复,不想之后留下多次修改记录,那么可以git rebase -i
进行 commit 的合并操作。git stash
可以将本地的修改进行暂存,在不想 commit 的情况下进行 merge、rebase、checkout 等操作时,可以先暂存一下,之后再 pop 出来就行。git commit --amend
:当一个 commit 还在本地没 push 上去时,这个命令可以把刚修改的东西追加到最后一次 commit 里,不会生成新的 commit 节点。
参考文章
Git - Pro Git 中文版(第二版)