git merge
git merge应该只用于为了保留一个有用的,语义化的准确的历史信息,而希望将一个分支的整个变更集成到另外一个branch时使用。这样形成的清晰版本变更图有着重要的价值
merge执行一个合并,或者说是一个融合。我们希望在当前分支上往前走,所以我们需要融合合并其他分支的工作,从而 放弃其他分支
merge 操作中,其他分支,代表了什么 ?
1、问:它是不是仅仅是一个local的,临时性的分支,创建它的目的仅仅是为了在开发它的同时又能防止master变得不稳定?
-
答:是
-
思考:如果让这个分支在历史图表中保持可见,不仅没有用,而且会造成误解,适得其反
-
结论:如果是对local私有的临时性质的分支,则直接
git rebase -i (startCommit, endCommit](git rebase -i master梳理历史信息比如合并成一个commit) +git merge产生一个fast forward,最终以一个commit展示在master分支上 -
注意事项
-
如果merge的目标分支(比如说master分支)在这个分支创建后又往前走了,也就是说master分支(头tip)已经不再是这个临时local分支的直接祖先了,我们会认为这个local分支too old了,所以我们往往需要使用
git rebase命令来在master的tip上重新运行我们local分支上的commit以便保持一个线性的历史 -
但是如果master分支在我们创建local分支之后一直没有改变,那么一个 fast-forward merge 就是足够了
-
2、问:它是一个well-kown的分支,被团队清晰了解或者仅仅是我的工作schedule来定义需要的 ?
-
答: 是
-
思考:在这种情况下,我们的这个分支可能代表了一个需求的实现过程,或者说代表了我们的一个 bug fix 过程。那么最好的操作,甚至是强制性的任务,此分支的整个提交在历史图表中仍然可见,我们必须使用 非fast forward 方式来进行合并
-
结论:如果是一个特别活动的分支,比如 feature 分支,bugfix分支那么永远不要使用 rebase,而是使用
git merge --no-ff,这样分支历史永远存续在主分支上 -
注意事项
-
如果接收分支(比如master分支)在我们分支创建后已经向前移动,merge操作默认采用非快进式
-
如果接收分支(比如master分支)在我们分支创建后保持不变,我们必须强制merge操作采用非快进式模式(
--no-ff)
-
git rebase
rebase 存在的价值是:对一个分支做 "变基" 操作,这意味着改变这个branch的初始commit(我们知道commit本身组成了一棵树)。它会在新的base上一个一个地运行这个分支上所有commits
这通常在当本地的工作(由一系列的commits组成)被认为是在一个过时的base基础上做的工作的时候才需要用它。
-
应用场景1:当你试图将local commits push到一个remote时而因为tracking branch(比如说origin/master)过于陈旧时而被拒绝时(原因是自从我们上次和origin同步(通过git pull)后别的同事已经做了很多工作并且也push到了origin/master上),这种情况下,如果我们强行将我们的代码push过去将会覆盖我们其他同事的并行工作成果。而这,往往是不允许的,所以push总会给出提示。
git pull内部相当于git fetch+git merge操作,针对这种场景,pull操作不是很好的应用方案,因为 merge 会产生一些杂乱的历史痕迹。 -
应用场景2:很久以前你曾经启动过一个并行工作(比如做一些实验),但是一直没有时间就耽搁了下来,现在又有了时间来做这件事情的时候,而这时候你工作的base可能已经非常落后了。当你再次来继续这个工作时,你一定希望你的工作是在一个新的base基础上来进行,以便你可以从已经解决的bugfix或者其他的ready功能中受益
- 如果分支已经被push到remote的话,你必须使用
-f参数来push它,以便你覆盖这个分支的commits历史,这时覆盖这个branch历史也无所谓,因为历史的所有commits都已经相应重新生成了(一个分支的历史由分支的起始commit和头tip commit来描述,还有一点需要注意:一旦我们做一次rebase后,那么这个分支上所有的commit由于这次变基,其commit HASH都会改变)。另外需要注意我们只能对private分支做这个rebase并且git push --force操作
- 如果分支已经被push到remote的话,你必须使用
-
应用场景3:在使用git时,我们通常频繁地向repo中做commit,但是我们的commit本身往往是零散的不连续的。只要是保留在local repo中,为了尊重别人同时也为了自己将来方便,我绝对避免将杂乱的历史信息push到remote上去。在我push之前,我会使用
git rebase -i的命令来清理一下历史。清理分支上的commit,此操作并不会实际的真真实实的变基。- 在实际工作场景中,我们工作的分支已经在远端库中存在(也就是说已经发布),你需要做的是清理自最近一次git pull之后所有的local commits。假设正在一个experiment分支,你的命令可能是这样的:
git rebase -i origin/experiment
- 在实际工作场景中,我们工作的分支已经在远端库中存在(也就是说已经发布),你需要做的是清理自最近一次git pull之后所有的local commits。假设正在一个experiment分支,你的命令可能是这样的:
-
git rebase -i会将所有 patch 的 commits 做 inline处理,即 线性历史记录 -
git rebase -p会保留 local merge ,当操作的commits中有分支合并记录时,使用此命令将更合理
rebase 黄金定律
-
永远不要rebase一个已经分享的分支,也就是说永远不要rebase一个已经在中央库中存在的分支,只能rebase你自己使用的私有分支
-
只要你的分支上需要rebase的所有commits历史还没有被push过,就可以安全地使用 git rebase 来操作
核心工作流原则
1、当需要merge一个临时的本地branch时,确保这个branch不会在版本变更历史图谱中显示,我总是用fast-forward策略来merge这类branch,而这往往需要在merge之前做一个rebase
2、当需要merge一个项目组都知道的local branch时,确保这个branch的信息会在历史图谱中一致显示,我总是执行一个true merge
3、当准备push我的本地工作时,我得首先清理我的本地历史信息以便我总是push一些清晰易读有用的功能
4、当我的push由于和别人已经发布的工作相冲突而拒绝时,我总是rebase更新到最新的remote branch以避免产生一些无意义的micro-merge来污染历史图谱
参考文章: