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

时间:2009-04-29 20:26:36

标签: git version-control git-merge git-rebase

何时建议使用git rebasegit merge

成功变装后我还需要合并吗?

17 个答案:

答案 0 :(得分:1051)

短版

  • Merge接受一个分支中的所有更改,并在一次提交中将它们合并到另一个分支中。
  • Rebase说我想要分支的点移动到新的起点

那你什么时候使用其中任何一个?

合并

  • 假设您已经创建了一个分支,用于开发单个功能。当您想要将这些更改带回master时,您可能需要 merge (您不关心维护所有临时提交)。

衍合

  • 第二种情况是,如果您开始进行一些开发,然后另一位开发人员进行了无关的更改。您可能想要拉动,然后 rebase 以来自repo的当前版本的更改。

答案 1 :(得分:334)

这很简单,你可以说使用另一个分支作为你工作的新基础

如果您有一个分支master,并且您创建了一个分支来实现一个新功能,那么您可以将其命名为cool-feature,当然主分支是您新功能的基础。< / p>

现在,您希望添加在master分支中实现的新功能。您可以切换到master并合并cool-feature分支:

$ git checkout master
$ git merge cool-feature

但是这样就添加了一个新的虚拟提交,如果你想避免意大利面条历史,你可以 rebase

$ git checkout cool-feature
$ git rebase master

然后将其合并到master

$ git checkout master
$ git merge cool-feature

这一次,由于主题分支具有相同的master提交加上具有新功能的提交,因此合并将只是一个快进。

答案 2 :(得分:260)

补充my own answer提到的by TSamper

  • 在合并之前做一个rebase通常是个好主意,因为你的想法是你在你的分支Y中集成你将要合并的分支B的工作。
    但是,再次,在合并之前,您解决 分支中的任何冲突(即:“rebase”,如“从分支B的最近点重播我的分支中的工作”
    如果正确完成,从分支到分支B的后续合并可以快速进行。

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


  在变革之后合并的重点是什么?

在我描述的情况下,我将B重新加入我的分支,只是为了有机会从B的最近一点重播我的工作,但留在我的分支。<登记/> 在这种情况下,仍然需要合并才能将我的“重播”作品带到B

另一种情况(例如described in Git Ready)是将您的工作直接放在B中通过一个rebase(这确实可以保存您所有的好提交,甚至让您有机会重新订购)他们通过一个互动的rebase) 在那种情况下(你在B分支中进行重组),你是对的:不需要进一步合并:

我们尚未合并或重新定位的默认情况下的git树

rebase1

我们通过变基来获得:

rebase3

第二种情况就是:我如何将新功能重新带回主人。

