git merge应该只用于为了保留一个有用的,语义化的准确的历史信息,而希望将一个分支的整个变更集成到另外一个branch时使用。这样形成的清晰版本变更图有着重要的价值。
所有其他的情况都是以不同的方式使用rebase的适合场景:经典型方式,三点式,interactive和cherry-picking.
一个清晰的,有意义的版本变更信息一个GIT用户的非常重要的技能是他们必须能够维护一个清晰的语义化的暴露给大众的变更历史。为了达到这个目的,他们必须依赖于四个主要的工具:
git commit --amend
git merge外加或不外加--no-ff参数
git rebase,特别是git reabase -i和git rebase -p
git cherry-pick(实际上这个命令和rebase是紧密绑定在一起的)
我经常看到人们将merge和rebase都堆放到一个篮子里,这里说明人们存在的普遍的误解:”获取别的branch的commits到我的branch上“。
但是实际上,这两个命令实际上并没有什么共通之处。他们有着完全不同的目的,并且实际上应用他们的原因也是完全不同的!
我将不仅要highlight出来他们各自的role,而且给你足够的知识和最佳实践技能以便你暴露给公众的历史信息不仅是表意的,而且是语义化的(通过查看版本变更历史图就应该能够明显地反映出团队的不同的开发的目的)。而这个组织良好的历史信息对于团队的价值也是明显的:比如有新的团队成员加入,或者过一段时间再回来维护项目,或者对于项目的管理,code review等等。。
什么时候我应该用git merge?正如merge的名字所隐含的意思:merge执行一个合并,或者说一个融合。我们希望在当前分支上往前走,所以我们需要融合合并其他分支的工作,从而放弃其他的分支。
你需要问你自己的问题是:这个其他分支到底代表了什么??
它是不是仅仅是一个local的,临时性的分支,创建它的目的仅仅是为了在开发它的同时又能防止master变得不稳定?如果答案是yes,那么, it is not only useless but downright counter-productive for this branch to remain visible in the history graph, as an identifiable “railroad switch.”
如果merge的目标分支(比如说master分支)在这个分支创建后又往前走了,也就是说master分支(头tip)已经不再是这个临时local分支的直接祖先了,我们会认为我们这个local分支too old了,所以我们往往需要使用git rebase命令来在master的tip上重新运行我们local分支上的commit以便保持一个线性的历史。但是如果master分支在我们创建local分支之后一直没有改变,那么一个fast-forward merge就是足够的了。
结论:如果是对local 私有的临时性质的分支,则直接git rebase -i master(梳理历史信息比如合并成一个commit)+git merge产生一个fast forward,最终以一个commit展示在master分支上; 它是一个well-kown的branch,被团队清晰了解或者仅仅是我的工作schedule来定义需要的?在这种情况下,我们的这个分支可能代表了一个sprint或者说user story的实现过程,或者说代表了我们的一个bug fix过程。
Is is then preferable, perhaps even mandatory, that the entire extent of our branch remain visible in the history graph. This would be the default result if the receiving branch (say master) had moved ahead since we branched out, but if it remained untouched, we will need to prevent Git from using its fast-forward trick. In both these cases, we will always use merge, never rebase.
结论:如果是一个特别活动的跟踪,比如feature分支,bugfix分支那么永远不要使用rebase,而是git merge --no-ff,这样该分支历史永远存续在主分支上 什么时候我应该使用rebase?正如他的名字所隐含的意思:rebase存在的价值是:对一个分支做“变基”操作,这意味着改变这个branch的初始commit(我们知道commits本身组成了一颗树)。它会在新的base上一个一个地运行这个分支上的所有commits.
这通常在当本地的工作(由一些列的commits组成)被认为是在一个过时的base基础上做的工作的时候才需要用它。这可能每天都会有几次这样的场景出现,比如当你试图将local commits push到一个remote时而因为tracking branch(比如说origin/master)过于陈旧而被拒绝时(原因是自从我们上次和origin同步(通过git pull)后别的同事已经做了很多工作并且也push到了origin/master上):这种情况下,如果我们强行将我们的代码push过去将会覆盖我们其他同事的并行工作成果。而这,往往是不允许的,所以push总会给出提示。
一个merge动作(往往pull就会内置执行这个merge动作)在这种情况下并不是很好的应用场景,因为merge会产生一些杂乱的历史遗迹。