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

Coo*_*coa 1461 git version-control git-merge git-rebase

什么时候建议使用git rebasevs. git merge

成功改造后我还需要合并吗?

Rob*_*rco 1088

精简版

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

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

合并

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

变基

  • 第二种情况是,如果您开始进行一些开发,然后另一位开发人员进行了无关的更改.你可能想拉,然后重订到从回购目前的版本为基础修改.

  • @Rob提到在合并时保持临时提交.我相信默认情况下将分支B(你正在处理的一个功能分支)合并到分支M(主分支)中将为M中的每个提交创建一个提交,因为两个分支是在B中进行的.但是如果使用--squash选项进行合并,则在分支B上进行的所有提交将"集中在一起"并在分支M上作为单个提交合并,从而使主分支上的日志保持良好和干净.如果您有许多开发人员独立工作并合并为主人,那么压缩可能就是您想要的. (98认同)
  • @ spaaarky21看起来我们都是对的.当可以进行快进合并时(如在您的示例中),git将默认包括功能分支B中的所有提交(或者如您所建议的,您可以使用--squash组合成单个提交).但是在你正在合并的有两个不同的分支M和B的情况下,如果合并到M中,git将不包括来自分支B的所有单独提交(无论你是否使用--squash). (21认同)
  • 我相信@ spaaarky21关于合并的假设是不正确的.如果将分支B合并到主M中,则M上将只有一个提交(即使B有多个提交),无论您使用普通还是--squash合并.什么--squash将做的是消除对作为父母的B的引用.这里有一个很好的可视化:http://www.syntevo.com/smartgithg/howtos.html?page = workingflows.merge (17认同)
  • @jpeskin那不是我所看到的.我刚做了一个快速测试来验证.创建一个带有文本文件的目录,`init`一个新的repo,`add`文件和`commit`.签出一个新的功能分支(`checkout -b feature`.)更改文本文件,提交并重复,以便在功能分支上有两个新的提交.然后`checkout master`和`merge feature`.在`log`中,我看到我对master的初始提交,然后是从feature中合并的两个提交.如果你'merge --squash feature`,功能被合并到master但没有提交,所以master上唯一的新提交将是你自己创建的. (13认同)
  • 在这个答案中,为什么"(你不关心维持所有的临时提交)"还在一边?它在09年没有任何意义,现在没有任何意义.此外,如果其他开发人员进行了您需要的*相关更改*,当然如果他们进行了无关的更改,您的功能分支应该轻松合并而不会发生冲突,并且您的历史记录将被维护,那么您肯定只需要重新设置. (4认同)
  • @蒂埃里哈?我关心维护良好的、“真实的”历史。这正是我(几乎)从不使用 rebase 的原因。变基通常会创建代表从未实际存在或从未测试过的代码状态的提交,即无用的历史记录,或者更确切地说,根本不是历史记录。合并提交保留了真正发生的事情以及在上下文中开发的内容。 (2认同)

Ald*_*uca 352

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

如果您有一个分支,master并且您创建了一个分支来实现一个新功能,请说出您的名称cool-feature,当然主分支是您新功能的基础.

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

$ git checkout master
$ git merge cool-feature
Run Code Online (Sandbox Code Playgroud)

但这样一个新的虚拟提交加入,如果你想避免意大利面条的历史,你可以变基:

$ git checkout cool-feature
$ git rebase master
Run Code Online (Sandbox Code Playgroud)

然后将其合并到master:

$ git checkout master
$ git merge cool-feature
Run Code Online (Sandbox Code Playgroud)

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

  • `但是这样就增加了一个新的虚拟提交,如果你想避免意大利面条的历史记录` - 它有多糟糕? (26认同)
  • @Aldo关于重新定位的历史,没有什么"干净"或"整洁".它通常是*肮脏*和恕我直言,因为你不知道真正发生了什么."最干净"的Git历史是实际发生的历史.:) (8认同)
  • 此外,合并的--no-ff标志非常有用. (5认同)
  • 我认为这里需要重复 - 记住所有这些术语(`merge`,`rebase`,`fast-forward`等)指的是有向无环图的特定操作.考虑到心理模型,他们更容易推理. (4认同)
  • @アレックス用户`Sean Schofield`在评论中写道:"Rebase也很好,因为一旦你最终将你的东西合并回主人(这已经描述过很简单),你就会把它放在你提交的"顶部"在几周之后可以编写功能但合并的大型项目中,你不想只是将它们合并到主服务器中,因为它们在历史记录中被"塞进"主流方式.我个人喜欢能够做到git log并在"top"看到最近的功能.请注意保留提交日期 - rebase不会更改该信息." (3认同)
  • @AdrienBe 这是一个非常好的观点,也是我喜欢重新设置基准的原因之一 - 历史看起来更加整洁;) (3认同)

