什么时候使用 Git rebase 而不是 Git merge?

建议何时使用 Git rebase 与 Git merge?

成功改组后是否仍需要合并?

答案

精简版

  • 合并将所有更改合并到一个分支中,并在一次提交中将它们合并到另一个分支中。
  • Rebase 说我希望分支的位置移动到新的起点

那你什么时候用哪一个呢?

合并

  • 假设您为开发单个功能而创建了一个分支。当您希望将这些更改重新掌握时,您可能需要合并 (您不必关心维护所有临时提交)。

变基

  • 第二种情况是,如果您开始进行一些开发,然后另一位开发人员进行了不相关的更改。你可能想拉,然后变基到从资源库中的当前版本为基础修改。

这很简单。使用 rebase,您说要使用另一个分支作为工作的新基础

例如,如果您具有分支master ,则可以创建一个分支来实现新功能,并说您将其命名为cool-feature ,当然 master 分支是新功能的基础。

现在,您要在某个时候添加您在master分支中实现的新功能。您可以切换到master并合并cool-feature分支:

$ git checkout master
$ git merge cool-feature

但是这样就添加了一个新的虚拟提交。如果您想避免意大利面的历史,可以重新设置基准

$ git checkout cool-feature
$ git rebase master

然后将其合并到master

$ git checkout master
$ git merge cool-feature

这次,由于主题分支具有相同的 master 提交以及具有新功能的提交,因此合并将是一个快速的过程。

为了补充TSamper提到 我自己的答案

  • 在合并之前,进行重新存储通常是一个好主意,因为该主意是将分支B的工作集成到分支Y ,您将在分支B上进行合并。
    但同样,合并之前,您解决您的分支任何冲突(即:“重订”,如 “重播我的分支我的工作从分支最近的点开始B )。
    如果正确完成,则从分支到分支B的后续合并可以快速进行。

  • 合并会直接影响目标分支B ,这意味着合并最好是微不足道的,否则分支B可能很长才能恢复到稳定状态(解决所有冲突的时间)


重组后的合并点?

在我所描述的情况下,我将B重新放置到分支上,只是有机会从B的较新点重播我的工作,而又留在了我的分支中。
在这种情况下,仍然需要合并以将我的 “重播” 作品带到B

另一种情况(例如, 在 Git Ready 中进行了描述 )是通过重新部署将您的工作直接带到B (这确实保存了所有不错的提交,甚至使您有机会通过交互式重新部署来重新排序它们)。
在那种情况下(当您在 B 分支中时变基),您是对的:不需要进一步的合并:

我们没有合并也没有重新设置基础时的默认 Git 树

rebase1

我们通过重新定级得到:

rebase3

第二种情况是关于:如何使新功能重新掌握。

通过描述第一个 rebase 方案,我的意思是提醒大家,rebase 也可以用作此步骤的第一步(即 “将新功能重新掌握”)。
您可以使用 rebase 首先将 master 引入 “新功能” 分支中:rebase 将重放HEAD master新功能提交,但仍在HEAD master分支中,有效地将分支起点从旧的 master 提交中移出给HEAD-master
这允许您解决您的分支任何冲突(意味着,在隔离,同时允许主继续同步发展,如果你的解决冲突的阶段时间过长)。
然后,您可以切换到 master 并合并new-feature (如果要保留在new-feature分支中完成的提交,则可以将new-feature new-featuremaster )。