我的观点是,通过描述第一个rebase场景,是为了提醒大家一个rebase也可以作为一个初步步骤(即“让新功能重新成为主人”)。 您可以使用rebase首先将主要“引入”新功能分支:rebase将重放来自HEAD master的新功能提交,但仍然在新功能分支中,有效地移动您的分支起始点旧主人承诺HEAD-master 这允许您解决 分支中的任何冲突(意味着,在隔离的情况下,如果您的冲突解决阶段需要太长时间,则允许master继续并行发展)。 然后,如果要保留new-feature分支中的提交,可以切换到master并将new-feature(或rebase master合并到new-feature

所以:

  • “rebase vs. merge”可以被视为导入作品的两种方式,例如master
  • 但是“rebase then merge”可以是一个有效的工作流程,可以首先独立解决冲突,然后恢复工作。

答案 3 :(得分:171)

TL; DR

如果您有任何疑问,请使用合并。

简答

rebase和merge之间的唯一区别是:

  • 历史记录的结果树结构(通常只在查看提交图时才会显着)是不同的(一个将有分支,另一个会赢得&#39; t。)
  • 合并通常会创建一个额外的提交(例如树中的节点)。
  • 合并和rebase将以不同方式处理冲突。 Rebase将一次提交一个提交的冲突,其中merge将立即显示它们。

所以简短的回答是根据你希望你的历史记录看起来像选择rebase或merge。

长答案

在选择要使用的操作时,您应该考虑几个因素。

您获得的分支是否与团队外的其他开发者共享(例如开源,公开)?

如果是这样,请不要改变。 Rebase会破坏分支,这些开发人员将拥有破坏/不一致的存储库,除非他们使用git pull --rebase。这是快速打乱其他开发人员的好方法。

您的开发团队技术娴熟吗?

Rebase是一种破坏性的操作。这意味着,如果您没有正确应用它,您可能会丢失承诺的工作和/或破坏其他开发人员存储库的一致性。

我曾在团队中工作过,开发人员都来自于公司能够负担得起专职人员来处理分支和合并的时候。那些开发人员对Git不太了解,也不想知道多少。在这些团队中,我不会冒任何理由推荐变基。

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

有些团队使用每个功能分支模型,其中每个分支代表一个功能(或错误修复或子功能等)。在此模型中,分支有助于识别相关提交的集合。例如,可以通过恢复该分支的合并来快速恢复特征(公平地说,这是一种罕见的操作)。或者通过比较两个分支(更常见)来区分特征。 Rebase将破坏分支,这不会是直截了当的。

我还使用过按开发人员分支模式的团队(我们都在那里)。在这种情况下,分支本身不会传达任何其他信息(提交已经有作者)。变基不会有任何伤害。

您是否想以任何理由还原合并?

与恢复合并相比,恢复(如在撤消中)相当困难和/或不可能(如果rebase存在冲突)。如果您认为有可能需要还原,请使用合并。

你在团队工作吗?如果是这样,你愿意在这个分支上采取全有或全无的方法吗?

需要使用相应的git pull --rebase提取Rebase操作。如果您自己工作,您可能能够记住在适当的时候应该使用哪些。如果您在团队中工作,那么协调将非常困难。这就是为什么大多数rebase工作流建议对所有合并使用rebase(并且git pull --rebase用于所有合并)。

常见的神话

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

假设你有以下合并:

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

有些人会说合并&#34;摧毁&#34;提交历史记录,因为如果您只查看主分支(A - D)的日志,您将错过B和C中包含的重要提交消息。

如果这是真的,我们就不会questions like this。基本上,你会看到B和C,除非你明确要求不要看到它们(使用--first-parent)。这很容易为自己尝试。

Rebase允许更安全/更简单的合并

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

Rebase更酷/更性感/更专业

如果您想将rm替换为rm -rf至&#34;请节省时间&#34;那么也许rebase适合你。

我的两分钱

我总是认为有一天我会遇到一个场景,git rebase是解决问题的绝佳工具。就像我想我会遇到一个场景,git reflog是一个很好的工具,可以解决我的问题。我已经和git合作了五年多了。它没有发生。

凌乱的历史对我来说从未真正成为问题。我不会像一部令人兴奋的小说那样阅读我的承诺历史。大多数时候我需要一个历史,我将使用git blame或git bisect。在这种情况下,合并提交对我来说实际上是有用的,因为如果合并引入了对我来说有意义的信息的问题。

更新(4/2017)

虽然我的一般建议仍然存在,但我有义务提及我个人已经软化了使用rebase。我最近与Angular 2 Material项目进行了很多交流。他们使用rebase来保持非常干净的提交历史。这使我能够非常轻松地查看修复给定缺陷的提交内容以及该提交是否包含在发布中。它是正确使用rebase的一个很好的例子。

答案 4 :(得分:170)

这里有很多答案说合并会将所有提交转换为一个,因此建议使用rebase来保留您的提交。 这是不正确的。如果你已经提交了你的提交,那就不好了

合并会删除您的提交。合并保留了历史! (只看gitk)Rebase重写历史记录,这是推送之后的坏事。

只要您已按下,就使用合并 - 而不是重新加入

Here is Linus' (author of git) take on it。这是一个非常好的阅读。或者您可以在下面阅读我自己的相同想法版本。

在master上重新分支:

  • 提供了如何创建提交的错误想法
  • 使用一堆可能未经过良好测试的中间提交来污染master
  • 实际上可以在这些中间提交中引入构建中断,因为在创建原始主题分支和重新引导时之间进行了更改。
  • 让在主人身上找到好位置很难结账。
  • 使提交时间戳与树中的时间顺序不一致。所以你会看到提交A在master中提交B之前,但是提交B首先被创作。 (什么?!)
  • 产生更多冲突,因为主题分支中的各个提交都可能涉及必须单独解决的合并冲突(进一步包含在每次提交中发生的事件的历史记录中)。
  • 是历史的重写。如果被重新分支的分支已被推送到任何地方(与除您之外的任何人共享),那么您已经搞砸了自从您重写历史以来拥有该分支的所有其他人。

相反,将主题分支合并为主分支:

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

答案 5 :(得分:69)

合并意味着:创建一个新的提交,将我的更改合并到目标中。

Rebase意味着:使用我当前的提交集作为提示创建一系列全新的提交。换句话说,如果我已经开始从我重新定位的角度开始制作它们,那么计算我的变化会是什么样子。因此,在rebase之后,您可能需要重新测试您的更改,并且在rebase期间,您可能会遇到一些冲突。

鉴于此,你为什么要坚持?只是为了保持发展历史的清晰。假设您正在使用功能X,当您完成后,您将合并您的更改。目标将现在有一个提交,可以说“添加功能X”。现在,如果您重新定位然后合并,则目标开发历史记录将包含单个逻辑进程中的所有单个提交,而不是合并。这使得稍后审查更改变得更加容易。想象一下,如果50位开发人员一直在合并各种功能,你会发现它有多难以回顾开发历史。

那就是说,如果你已经推动了你正在上游工作的分支,你不应该改变,而是合并。对于尚未被推送到上游的分支,rebase,test和merge。

另一次你可能想要重新定义的是你想要在推送上游之前从你的分支中删除提交。例如:承诺尽早引入一些调试代码,并进一步提交清除代码的其他提交。唯一的方法是执行交互式rebase:git rebase -i <branch/commit/tag>

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

UPDATE2:另一种思考rebase的方法是,它允许从您的开发样式到您承诺的存储库中接受的样式的一种映射。假设你喜欢小而小的块。你有一个提交修复错误,一个提交摆脱未使用的代码,等等。当你完成了你需要做的事情时,你需要进行一系列的提交。现在让我们说你承诺鼓励大量提交的存储库,所以对于你正在做的工作,人们会期望一个或两个提交。你如何处理你的提交并将它们压缩到预期的位置?您将使用交互式rebase并将您的小提交压缩为更少的更大块。如果需要反向,情况也是如此 - 如果你的风格是一些大型提交,但是repo需要长串的小提交。您也可以使用rebase来做到这一点。如果您已合并,则现在已将提交样式移植到主存储库中。如果有很多开发人员,你可以想象在一段时间后跟踪具有几种不同提交样式的历史记录是多么困难。

UPDATE3:Does one still need to merge after a successful rebase?是的,你做到了。原因在于,基础改变主要涉及提交的“转移”。正如我上面所说,这些提交是计算出来的,但是如果从分支开始就有14次提交,那么假设你的rebase没有出错,那么你将会提前14次提交(在你重新调整之后)。 rebase已经完成。在rebase之前你有一个分支。之后你会有一个相同长度的分支。在发布更改之前,您仍需要合并。换句话说,可以根据需要多次使用rebase(同样,只有在没有将更改推送到上游时)。只有在你重新定位后合并。

答案 6 :(得分:60)

在合并/ rebase之前

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的更好说明的信息可以在这里找到:http://excess.org/article/2008/07/ogre-git-tutorial/

答案 7 :(得分:50)

虽然合并绝对是集成更改的最简单和最常用的方式,但它并不是唯一的方法: Rebase 是另一种集成方式。< / p>

了解合并更好

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

  • (1)共同祖先提交如果您遵循项目中两个分支的历史记录,它们总是至少有一个共同的提交:此时,两个分支具有相同的内容,然后以不同的方式进化。
  • (2)+(3)每个分支的端点集成的目标是组合两个分支的当前状态。因此,他们各自的最新修订是特别感兴趣的。 结合这三个提交将导致我们的目标集成。

快进或合并提交

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

enter image description here

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

enter image description here

然而,在很多情况下,两个分支都是单独前进的。 enter image description here

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

enter image description here

人类提交&amp;合并提交

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

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

与Rebase集成

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

enter image description here

让我们一步一步地完成一个rebase操作。该场景与前面的示例相同:我们希望将branch-B中的更改集成到branch-A中,但现在使用rebase。

enter image description here

我们将分三步完成这项工作

  1. git rebase branch-A // syncs 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
  4. 首先,Git会&#34;撤消&#34;所有在分支-A上的提交都发生在行开始分支之后(在共同的祖先提交之后)。但是,当然,它不会丢弃它们:相反,你可以将这些提交视为暂时和#34;

    enter image description here

    接下来,它应用我们想要集成的branch-B的提交。此时,两个分支看起来完全一样。

    enter image description here

    在最后一步中,现在重新应用分支-A上的新提交 - 但是在新位置上,在分支-B的集成提交之上(它们是基于重新提交的)。 结果看起来发展是直线发生的。而不是包含所有组合更改的合并提交,保留了原始提交结构。

    enter image description here

    最后,你得到一个干净的分支 branch-A ,没有不需要的和自动生成的提交。

    注意:取自post的真棒git-towerrebase缺点在同一篇文章中也是一本很好的读物。

答案 8 :(得分:26)

这句话得到了:

  

一般来说,两全其美的方法就是改变当地   你做出的改变,但在你推进之前还没有分享   为了清理你的故事,但永远不要改变你所推动的任何东西   某处。

来源:http://www.git-scm.com/book/en/v2/Git-Branching-Rebasing#Rebase-vs.-Merge

答案 9 :(得分:18)

这个答案广泛针对Git Flow。这些表格是使用精美的ASCII Table Generator生成的,历史记录树使用这个精彩的命令生成(aliasedgit 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

第一点:始终将功能合并到开发中,永远不会从功能中进行重新开发。这是Golden Rule of Rebasing

的结果
  

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

In other words

  

永远不要贬低你推到某处的任何东西。

我个人会补充:除非它是一个功能分支,你和你的团队都知道后果

因此,git merge vs git rebase的问题几乎只适用于要素分支(在以下示例中,合并时始终使用--no-ff)。请注意,由于我不确定是否有一个更好的解决方案(a debate exists),我只会提供两个命令的行为方式。就我而言,我更喜欢使用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选项附加一行“(从提交中挑选出来的樱桃) “对于原始提交邮件正文,因此使用它通常是个好主意 - 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 :)文章中缺少的是you can enable it by default

git config --global pull.rebase true

git rerere

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

答案 10 :(得分:15)

Pro git book是rebasing page的一个非常好的解释。

基本上合并将需要2次提交并将它们组合起来。

rebase将转到2上的共同祖先,并逐步将更改应用于彼此之上。这使得“更清洁”和更线性的历史。

但是当你重新放弃以前放弃之前的提交并创建新的提交时。所以你永远不应该改变一个公开的回购。处理回购的其他人会讨厌你。

仅凭这个原因,我几乎完全合并。 99%的时间我的分支没有那么大的差异,所以如果有冲突,它只会在一两个地方。

答案 11 :(得分:11)

我刚刚用自己的话为团队创建了一个常见问题解答,它回答了这个问题。我分享一下:

什么是merge

提交,将不同分支的所有更改组合到当前中。

什么是rebase

将当前分支的所有提交重新提交到另一个基本提交。

mergerebase之间的主要区别是什么?

  1. merge仅执行一个新提交。 rebase通常执行多次(当前分支中的提交次数)。
  2. merge生成一个 new 生成的提交(所谓的merge-commit)。 rebase仅移动现有个提交。

在哪种情况下我们应该使用merge

只要想将分支分支的更改后退添加到基础分支中,就使用merge

通常,您可以通过单击“拉/合并请求”上的“合并”按钮来执行此操作,例如在GitHub上。

在哪种情况下我们应该使用rebase

只要您想将基础分支的更改添加回分支分支,请使用rebase

通常,只要feature分支发生更改,就在main分支中执行此操作。

为什么不使用merge将更改从基础分支合并到功能分支?

  1. git历史记录将包含许多不必要的合并提交。如果一个功能分支需要多个合并,则该功能分支甚至可能包含比实际提交更多的合并提交!

  2. 这将创建一个循环,该循环破坏了设计Git的心理模型,这会在Git历史记录的任何可视化中引起麻烦。

    想象有一条河(例如“尼罗河”)。水在一个方向(Git历史中的时间方向)流动。时不时地想象那条河有一条支流,并假设其中大部分分支合并回到河中。这就是自然的河流流动的样子。有道理。

    但是,请想象那条河有一条小分支。然后,由于某种原因,河流汇入支流,支流从那里继续。这条河现在已经在技术上消失了,它已经在支流中了。但是随后,以某种方式神奇地将那个分支合并回到了河中。您问哪条河?我不知道。这条河现在实际上应该在支流中,但是不知何故它仍然存在,我可以将支流合并回河中。因此,河在河中。那种没有道理。

    当您将基础分支merge分支到feature分支,然后完成feature分支后,您再次将其合并回基础分支,就会发生这种情况。心理模型已损坏。因此,您最终得到的分支可视化效果不是很好。

使用merge时的示例Git历史记录:

Example Git History when using merge

请注意许多以Merge branch 'develop' into ...开头的提交。如果您重新设置基准,它们甚至不存在(在那里,您将仅具有合并请求合并提交)。还有许多可视分支合并循环(从developfeaturedevelop)。

使用rebase时的示例Git历史记录:

Example Git History when using rebase

更清晰的Git历史记录,合并提交更少,并且没有混乱的可视分支合并循环。

rebase有什么缺点/陷阱吗?

是:

  1. 由于rebase移动了提交(从技术上重新执行了这些提交),所有移动的提交的提交日期都是重新设置基准的时间, git历史记录丢失了初始提交时间 。因此,如果出于某种原因需要提交的确切日期,那么merge是更好的选择。但是通常,干净的git历史记录比确切的提交日期有用得多。
  2. 如果重新创建的分支具有更改同一行的多个提交,并且该行在基础分支中也发生了更改,则可能需要多次解决同一行的合并冲突,而合并时则不需要这样做。因此,平均而言,有更多的合并冲突需要解决。

使用rebase时减少合并冲突的提示:

  1. 经常重新设置。我通常建议每天至少执行一次。
  2. 尝试将同一行上的更改压入一个提交中。

答案 12 :(得分:4)

Git rebase用于使历史记录中的分支路径更清晰,存储库结构是线性的。

它还用于保持您创建的分支的私密性,因为在重新定位并将更改推送到服务器之后,如果删除分支,则不会有您已经处理过的分支的证据。因此,您的分支机构现在是您当地的关注点。

在做了rebase后,我们还摆脱了一个额外的提交,我们常常看看我们是否正常合并。

是的,仍然需要在成功的rebase之后进行合并,因为rebase命令只是将你的工作放在你在rebase中提到的分支的顶部说master并且使你的分支的第一次提交作为master分支的直接后代。这意味着我们现在可以进行快进合并,以便将更改从此分支转移到主分支。

答案 13 :(得分:3)

一些实际的例子,与大规模开发有些联系,其中gerrit用于审查和交付整合。

当我将我的功能分支提升到一个新的远程主控时,我合并了。这提供了最小的提升工作,并且很容易跟踪功能开发的历史,例如gitk。

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature

我准备交付提交时合并。

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master

当我的交付提交由于某种原因导致集成失败时,我会重新定义,我需要将其更新为新的远程主服务器。

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master

答案 14 :(得分:1)

很多时候都解释了什么是基础,什么是合并,但是什么时候使用什么呢?

何时使用变基?

  • 当您尚未推动分支机构/没有其他人正在分支机构上
  • 您想要完整的历史记录
  • 您要避免所有自动生成的“合并的..”提交消息

随着git rebase更改历史记录。因此,当其他人在同一分支上工作时/如果已推送它,则不应使用它。但是,如果您有本地分支机构,则可以在将分支机构合并回master之前进行合并基础变更,以保持更清晰的历史记录。这样做,合并到master分支后,您将不会看到您在master分支中使用了分支-历史记录比较“干净”,因为您没有自动生成的“ merged ..”但仍具有完整的没有自动生成“合并..”提交的主分支中的历史记录。但是请确保使用git merge feature-branch --ff-only来确保在将功能合并回main时创建单个提交时没有冲突。 如果在获取功能分支的历史记录时正在处理的每个任务都使用功能分支,而不是“合并..”提交

,则这很有趣

第二种情况是,如果您从一个分支分支出来,想知道主分支中发生了什么变化。 rebase为您提供了信息,因为它包含每个单次提交。

何时使用合并?

  • 当您推动分支机构/其他机构也在努力
  • 您不需要完整的历史记录
  • 简单地合并对您就足够了

当您不需要或不想在主分支中拥有某个功能分支的所有历史记录时,或者如果其他人正在同一分支上工作/您已将其推入。如果您仍然想要历史记录,只需将master合并到feature分支中,然后再将feature分支合并到master中。这将导致快速合并,您在母版中拥有功能分支的历史记录(包括由于将母版合并到功能分支中而导致的合并提交)。

答案 15 :(得分:1)

如果您只是一个开发人员,则可以使用 rebase 而不是 merge 来获得清晰的历史记录

enter image description here

答案 16 :(得分:-6)

我何时使用git rebase?几乎从来没有,因为它改写了历史。 git merge几乎总是首选,因为它尊重项目中实际发生的事情。