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

成功重新创建数据库后,是否仍需要合并?


当前回答

这个答案广泛围绕GitFlow。这些表是用漂亮的ASCII表生成器生成的,历史树是用这个奇妙的命令(别名为gitlg)生成的:

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 merge和git merge--no ff之间的区别(您通常希望使用git merge--no ff,因为它使您的历史看起来更接近现实):

合并分支

命令:

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--无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合并vs git重基

第一点:总是将特性合并到开发中,而不是从特性重新基础开发。这是回扣黄金法则的结果:

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

换句话说:

永远不要把你推到某个地方的东西重新放在底座上。

我个人会补充一句:除非这是一个功能分支,并且您和您的团队意识到其后果。

因此,git merge与git rebase的问题几乎只适用于功能分支(在下面的示例中,合并时始终不使用ff)。注意,由于我不确定是否有更好的解决方案(存在争论),所以我只提供两个命令的行为方式。在我的例子中,我更喜欢使用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 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重基

命令:

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

从开发到功能分支

合并分支

命令:

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 develop
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重基

命令:

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 develop
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是一个很好的解决方案(-x选项在原始提交消息正文中附加一行“(cherry-picked from 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--重新基数

我不确定我能比德里克·古尔利解释得更好。。。基本上,使用git pull--rebase而不是git pull:)但本文中缺少的是,您可以默认启用它:

git config --global pull.rebase true

吉特重读

再次,这里很好地解释了一下。但简单地说,如果您启用了它,您就不再需要多次解决同一冲突。

其他回答

TL;博士

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

简短回答

重新基准和合并之间的唯一区别是:

生成的历史树结构(通常只有在查看提交图时才明显)不同(一个有分支,另一个没有分支)。合并通常会创建一个额外的提交(例如树中的节点)。合并和重新基础将以不同的方式处理冲突。Rebase将一次提交一个冲突,而merge将一次显示所有冲突。

因此,简单的答案是根据您希望的历史外观选择重新基准或合并。

长答案

在选择要使用的操作时,需要考虑以下几个因素。

您从中获得更改的分支是否与团队之外的其他开发人员共享(例如,开源、公共)?

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

您的开发团队有多熟练?

回扣是一种破坏性操作。这意味着,如果不正确应用它,您可能会丢失已提交的工作和/或破坏其他开发人员存储库的一致性。

我曾在团队中工作过,这些团队中的开发人员都来自一个公司能够负担得起专门的员工来处理分支和合并的时代。这些开发人员对Git了解不多,也不想了解太多。在这些团队中,我不会因为任何原因而冒险建议重新定基。

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

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

我也曾在使用每个开发人员分支模型的团队中工作过(我们都去过)。在这种情况下,分支本身不传递任何附加信息(提交已经包含作者)。换基不会有什么害处。

是否出于任何原因要还原合并?

与恢复合并相比,恢复(如撤消)重基相当困难和/或不可能(如果重基发生冲突)。如果您认为有机会恢复,请使用merge。

你是团队成员吗?如果是的话,你愿意在这一分支机构采取“要么全有要么全无”的方法吗?

需要使用相应的git pull--Rebase来拉动Rebase操作。如果你是自己工作,你可能会记得在适当的时候应该使用哪一个。如果你在一个团队中工作,这将很难协调。这就是为什么大多数rebase工作流建议对所有合并使用rebase(以及git pull--rebase对所有合并)。

常见的神话

合并销毁历史记录(压缩提交)

假设您有以下合并:

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

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

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

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

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

热巴更酷/更性感/更专业

如果您想将rm别名为rm-rf以“节省时间”,那么可能需要重新设置基准。

我的两美分

我一直认为有一天我会遇到这样一个场景,Git rebase是解决这个问题的绝佳工具。就像我认为我会遇到这样一个场景,Git reflog是一个很好的工具,可以解决我的问题。我已经在Git工作了五年多了。这还没有发生。

混乱的历史对我来说从来都不是一个问题。我从来没有像读一本令人兴奋的小说那样阅读我的承诺历史。大多数时候,我需要一段历史,无论如何,我都会使用Git责备或Git平分。在这种情况下,合并提交实际上对我很有用,因为如果合并引入了问题,那对我来说就是有意义的信息。

更新(2017年4月)

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

这句话的意思是:

总的来说,实现两个世界的最佳效果的方法是重新定位本地在推送更改之前,您已进行但尚未共享的更改为了整理你的故事,但千万不要重复你推送的任何内容在某处

来源:3.6 Git分支-重新分类、重新分类与合并

信息图形总是有用的:)