所以:

  • 可以将 “rebase vs. merge” 视为导入工作(例如master两种方式。
  • 但是 “先变基础然后合并” 可以是有效的工作流程,它首先可以隔离解决冲突,然后恢复工作。

TL; DR

如有疑问,请使用合并。

简短答案

重新设置和合并之间的唯一区别是:

  • 历史记录的结果树结构(通常仅在查看提交图时才注意到)是不同的(一个将具有分支,另一个将不具有分支)。
  • 合并通常会创建一个额外的提交(例如,树中的节点)。
  • 合并和变基将以不同方式处理冲突。 Rebase 将一次提交一次冲突,而一次合并将一次提交所有冲突。

因此,简短的答案是根据您希望历史记录的样子选择变基或合并

长答案

选择要使用的操作时,应考虑一些因素。

您正在与其他团队(例如,开源,公共)开发人员共享更改的分支吗?

如果是这样,请不要重新设置基准。 Rebase 破坏了分支,除非使用git pull --rebase否则这些开发人员将有损坏 / 不一致的存储库。这是快速使其他开发人员感到沮丧的好方法。

您的开发团队有多熟练?

变基是一种破坏性操作。这意味着,如果未正确应用它,则可能会丢失已完成的工作和 / 或破坏其他开发人员存储库的一致性。

我曾在团队中工作,开发人员全部来自公司可以聘请专门人员处理分支和合并的时代。这些开发人员对 Git 并不了解很多,也不想了解太多。在这些团队中,我不会因为任何原因而建议重新定级。

分支本身是否代表有用的信息

一些团队使用每个功能的分支模型,其中每个分支代表一个功能(或错误修正或子功能等)。在此模型中,该分支有助于标识相关的提交集。例如,可以通过还原该分支的合并来快速还原功能(公平地说,这是一种罕见的操作)。或通过比较两个分支来比较特征(更常见)。重新设置基准将破坏分支,这将不容易。

我还曾在使用每个开发人员分支模型的团队中工作(我们都去过那里)。在这种情况下,分支本身不会传达任何其他信息(提交已经具有作者)。改基地不会有任何危害。

您可能出于任何原因想要还原合并吗?

与还原合并相比,还原(如撤消)还原非常困难和 / 或不可能(如果还原有冲突)。如果您认为有机会恢复,请使用合并。

您是否在团队中工作?如果是这样,您是否愿意对该分支机构采取全有或全无的方法?

需要使用相应的git pull --rebase来拉回 rebase 操作。如果您是自己工作,则可以记住在适当的时间应该使用哪种工具。如果您在团队中工作,这将很难协调。这就是为什么大多数 rebase 工作流都建议对所有合并使用 rebase(对于所有 pull 都使用git pull --rebase )。

常见的神话

合并破坏历史记录(南瓜提交)

假设您有以下合并:

B -- C
   /      \
  A--------D

有人会指出,合并会 “破坏” 提交历史记录,因为如果仅查看 master 分支(A-D)的日志,则会错过 B 和 C 中包含的重要提交消息。

如果这是真的,我们就不会有这样的问题 。基本上,您将看到 B 和 C,除非您明确要求不看它们(使用 --first-parent)。自己尝试很容易。

变基允许更安全 / 更简单的合并

两种方法的合并方式不同,但是尚不清楚哪种方法总是比另一种更好,并且这可能取决于开发人员的工作流程。例如,如果开发人员倾向于定期提交(例如,他们每天从工作到家中过渡两次),那么给定分支可能会有很多提交。这些提交中的许多看起来可能都不像最终产品(我倾向于对每个功能重构一次或两次)。如果其他人正在处理相关的代码领域,并且他们试图重新整理我的更改,那可能是一个相当繁琐的操作。

Rebase 更酷 / 更性感 / 更专业

如果您想将rm别名为rm -rf来 “节省时间”,那么可能适合您。

我的两分钱

我一直认为,总有一天,我会遇到一个场景,其中 Git rebase 是解决问题的出色工具。就像我认为我会遇到一种情况,其中 Git reflog 是一个很好的工具,可以解决我的问题。我已经与 Git 合作超过五年了。没发生

乱七八糟的历史从来就不是我真正的问题。我从来没有像一部激动人心的小说那样阅读我的提交历史。在大多数情况下,我需要历史记录,无论如何我都会使用 Git 责备或 Git 对分。在那种情况下,进行合并提交实际上对我有用,因为如果合并引起了问题,那对我来说是有意义的信息。

更新(4/2017)

我不得不提一下,尽管我的一般建议仍然成立,但我个人还是对使用 rebase 感到软化。我最近与Angular 2 Material项目进行了很多互动。他们使用 rebase 来保持非常干净的提交历史记录。这使我可以很容易地查看是什么提交修复了给定的缺陷,以及该提交是否包含在发行版中。它是正确使用 rebase 的一个很好的例子。

这里有很多答案说,合并会将您的所有提交变成一个提交,因此建议使用 rebase 保留您的提交。 这是不正确的。如果您已经推送了您的提交,那将是一个坏主意

合并泯你的提交。合并保存历史! (只需查看 gitk)Rebase 会重写历史记录,这在您推送历史记录之后是一件坏事。

使用合并 -在您已经推送时不重新设置基准

这是 Linus(Git 的作者)的看法 。这真是一本好书。或者,您可以在下面阅读我自己的相同想法版本。

在 master 上重新分支:

  • 提供了关于如何创建提交的错误想法
  • 用一堆可能没有经过良好测试的中间提交来污染主机
  • 由于在原始主题分支的创建时间与基础主题重新建立之间进行了更改,因此实际上可以在这些中间提交上引入构建中断。
  • 很难找到主人结帐的好地方。
  • 导致提交的时间戳记与树中的时间顺序不符。因此,您会看到提交 A 在主机中的提交 B 之前,但是提交 B 是第一个编写的。 (什么?!)
  • 产生更多的冲突,因为主题分支中的每个提交都可能涉及必须单独解决的合并冲突(有关每个提交中发生的事件的历史进一步记录)。
  • 是对历史的重写。如果要重设分支的分支已被推送到任何地方(与您以外的任何人共享),则自从您重写历史以来,您就已经搞砸了拥有该分支的其他所有人。

相反,将主题分支合并到 master:

  • 保留有关主题分支创建位置的历史记录,包括从 master 到 topic 分支的任何合并以帮助使其保持最新状态。您确实可以准确地了解开发人员在构建时使用的代码。
  • master 是一个主要由合并组成的分支,每个合并提交通常都是历史上的 “好点”,可以安全地进行检查,因为这是可以集成主题分支的地方。
  • 保留了该主题分支的所有单个提交,包括它们在主题分支中的事实,因此隔离这些更改是很自然的,您可以在需要的地方进行钻取。
  • 合并冲突仅需解决一次(在合并时),因此在主题分支中进行的中间提交更改不必独立解决。
  • 可以顺利完成多次。如果您将主题分支定期集成到母版中,那么人们可以继续建立在该主题分支上,并且可以继续独立合并。

TLDR: 这取决于最重要的部分 - 整洁的历史或对发展顺序的真实表示

如果整洁的历史记录最重要,那么您首先需要重新设置基础,然后合并所做的更改,因此很清楚新代码是什么。 如果您已经推动了分支机构,那么除非可以处理后果,否则不要重新建立基础。

如果最重要的是顺序的真实表示,那么您无需合并即可合并。

合并意味着:创建一个新的提交,将我的更改合并到目标中。 注意:此新提交将具有两个父级 - 您的提交字符串中的最新提交以及您要合并的另一个分支的最新提交。

Rebase 的意思是:使用我当前的提交集作为提示,创建一个全新的提交系列。换句话说,计算一下如果我从我的基础上开始进行更改,我将会看到什么样的更改。因此,在重新设置基准之后,您可能需要重新测试所做的更改,并且在重新基准设置期间,您可能会遇到一些冲突。

鉴于此,您为什么要重新定基?只是为了保持发展历史清晰。假设您正在使用功能 X,并在完成后将更改合并到其中。目标现在将具有一个提交,该提交将按 “添加的功能 X” 的内容进行说明。现在,如果不进行合并,而是先进行基础合并再合并,则目标开发历史记录将在单个逻辑进程中包含所有单个提交。这样一来,以后审查更改就变得容易得多。想象一下,如果 50 名开发人员一直在合并各种功能,那么很难回顾一下开发历史。

也就是说,如果您已经将要处理的分支推送到上游,则不应该重新设置基础,而应该合并。对于尚未推送到上游的分支,请进行基础,测试和合并。

另一个可能需要重新设置基准的时间是,您希望在推向上游之前摆脱分支的提交。例如:提早引入一些调试代码的提交,以及其他进一步清理代码的提交。唯一的方法是执行交互式变基: git rebase -i <branch/commit/tag>

更新:当您使用 Git 连接到不支持非线性历史记录的版本控制系统(例如Subversion )时,您还希望使用 rebase。当使用 git-svn 桥时,非常重要的一点是,合并回 Subversion 的更改是中继的最新更改之上的顺序更改列表。只有两种方法可以做到:(1)手动重新创建更改;(2)使用 rebase 命令,这要快得多。

更新 2:想到变基的另一种方法是,它可以实现从开发样式到要提交的存储库中接受的样式的某种映射。假设您喜欢以很小的小块来提交。您可以进行一次提交来解决打字错误,一次提交可以摆脱未使用的代码,依此类推。当您完成需要做的工作时,您将进行一系列的提交。现在,假设您要提交的存储库鼓励进行较大的提交,因此对于您正在执行的工作,人们可能希望进行一次或两次提交。您如何获取提交字符串并将其压缩到期望的值?您将使用交互式基准,并将您的微小提交压缩为更少的较大块。如果需要反向操作,也是如此 - 如果您的样式是一些大的提交,但是存储库需要一长串的小提交。您也可以使用 rebase 来做到这一点。如果已合并,则现在将提交样式嫁接到主存储库中。如果有很多开发人员,您可以想象一段时间后要遵循几种不同的提交样式的历史记录会多么困难。

更新 3: Does one still need to merge after a successful rebase?是的你是。原因是重新设置本质上涉及提交的 “转移”。就像我上面说的,这些提交是经过计算的,但是如果从分支的角度来看有 14 个提交,那么假设您的 rebase 没有任何问题,那么之后(在重新设置基准之前)将有 14 个提交重新设置完成。在重新设置基准之前,您有一个分支。之后,您将具有相同长度的分支。在发布更改之前,您仍然需要合并。换句话说,根据需要进行多次变基(同样,仅当您尚未将更改推送到上游时)。仅在重新设置基准后才合并。

合并 / 变基之前:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

git merge master

A <- B <- C
^         ^
 \         \
  D <- E <- F

git rebase master

A <- B <- C <- D' <- E'

(A,B,C,D,E 和 F 为提交)

该示例以及有关 Git 的详细说明可以在《 Git 基础教程》中找到

虽然合并无疑是集成更改的最简单,最常见的方法,但它并不是唯一的方法: Rebase是替代的集成方法。

了解合并会更好

当 Git 执行合并时,它将查找三个提交:

  • (1)共同祖先犯。如果您跟踪一个项目中两个分支的历史,则它们总是至少有一个共同的提交:在这个时间点上,两个分支的内容相同,然后演变不同。
  • (2)+(3)每个分支的端点。集成的目标是合并两个分支的当前状态。因此,它们各自的最新版本特别受关注。将这三个提交组合在一起将实现我们所期望的集成。

快进或合并提交

在非常简单的情况下,两个分支之一自分支发生以来就没有任何新提交 - 它的最新提交仍然是共同的祖先。

在此处输入图片说明

在这种情况下,执行集成非常简单:Git 可以将其他分支的所有提交添加到公共祖先提交之上。在 Git 中,这种最简单的集成形式称为 “快进” 合并。然后,两个分支共享完全相同的历史记录。

在此处输入图片说明

但是,在很多情况下,两个分支机构都各自向前发展。

在此处输入图片说明

为了进行集成,Git 必须创建一个包含它们之间差异的新提交 - 合并提交。

在此处输入图片说明

人类承诺与合并承诺

通常,提交是由人类精心创建的。这是一个有意义的单元,它仅包装相关的更改并用注释对其进行注释。

合并提交有点不同:它不是由开发人员创建的,而是由 Git 自动创建的。与其包装一组相关的更改,它的目的是像打结一样连接两个分支。如果以后要了解合并操作,则需要查看两个分支的历史以及相应的提交图。

与 Rebase 集成

有些人更喜欢没有这种自动合并提交。相反,他们希望项目的历史看起来像是沿着一条直线发展的。没有迹象表明它在某个时刻被拆分为多个分支。

在此处输入图片说明

让我们逐步完成一个变基操作。该场景与前面的示例相同:我们希望将分支 B 的更改集成到分支 A,但现在要使用 rebase。

在此处输入图片说明

我们将分三步进行

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

首先,Git 将 “撤消” 分支 A 上行开始分支之后(公共祖先提交之后)发生的所有提交。但是,当然,它不会丢弃它们:相反,您可以将这些提交视为 “暂时保存”。

在此处输入图片说明

接下来,它将应用我们要集成的分支 B 的提交。此时,两个分支看起来完全相同。

在此处输入图片说明

在最后一步中,现在重新应用分支 A 上的新提交 - 但在分支 B 的集成提交之上的新位置上(它们是基于新提交的)。

结果似乎是直线发展了。保留了原来的提交结构,而不是包含所有组合更改的合并提交。

在此处输入图片说明

最后,您将获得一个干净的分支branch-A,其中没有不需要的和自动生成的提交。

注意:摘自git-tower的精彩帖子 。在同一篇文章中也很好地了解了rebase缺点

这句话可以得到:

总的来说,要想做到两全其美,最好的方法是重新整理您所做的但尚未共享的本地更改,然后再推送它们以清理故事,但永远不要重新放置已推送到某处的内容。

资料来源: 3.6 Git 分支 - 变基,变基与合并

这个答案围绕Git Flow 展开 。这些表是使用漂亮的ASCII 表生成器生成的 ,而历史树则使用此妙用命令( 别名git lg )生成:

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

表按时间倒序排列,以便与历史树更加一致。另请参阅git mergegit merge --no-ff之间的区别(您通常希望使用git merge --no-ff因为这会使您的历史看起来更接近现实):

git merge

命令:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

结果:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

命令:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

结果:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge vs git rebase

第一点: 始终将功能合并到开发中,永远不要从功能中重新建立开发基础 。这是重新定购黄金法则的结果

git rebase的黄金法则是永远不要在公共分支上使用它。

换句话说

切勿使您推送到任何地方的内容变基。

我个人要补充: 除非是功能分支,并且您和您的团队都知道后果

因此git merge vs git rebase的问题几乎只适用于功能分支(在以下示例中,合并时始终使用--no-ff )。请注意,由于我不确定是否有更好的解决方案( 存在争论 ),因此我仅提供两种命令的行为方式。就我而言,我更喜欢使用git rebase因为它会产生更好的历史树:)

