今天分享Git
分支最后一部分内容:变基,英文名为rebase
。变基也是一种将一个分支合并到另外一个分支上的方法。
7 变基
在介绍变基的相关操作之前,先来回顾一下之前合并操作的逻辑示意图。假设当前项目中共有2个分支:master
和example
分支,并且每个分支上都有对应的提交。在“Git
分支机制与使用(2)”中介绍过,合并两个分支时会采用“三方合并”的方式,即:使用两个分支最新的快照(master
分支的1674
和example
分支的ec25
)以及两者共同的祖先快照(cea4
)进行合并,最后得到一个新的快照。
还有另外一种实现方式:将example
分支提交的更改以补丁的形式提交应用到master
分支上,这个操作就是变基,使用的命令是rebase
。
7.1 变基的基本操作
变基的基本工作原理:
- 找到要整合的分支(一个是当前所在的分支,例如:
example
,另一个是要整合到的分支,例如:master
)的共同祖先 - 获取当前所在分支的从共同祖先到最新快照的每次提交的更改,并把这些更改保存至临时文件;
- 将当前分支重置为要整合到的分支
- 在当前分支上依次引入之前保存的每个更改
使用变基来将example
分支整合到master
分支的操作步骤如下:
- 切换至
example
分支
$ git checkout example
Switched to branch 'example'
- 执行变基操作,将
example
分支内容变基到master
分支上:
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: 增加example.txt
- 返回
master
分支:
$ git checkout master
Switched to branch 'master'
- 执行快进合并:
$ git merge example
Updating ec4324b..24236ec
Fast-forward
example.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 example.txt
到这里呢,整个变基操作就完成了。如果这时查看1674'
快照内容,你会发现它和merge
方法得到的最后的快照的内容是一致的,这就说明了,rebase
和merge
这两种方法的整合结果并没有区别,都是一样的。但是,使用变基得到的提交历史会更清楚一些,这时候的提交历史是一条线,就好像执行的过程都是顺序执行的。
这里对变基和合并两个操作做一个简单的总结:
- 两个操作得到的结果都是一样的,只是这两者的提交历史不一样
- 变基操作是将某条开发分支上的工作在另一个分支上顺序呈现
- 合并操作是将两个分支的最新快照合并到一起得到一个新的快照
7.2 变基的其他操作
在变基操作时,还可以把分支的工作在变基目标分支之外的分支上重现。这就话有点绕口,接下通过一个例子来理解它。
假设,当前项目中有类似的提交结构。项目主线是master
分支,并在某个时间点,基于master
分支创建example
分支;example
分支上有一个提交,然后在此分支的基础上创建test
分支,然后在example
、test
和master
分支上都有对应的提交内容。
现在,决定将test
分支上的内容整合到master
分支上,同时,又不想把没有通过测试的example
分支的内容,这时候简单使用git rebase
肯定是不可以的,因为example
分支和test
分支的共同祖先jkh8
是没有通过测试的。可以使用git rebase
的--onto
选项将test
分支上独有的提交内容在master
分支上重现。
$ git rebase --onto master example test
这条命令的大致意思是:将当前分支切换到test
分支,并找出test
分支和example
分支的共同祖先提交,然后把自从共同祖先以来test
分支上独有的工作在master
分支上重现。
然后,切换至master
分支并进行快进合并操作。
$ git checkout master
$ git merge test
example
分支通过测试后,需要将其变更到master
分支,按照前面的方法,需要先切换到example
分支,然后在将该分支上执行变基操作。Git
对上述操作提供了简单的命令:git rebase [basebranch] [topicbranch]
。如果将example
分支内容整合到master
分支,可使用命令git rebase master example
。变基操作后,执行git checkout master
和git merge example
命令,对master
分支执行快进合并。
上述操作将example
分支和test
分支内容整合到master
分支,这时可使用git branch -d <branch>
命令将不再使用的分支删除掉。
7.3 变基的潜在危害
变基操作虽然有相对于合并操作的优点,但是它也存在一些潜在的危险,总结成一句话:不要对已存在与本地仓库之外的提交执行变基操作,也就是说,不要对已经推送到远程服务器的公开提交执行变基操作。
正如上面提到的,执行变基操作时,其实是抛弃了之前的某些提交,随后创建的新的提交,虽然新提交和原有的提交内容上相似,但是实际上它们是不同的提交。
举个例子,你已经把你的提交推送至远程服务器,然后项目组其他成员拉取了这些提交,并在此基础上开始进行工作;随后,你使用git rebase
命令执行变基操作,改写之前的提交并重新向远程服务器提交;如果这时,你的同事再从服务器上拉取数据,那么他们就不得不重新整合他们的工作。
- 假设你从服务器上拉取代码,并基于此做了一些提交
- 其他同事也同时进行了一些工作,包括一些提交工作,然后提交至远程仓库;你从服务器上拉取最新的内容,然后与你本地的工作进行合并
- 如果之前推送提交的同事决定使用变基操作来替换之前的合并操作,然后使用
git push --force
命令覆盖之前推送到服务器的提交历史,然后你又从服务器上拉取最新的内容
- 然后,你使用
git merge
命令执行快进合并
- 如果使用
git log
命令,可以查看到有两个提交拥有相同的作者、日期和提交信息,让人有些困惑。如果这时候,你再把现有的提交历史推送到服务器,那么就会把变基操作的提交引入到远程服务器,其他同事拉取时,也会感到困惑,因为他们不需要C4
和C6
这两个提交,只需要C4'
这个提交即可。
7.4 只有在需要的时候执行变基操作
在7.3小节中我们了解到了变基操作的相关危害,但是,如果将变基操作看做是在推送数据到远程服务器之前整理和处理提交的一个手段,并且只是对存在于本地仓库的那些还没有提交到远程仓库的提交进行变基操作,那么7.3节中描述的潜在危害就不会出现。反之,如果对以提交到远程仓库的内容进行变基操作,并且其他同事有可能基于这些提交的内容做了自己的开发工作,那么就会遇到很大的麻烦。
如果确实遇到了这些问题,可以请所有同事了解并执行git pull --rebase
命令来减轻变基操作带来的危害,具体原因在后续介绍提交"补丁"时阐述。
7.5 变基操作和合并操作的对比
在介绍变基操作和合并操作的对比之前,我们先来聊聊怎么理解“提交历史”:
- 一种观点认为,
Git
仓库的提交历史就是实际发生的事件的记录,不能随意篡改。从这个角度来说,提交历史不允许更改,因为这些提交应该被完整的记录,以便后来查阅 - 另一种观点认为,
Git
仓库的提交历史是关于项目如何被构建的历史,从这个角度来说,可以更改提交历史,以便能够使得后来者更好地理解项目的构建过程
从上面的两种观点来说,并不能判断合并操作和变基操作哪个更好,通常来说,结合2种操作的优点的操作方式是:对本地尚未推送的更改进行变基操作,从而能够简化提交历史,但决不能对于任何已经推送的提交进行变基操作。