Von*_*onC 264

为了补充对TSamper提到的答案,

  • 在合并之前做一个rebase通常是一个好主意,因为这个想法是你在你的分支中集成你将合并的分支Y的工作B.
    但同样,合并之前,你解决任何冲突分支(即:"变基",如在"重播我的分支我的工作从分支最近的点开始B),
    如果做得正确,后续合并从树枝间B可以快进.

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


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

在我描述的情况下,我重新B加入我的分支,只是为了有机会从最近的一点重播我的工作B,但是在我的分支机构工作期间.
在这种情况下,仍需要合并才能将我的"重播"作品带到B.

另一种情况(例如在Git Ready中描述)是B通过rebase 直接引入你的工作(它确实保存你所有的好提交,甚至让你有机会通过交互式rebase重新排序它们).
在那种情况下(你在B分支中进行重组),你是对的:不需要进一步的合并:

我们没有合并也没有重新定义的默认git树

rebase1

我们通过变基来获得:

rebase3

第二种情况是关于:如何将新功能重新用于主服务器.

我的观点是,通过描述第一个rebase场景,提醒大家一个rebase也可以作为一个初步步骤(即"让新功能重新成为主人").
您可以使用rebase首先将master添加到new-feature分支中:rebase将重放新功能提交HEAD master,但仍然在新功能分支中,有效地将您的分支起点从旧的主提交转移到HEAD-master.
这让你解决任何冲突分支(意思,隔离,同时允许主继续同步发展,如果你的解决冲突的阶段时间过长).
然后,你可以切换到主和合并new-feature(或重订new-featuremaster,如果你想保留你的提交完成的new-feature分支).

所以:

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

  • Rebase也很好,因为一旦你最终将你的东西合并回master(这已经很简单了),你可以将它放在你的提交历史的"顶部".在可以编写功能但几周后合并的大型项目中,您不希望将它们合并到主服务器中,因为它们会在历史记录中被"填充"到主服务器中.就个人而言,我喜欢能够做git日志,并在"顶部"看到最近的功能.请注意,保留提交日期 - rebase不会更改该信息. (27认同)
  • 在rebase是一个微不足道的快进之后合并而不必解决冲突. (17认同)
  • @obelcap:的确,这就是一个想法:你在*your*环境(你的新功能分支中的rebase master)中解决了所有问题冲突,然后是co master,合并新功能:1皮秒(快进)如果主人没有进化 (4认同)
  • @scoarescoare的关键是看看你的本地变化如何兼容*在最新的上游分支的顶部*.如果您的某个提交引入了冲突,您将立即看到它.合并只引入一个(合并)提交,这可能会触发许多冲突,而不会轻易地查看您自己的本地提交中哪一个确实添加了冲突.因此,除了更清晰的历史记录之外,您还可以更准确地了解更改*您*引入,提交提交(由rebase重播),而不是*all*上游分支引入的更改(转储到一个合并). (4认同)
  • @Joe:从心理上讲,你说的是"在我的私人分支中重放我的任何变化(在我的私人分支中独立完成)",但是一旦完成变基,就把我留在我的私人分支中".这是一个清理当地历史,避免"检查站提交",破坏平分和不正确的责备结果的好机会.请参阅"Git工作流程":http://sandofsky.com/blog/git-workflow.html (3认同)
  • @VonC我同意变基会产生更清晰的历史,但我需要帮助理解变基如何允许人们解决功能分支中的冲突更好/不同于简单合并?我还在学习,谢谢. (2认同)

Pac*_*ace 193

TL; DR

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

简答

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

  • 生成的历史树结构(通常只在查看提交图时才会显着)是不同的(一个将具有分支,另一个将不具有).
  • 合并通常会创建一个额外的提交(例如树中的节点).
  • 合并和rebase将以不同方式处理冲突.Rebase将一次提交一个提交的冲突,其中merge将同时呈现它们.

所以简短的回答是根据您希望历史记录的样子选择rebase或merge.

答案很长

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

分支机构是否与团队外部的其他开发人员共享变更(例如开源,公共)?

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

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

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

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

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

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

我还参与了使用每个开发者分支模型的团队(我们都在那里).在这种情况下,分支本身不传达任何其他信息(提交已经有作者).变基不会有任何伤害.

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

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

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

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

常见的神话

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

假设您有以下合并:

    B -- C
   /      \
  A--------D
Run Code Online (Sandbox Code Playgroud)

有些人会说合并"破坏"提交历史记录,因为如果你只查看主分支(A - D)的日志,你会错过B和C中包含的重要提交消息.

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

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

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

Rebase更酷/更性感/更专业

如果你想别名rmrm -rf"节省时间",那么也许rebase适合你.

我的两分钱

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

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

更新(4/2017)

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

  • 我最喜欢这个答案。但是:Rebase不会留下“干净的”历史记录。它使历史更线性,但这根本不是一回事,因为谁知道现在每次提交都隐藏了很多“污垢”?最干净,最清晰的Git历史就是保持分支并提交完整性的历史。 (4认同)
  • “普通的神话,您看到的是B和C”:不一定!实际上,只有在合并是快速合并的情况下,您才看到B和C,并且只有在没有冲突的情况下才可能看到。如果有冲突,您将获得一次提交!但是:您可以先将母版合并到特征中,然后解决冲突,然后再将特征合并到母版中,则将获得提交B和C,并且(第一个)从母版到特征中的(第一个)合并中的单个提交X。 (3认同)
  • 值得一提的是,git 最近更改了它的“git pull”行为,默认包含“--rebase”标志。这意味着对多个开发人员使用的分支进行变基的危险性要小一些。拉动您的更改的人可能会惊讶于在此类操作期间需要解决一些冲突,但不会造成灾难。 (3认同)
  • rebase 的另一个缺点是,在高速 git 存储库中,尤其是在构建时间 > 合并之间的平均时间的情况下,它更难。如果分支不断被合并,您需要不断重新调整基础,直到轮到您为止,如果您还要与要通过的构建进行协调,这可能会很棘手。例如 monorepos rebase 合并到共享分支可能很困难 (3认同)
  • 应该是经过验证的答案. (2认同)

And*_*ott 173

这里有很多答案说合并将你所有的提交变为一个,因此建议使用rebase来保存你的提交.这是不正确的.如果你已经提交了你的提交,这是一个坏主意.

合并并不能抹杀你的提交.合并保留了历史!(只看gitk)Rebase重写了历史,这是你推动它之后的坏事.

使用合并 -只要你已经推动,就不要使用rebase.

这是Linus'(git的作者)对它的看法.这是一个非常好的阅读.或者您可以在下面阅读我自己的相同想法版本.

在master上重新分支:

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

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

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

  • 此外,git merge具有"--no-ff"(无快进)选项,允许您非常轻松地还原某个合并引入的所有更改. (3认同)
  • 只是让它更清楚:你提到"只要你已经推动"的情况 - 这应该是大胆的.链接到Linus帖子很棒,顺便说一句,澄清一下. (3认同)
  • 通常不是,@苏米特.即使已对其中一个或两个分支进行了更改,Git也可以合并任一方向.只有在两个分支上修改相同的代码行(或非常接近)时才会出现冲突.如果在任何团队中经常发生这种情况,团队应该重新考虑他们如何分配工作,因为解决冲突是一种税收并且会减慢它们的速度. (3认同)
  • 但是在通过PR将主题分支合并到master(解决分支而不是master中的冲突)之前,从master更新到主题分支不是最佳实践吗?我们这样做是为了让大多数主题分支最后一次提交“将分支母版合并到主题-...”,但是在这里,这被列为重新定基的“功能”,没有人提及要合并...? (2认同)
  • @AndrewArnott“大多数主题分支应该能够在没有冲突的情况下合并到其目标分支中”当20个开发人员在30个分支上工作时,怎么可能?在您进行工作时会发生合并-因此,您当然必须在创​​建PR之前从目标更新主题分支。 (2认同)
  • @AndrewArnott 如果 master 发生了变化,我认为 Sumit 推荐的额外合并是个好主意。你是对的,无论哪种方式,代码都会以相同的方式输出,但这没有考虑到这样一个事实:如果 master 发生更改,我必须测试我的功能分支是否仍然有效。最简单的方法是将 master 合并到功能分支中,测试并进行任何必要的更改,直到功能分支正确,然后将其合并到 master 中。当然,它可能会生成额外的合并提交,但那又怎样呢? (2认同)

Jee*_*hut 88

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

什么是merge

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

什么是rebase

将当前分支的所有提交重新提交到不同的基础提交上。

merge和之间的主要区别是rebase什么?

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

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

使用merge时要添加一个分支出来的分支的变化到基地分支。

通常,您可以通过单击 Pull/Merge Requests 上的“Merge”按钮(例如在 GitHub 上)来执行此操作。

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

使用rebase时要添加一个基地分支的变化回支出的分支。

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

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

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

  2. 这会创建一个循环,它破坏了 Git 设计的思维模型,这会导致 Git 历史的任何可视化出现问题。

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

    但是再想象一下那条河有一个小支流。然后,由于某种原因,河流汇入分支,分支从那里继续。这条河现在技术上已经消失了,它现在在分支中。但后来,不知何故神奇地,那根树枝又汇回了河里。你问哪条河?我不知道。河流现在实际上应该在分支中,但不知何故它仍然继续存在,我可以将分支合并回河流。所以,河在河中。有点说不通

    这正是当您merge将基本分支转换为一个feature分支,然后当feature分支完成时,您再次将其合并回基本分支时会发生的情况。心智模式被打破了。正因为如此,你最终会得到一个不太有用的分支可视化。

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

使用合并时的示例 Git 历史记录

请注意以Merge branch 'main' into ....开头的许多提交。如果您变基,它们甚至不存在(在那里,您将只有拉取请求合并提交)。还有许多视觉分支合并循环(maininto featureinto main)。

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

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

更清晰的 Git 历史记录,更少的合并提交和任何混乱的视觉分支合并循环。

有什么缺点/陷阱rebase吗?

是的:

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

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

  1. 经常变基。我通常建议每天至少做一次。
  2. 尽量将同一行上的更改压缩为一次提交。

  • 写得很好 竖起大拇指 (2认同)
  • 我会从你的列表中完全删除缺点(2),因为正如你所说,挤压是(2)的完美解决方案,而且它总是有效 (2认同)
  • 所有提交看起来都没有顺序,按时间顺序和可能的分支(已消失)。这一切都是为了“干净的历史”;我只是找不到其中的“历史”部分。 (2认同)

Car*_*arl 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来做到这一点.如果您已合并,则现在已将提交样式移植到主存储库中.如果有很多开发人员,你可以想象在一段时间之后跟踪具有几种不同提交样式的历史记录是多么困难.

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

  • 当您通过简单地使用搜索/过滤命令查看历史记录来获得“整洁的历史记录”的相同优点而没有它的巨大缺点时,它并不真正“依赖”。使变基几乎毫无用处。 (3认同)
  • 与 master 合并可能会导致快进。在功能分支中可能有一些提交,其中有小错误或什至无法编译。如果您只在功能分支中进行单元测试,我会忽略集成中的一些错误。在与 master 合并之前,需要进行集成测试,并且可以显示一些错误。如果这些是固定的,则可以集成该功能。由于您不希望将有缺陷的代码提交给 master,因此似乎需要 rebase 以防止全部提交快进。 (2认同)

guy*_*ush 60

在合并/ rebase之前:

A <- B <- C    [master]
^
 \
  D <- E       [branch]
Run Code Online (Sandbox Code Playgroud)

之后git merge master:

A <- B <- C
^         ^
 \         \
  D <- E <- F
Run Code Online (Sandbox Code Playgroud)

之后git rebase master:

A <- B <- C <- D' <- E'
Run Code Online (Sandbox Code Playgroud)

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

关于git的这个例子和更好的插图信息可以在这里找到:http://excess.org/article/2008/07/ogre-git-tutorial/


Abd*_*han 58

虽然合并绝对是集成更改的最简单和最常用的方式,但它并不是唯一的方法:Rebase是一种替代的集成方式.

理解合并更好一点

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

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

快进或合并提交

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

在此输入图像描述

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

在此输入图像描述

然而,在很多情况下,两个分支都是单独前进的. 在此输入图像描述

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

在此输入图像描述

人类提交和合并提交

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

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

与Rebase集成

有些人喜欢没有这样的自动合并提交.相反,他们希望项目的历史看起来好像是在一条直线上演变而来.没有迹象表明它在某些时候被分成了多个分支.

在此输入图像描述

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

在此输入图像描述

我们将分三步完成

  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

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

在此输入图像描述

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

在此输入图像描述

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

在此输入图像描述

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

注:从真棒摘自通过git-tower.该缺点rebase也是在同一职位一个很好看的.


小智 28

这句话得到了:

一般来说,充分利用这两个世界的方法是重新定义你所做的本地更改,但是在推送它们之前还没有共享以便清理你的故事,但是从不改变任何你推到某个地方的东西.

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


sp0*_*00m 21

这个答案广泛针对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)'
Run Code Online (Sandbox Code Playgroud)