合并:将一个分支重叠到另一个分支上

Assume the following history exists and the current branch is "master":

                     A---B---C topic
                    /
               D---E---F---G master

       Then "git merge topic" will replay the changes made on the topic branch since it
       diverged from master (i.e., E) until its current commit (C) on top of master, and
       record the result in a new commit along with the names of the two parent commits
       and a log message from the user describing the changes.

                     A---B---C topic
                    /         \
               D---E---F---G---H master

Rebase:将一个分支的更改移动到另一个分支末端

Assume the following history exists and the current branch is "topic":

                     A---B---C topic
                    /
               D---E---F---G master

       From this point, the result of either of the following commands:

           git rebase master
           git rebase master topic

       would be:

                             A'--B'--C' topic
                            /
               D---E---F---G master

       NOTE: The latter form is just a short-hand of git checkout topic followed by git
       rebase master. When rebase exits topic will remain the checked-out branch.

因此,我们基本上可以得出结论,合并是一个安全的选项,它可以保存存储库的整个历史,而重基化通过将特性分支移动到main的顶端来创建线性历史。

Credit:帮助页面gitmerge--help和gitrebase--help

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

在合并之前,重新创建数据库通常是一个好主意,因为这样做的目的是在分支Y中集成将要合并的分支B的工作。但同样,在合并之前,您要解决分支中的任何冲突(即:“rebase”,如“从分支B的最近一点开始在分支中回放我的工作”)。如果操作正确,后续从分支到分支B的合并可以快速进行。合并直接影响目标分支B,这意味着合并最好是微不足道的,否则分支B可能很长时间才能恢复到稳定状态(是时候解决所有冲突了)


重新基础后合并的意义?

在我描述的情况下,我将B重新放在我的分支上,只是为了有机会从B的最近一点回放我的工作,但同时留在我的分支。在这种情况下,仍然需要合并才能将我的“回放”工作带到B上。

另一种场景(例如Git Ready中描述的)是通过重基(rebase)将您的工作直接带到B中(这会保留所有漂亮的提交,甚至会给您机会通过交互式重基重新排序)。在这种情况下(当您在B分支中时重新创建基础),您是正确的:不需要进一步合并:

当我们没有合并或重新基础时,默认情况下是Git树

我们通过重新定基获得:

第二个场景的全部内容是:我如何将新功能恢复到主功能。

通过描述第一个重基场景,我的目的是提醒大家,重基也可以作为实现这一目标的初步步骤(即“将新功能重新引入主功能”)。您可以使用rebase首先将master“带入”新特性分支:rebase将重放来自HEAD master的新特性提交,但仍在新特性分支中,有效地将分支起点从旧的master提交移动到HEAD master。这允许您解决分支中的任何冲突(即,隔离,同时如果冲突解决阶段花费太长时间,则允许主分支继续并行发展)。然后,您可以切换到主功能并合并新功能(如果您想保留在新功能分支中完成的提交,则可以将新功能重新放到主功能上)。

So:

“rebase与merge”可以被视为两种方法来导入一个工作,例如master。但“先重新创建基础,然后合并”可以是一个有效的工作流,它可以先孤立地解决冲突,然后再恢复工作。

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

合并不会消除提交。合并保留历史!(看看gitk)热巴改写了历史,这是你推后的坏事。

使用merge,而不是在已经推送的情况下重新设置基址。

这是Linus(Git的作者)对它的看法(现在托管在我自己的博客上,由Wayback Machine恢复)。这真是一本好书。

或者你可以在下面阅读我自己的版本。

在主节点上重新分配分支:

提供了如何创建提交的错误想法用一堆可能没有经过良好测试的中间提交来污染master实际上可能会在这些中间提交上引入构建中断,因为在创建原始主题分支和重新创建主题分支之间对master进行了更改。这使得在master中找到好地方结账变得困难。导致提交的时间戳与其在树中的时间顺序不一致。因此,您将看到提交A在主提交B之前,但提交B是先编写的。(什么?!)产生更多的冲突,因为主题分支中的每个提交都可能涉及合并冲突,这些冲突必须单独解决(关于每个提交中发生的情况,请进一步查看历史)。是对历史的改写。如果正在重新创建的分支被推到任何地方(与除您之外的任何人共享),那么自您改写历史以来,您已经将拥有该分支的所有其他人都搞砸了。

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

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