在要素分支之间

git merge

命令:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

结果:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

命令:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

结果:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

develop到功能分支

git merge

命令:

Time           Branch “develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m “Sixth commit"
15:08                                                                    git merge --no-ff development
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m “Fifth commit"
15:05                                                                    git commit -m “Fourth commit"
15:04                                    git commit -m “Third commit"
15:03                                    git commit -m “Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m “First commit"

结果:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

命令:

Time           Branch “develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m “Sixth commit"
15:08                                                                    git rebase development
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m “Fifth commit"
15:05                                                                    git commit -m “Fourth commit"
15:04                                    git commit -m “Third commit"
15:03                                    git commit -m “Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m “First commit"

结果:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

旁注

git cherry-pick

当您只需要一个特定的提交时, git cherry-pick是一个不错的解决方案( -x选项在原始提交消息正文中附加一行 “ (从 commit 摘下的樱桃...) ” 行,因此通常是个好主意。使用它git log <commit_sha1>来查看它):

命令:

Time           Branch “develop"              Branch "features/foo"                Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m “Sixth commit"
15:07                                                                    git cherry-pick -x <second_commit_sha1>
15:06                                                                    git commit -m “Fifth commit"
15:05                                                                    git commit -m “Fourth commit"
15:04                                    git commit -m “Third commit"
15:03                                    git commit -m “Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m “First commit"

结果:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

我不确定我能否比Derek Gourlay更好地解释它... 基本上,使用git pull --rebase代替git pull :) 虽然本文缺少了,但是您可以默认启用它

git config --global pull.rebase true

git rerere

再次, 在这里很好地解释。但是简单地说,如果启用它,则不必再多次解决相同的冲突。