Git 之 merge 与 rebase 的区别
首先看下面举例:
- 假定 1-2 是现在的master分支状态;
- 这个时候从 master 分支 checkout 出来一个 Dev01 分支;
- 然后 master 提交了 3、4,Dev01 提交了5、6、7;
- 这个时候 master 分支状态就是 1-2-3-4 ,Dev01 分支状态变成 1-2-5-6-7
- 如果在 Dev01 分支上,用 rebase master,Dev01 分支状态就成了 1-2-3-4-5-6-7
- 如果在 Dev01 分支上,用 merge:
1-2-5-6-7-8
........ |3-4|
7.会出来一个 8,这个 8 的提交就是把 3-4 合进来的提交。
2者区别:
假设当前从 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)。
转自:时间被海绵吃了 https://segmentfault.com/a/1190000012897637
参考
- https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372
- https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA
- https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html
- https://medium.com/@porteneuve/getting-solid-at-git-rebase-vs-merge-4fa1a48c53aa