文章目录
git merge
下述的功能需要新版本git支持,笔者实验版本为: git version 2.30.0.windows.2
git merge --ff (-ff可不写, 默认参数fast-forward )
Fast-forward (–ff) 是指 Master 合并 Feature 时候发现 Master 当前节点一直和 Feature 的根节点相同,没有发生改变,那么 Master 快速移动头指针到 Feature 的位置。
所以 Fast-forward 并不会发生真正的合并,只是通过移动指针造成合并的假象,这也体现 git 设计的巧妙之处。
如果merge成功会提示使用的模式是Fast-forward
合并前的分支情况:
这样子可以得到干净线性的git log
合并后的分支指针如下:
git merge --no-ff (non-fast-forward)
如果Master 合并 Feature 时候发现 Master 当前节点和 Feature 的根节点不相同时,则–ff模式会失效,git就会自动使用–no-ff这种合并方式,这种方式会生成一个新的合并节点。
即使Master 合并 Feature 时候发现 Master 当前节点和 Feature 的根节点相同时,如果显式使用–no-ff模式,git仍然会生成一个新的合并节点。
合并前两个分支的log:
将分支ba的修改合并到master分支上,并解决冲突:
使用节点图描述如下:
其中绿色线表示merge过来的节点以及此次合并的生成的一个新的节点
- 所有节点均按提交时间顺序排列。
- 如果需要回退本次的合并,只需回退到merge commit 节点前一个提交到master分支上的节点即可回退本次合并内容,则master分支又会回到合并前的状态。
- 这种情况就无法得到干净且线性的log,多了之后就会造成git log的混乱。
- 由于合并的commit不带有任何修改信息,不便于回溯问题。
- 好处就是可以纪录每次的合并信息。
git merge --ff-only (fast-forward-only )
为了追求干净线性 git 历史记录,则可以使用 git merge --ff-only 方式保持主线模式开发是一种不错的选择。在这种情况下,如果Master 合并 Feature 时候发现 Master 当前节点和 Feature 的根节点不相同时,则git merge会提示失败。
如下图所示:
git merge --squash
在git merge --ff-only失败的时候,这时候可以采用放弃feature分支上commit信息,重新提交一次修改。效果就是相当于在当前分支上做了feature分支上的所有修改。
这样子就可以保持主分支git纪录的干净和线性,但遗憾的是他会丢失这次的合并信息,因此在下次使用git merge时,仍会提示冲突。
git merge 小结
- Fast-forward (–ff) 是指 Master 合并 Feature 时候发现 Master 当前节点一直和 Feature 的根节点相同,没有发生改变,那么 Master 快速移动头指针到 Feature 的位置,不会生成新的合并节点。
- –no-ff (non-fast-forward): 如果Master 合并 Feature 时候发现 Master 当前节点和 Feature 的根节点不相同时,则–ff模式会失效,git就会自动使用–no-ff这种合并方式,这种方式会生成一个新的合并节点。
- –ff-only (fast-forward-only): 如果Master 合并 Feature 时候发现 Master 当前节点和 Feature 的根节点不相同时,则git merge会提示失败
- –squash: 放弃feature分支上所有commit信息,重新提交一次修改。但遗憾的是他会丢失这次的合并信息,因此在下次使用git merge时,仍会提示冲突。
延申一下,git pull的默认行为是git fetch + git merge,因此也能指定上述参数。
git rebase
rebase的含义是改变当前分支branch out的位置,即将当前分支修改的提交均以补丁的形式链在新的branch out根节点位置。
最大的特性就是它可以修改重整提交纪录。git rebase 是一个危险命令,因为它改变了历史,我们应该谨慎使用。使用不当可能会导致历史丢失。
使用场景1: 合并分支(变基)
将master分支合并到ba分支上(此时ba 合并 master, 发现 ba 当前节点和 master 的根节点不相同,如果相同的话,效果相当于git merge --ff):
rebase的操作如下:
-
git 会把 ba 分支里面的每个 commit 取消掉;
-
把上面的操作临时保存成 patch 文件,存在 .git/rebase 目录下;
-
把 ba 分支更新到最新的 master 分支;
-
把上面保存的 patch 文件应用到 ba 分支上;
-
在 rebase 的过程中,也许会出现冲突 conflict。在这种情况,git 会停止 rebase 并会让你去解决冲突。在解决完冲突后,用 git add 命令去更新这些内容(当然也可以使用git mergetool来解决合并冲突)。
注意,你无需执行 git-commit,只要执行 continue
然后git rebase --continue
这样 git 会继续应用余下的 patch 补丁文件链接到节点末端。 -
在任何时候,我们都可以用 --abort 参数来终止 rebase 的行动,并且分支会回到 rebase 开始前的状态。
合并过程的节点图:
-
git rebase可以保持线性干净的提交纪录
-
这时候会打破提交纪录的时间顺序,造成阅读混乱。可以使用git rebase --ignore-date参数进行合并,git则会自动将patch的commit修正为当前的时间。
-
同理,git pull --rebase 则使用git rebase来进行合并。
使用场景2: 合并多次提交纪录
当开发一个特性时,往往会多次提交修改。这时候为了保持干净的log,可以使用rebase合并提交:
第一次进入vi,编辑多次commit的pick or squash
这里一直不太明白的是,git为什么第一个commit信息必须是pick的,不能是squash的。否则会报错error: cannot ‘squash’ without a previous commit。这时候需要我们使用git rebase --edit-todo才能第二次进入vi。
如果没有这个报错的话,git会自动第二次进入vi,并且让我们编辑提交信息,修改完后,git会自动提交,生成一个新的commit id。
在报错error: cannot ‘squash’ without a previous commit的情况下。这时候我们使用git rebase --edit-todo再第一次第二次进入vi的时候,这时候才是我们会选取保留的一次commit纪录,同时可以修改commit信息。然后执行git rebase --continue完成合并
使用习惯
一般来说,在多人协作的开发分支或者发布分支上,我们会要求git的log纪录是线性干净的,方便回溯问题和回滚。
因此当在feature、bugfix或者个人dev分支上开发时,需要追踪主线分支的修改,便于我们开发完之后合并到主线分支的时候是no merge conflict。这时候建议使用 git rebase --ignore-date将 master 合并 到特性分支上(这时候可能会有一个问题,就是远程的分支log和本地的会出现对不上的问题,因为rebase会变基。所以需要强制提交,才能提交成功)。
然后使用 git merge 将特性分支合并到 master 分支上,此时应该是可以fase forward的,所以不会有生成新的合并节点。这时候也可以通过git rebase 合并多个commit,让你的开发分支保持干净简洁。
当需要将合并的主线分支推到服务器上时,由于是多人开发,有可能此时你的本地主线已经落后于服务器的主线分支了。因此需要将远程分支同步到本地分支上。这时候建议使用 git fetch && git rebase --ignore-date origin/master 来保证主线分支log的干净简洁以及时间顺序。
参考
git merge --ff/–no-ff/–ff-only 三种选项参数的区别
彻底搞懂 Git-Rebase
git rebase