Git 之 merge 与 rebase 的区别
假设当前从 master 分别切出了两个分支:learn-rebase 和 learn-merge,它俩分别都进行了两次 commit,我们希望把两个分支的内容都合并到 master 上
Merge(合并)
fast-forward
使用 merge 可以合并多个历史记录的流程。
首先我们合并 learn-merge 分支,只看蓝色的部分,learn-merge 分支与 master 分支的最新 commit 在同一条线上,这说明 learn-merge 分支的历史记录包含 master 分支所有的历史记录,那么这个合并是非常简单的。只需要通过把 master 分支的位置移动到 learn-merge 的最新分支上,Git 就会合并。这样的合并被称为 fast-forward(快进)合并。(fast-forward 模式下是不可能出现冲突的)
操作如下:
➜ git branch learn-merge learn-rebase * master ➜ git merge learn-merge Updating 594a6c9..c2b4ae3 Fast-forward myfile.txt | 2 ++ 1 file changed, 2 insertions(+)
结果如下:
非 fast-forward
接下来需要合并 learn-rebase 分支,此时可以看到 master 的最新 commit 已经和 learn-merge 保持一致了,与 learn-rebase 分支不在一条直线上,那此时就不能进行 fast-forward 合并了。
在这种情况下使用 merge 方法,可以合并两个修改并生成一个新的提交。(此时可能会出现冲突的情况,因为 learn-rebase 分支可能与 learn-merge 分支修改同一个文件)
操作如下:
➜ git merge learn-rebase Auto-merging myfile.txt CONFLICT (content): Merge conflict in myfile.txt Automatic merge failed; fix conflicts and then commit the result. ➜ vim myfile.txt # 解决冲突 ➜ git add myfile.txt ➜ git commit -m 'fix conflicts' [master 2a6899b] fix conflicts
结果如下:
设置 non fast-forward
执行合并时,如果设定了non fast-forward 选项,即使在能够 fast-forward 合并的情况下也会生成新的提交并合并。
设置方法:添加 -no-ff
选项
rebase(变基)
与 merge 会保留修改内容的历史记录不同,rebase 是在原有提交的基础上将差异内容反映进去。让我们回到 merge learn-rebase 之前的样子。
rebase 的步骤分为两步,第一步将 learn-rebase 分支的历史记录添加在 master 分支的后面。这个时候可能会有冲突,当出现冲突时,解决冲突后的提交不是使用 commit
命令,而是执行 rebase
命令指定 --continue
选项。若要取消 rebase,指定 --abort
选项。
操作如下:
➜ git checkout learn-rebase Switched to branch 'learn-rebase' ➜ git rebase master First, rewinding head to replay your work on top of it... Applying: add rebase Using index info to reconstruct a base tree... M myfile.txt Falling back to patching base and 3-way merge... Auto-merging myfile.txt CONFLICT (content): Merge conflict in myfile.txt error: Failed to merge in the changes. Patch failed at 0001 add rebase The copy of the patch that failed is found in: .git/rebase-apply/patch When you have resolved this problem, run "git rebase --continue". If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort". 1 merge 与 rebase 的差别 ➜ vim myfile.txt # 解决冲突 ➜ git add myfile.txt ➜ git rebase --continue Applying: add a new line
结果如下:
然后再使用 fast-forward 合并即可
➜ git checkout master Switched to branch 'master' ➜ git merge learn-rebase Updating 0b9e66f..1fc71b9 Fast-forward myfile.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
对比 merge 和 rebase 最终的历史记录,可以发现 merge 保持了修改内容的历史记录,但是历史记录会很复杂;而 rebase 后的历史记录简单,是在原有提交的基础上将差异内容反映进去。
rebase 黄金定律
奇妙的变基也并非完美无缺,要用它得遵守一条准则:
不要对在你的仓库外有副本的分支执行变基。
变基操作的 实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
merge vs rebase
到底该使用 merge 方式开发还是使用 rebase 方法开发是有争议的
有一种观点认为,仓库的提交历史即是 记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用谎言掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。
另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。
总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。
撤销提交的不同
如果使用 merge 进行合并,可以使用 revert 命令对 merge 的内容进行撤销操作(参考 revert),而使用 rebase 则不行(已经没有 merge commit 了),而需要使用 rebase -i
对提交进行重新编辑(参考 交互式 rebase)。