表格按时间倒序排列,与历史树木更加一致.另请参阅git merge和之间的区别git 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"
Run Code Online (Sandbox Code Playgroud)

结果:

* 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
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

结果:

*   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
Run Code Online (Sandbox Code Playgroud)

git merge VS git rebase

第一点:始终将功能合并到开发中,从不根据功能进行重新开发.这是重新黄金法则的结果:

黄金法则git rebase是永远不要在公共分支机构使用它.

换句话说:

永远不要贬低你推到某个地方的东西.

我个人补充说:除非它是一个功能分支,否则你和你的团队都会意识到后果.

所以git mergevs 的问题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"
Run Code Online (Sandbox Code Playgroud)

结果:

*   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
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

结果:

*   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
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

结果:

*   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
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

结果:

*   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
Run Code Online (Sandbox Code Playgroud)

旁注

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"                                                                              
Run Code Online (Sandbox Code Playgroud)

结果:

*   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
Run Code Online (Sandbox Code Playgroud)

git pull --rebase

不确定我能否比Derek Gourlay更好地解释...基本上,使用git pull --rebase而不是git pull:)文章中缺少的是,您可以默认启用它:

git config --global pull.rebase true
Run Code Online (Sandbox Code Playgroud)

git rerere

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


xer*_*ero 15

pro git book是对rebasing页面的一个非常好的解释.

基本上合并将需要2次提交并将它们组合在一起.

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

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

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

  • 合并不合并提交 - 这将重写历史。Rebase 就是这样做的。 (3认同)

yoA*_*ex5 13

  • 一般使用合并
  • 如果您只是一名开发人员,则可以使用rebase 来获得清晰的历史记录。
  • 在共享项目中不要使用变基,因为缓存总和已更改

  • 在某些情况下,您希望对功能分支进行变基以从 master 获取提交。所以也需要考虑这一点。 (2认同)
  • 对于大型项目,进行变基是有用的,以避免在您执行 Pull 请求时将其他人的提交标记为您的提交。 (2认同)

cvi*_*bha 5

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

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

在执行 rebase 之后,我们还摆脱了一个额外的提交,我们过去常常用它来查看是否进行了正常的合并。

是的,在成功 rebase 后仍然需要进行合并,因为 rebase 命令只是将您的工作放在您在 rebase 期间提到的分支之上,例如 master,并使您的分支的第一次提交作为 master 分支的直接后代. 这意味着我们现在可以进行快进合并,将更改从这个分支带到主分支。


Mar*_*n G 5

一些实际示例,在某种程度上与大规模开发有关,其中Gerrit用于审核和交付集成:

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

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature
Run Code Online (Sandbox Code Playgroud)

当我准备交付提交时我会合并。

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master
Run Code Online (Sandbox Code Playgroud)

当我的交付提交由于某种原因失败集成时,我会重新设置基准,并且我需要将其更新为新的远程主控。

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master
Run Code Online (Sandbox Code Playgroud)