git merge & git rebase & 核心工作流

531 阅读6分钟

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操作
  • 应用场景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 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来污染历史图谱


参考文章: