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

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


当前回答

我刚刚用自己的话为我的团队创建了一个FAQ,以回答这个问题。让我分享一下:

什么是合并?

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

什么是重基?

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

合并和再基础之间的主要区别是什么?

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

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

如果要将分支分支的更改添加回基本分支,请使用merge。

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

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

每当您想将基本分支的更改添加回分支时,请使用rebase。

通常,只要主分支发生变化,就可以在功能分支中执行此操作。

为什么不使用合并将基本分支中的更改合并到要素分支中?

git历史记录将包含许多不必要的合并提交。如果在一个功能分支中需要多个合并,那么该功能分支甚至可能包含比实际提交更多的合并提交!这就产生了一个循环,它破坏了Git所设计的心理模型,这在Git历史的任何可视化中都会带来麻烦。想象有一条河(例如“尼罗河”)。水流向一个方向(Git历史上的时间方向)。有时,想象那条河有一条支流,假设这些支流中的大部分都汇回了这条河。这就是河流的自然流动的样子。这是有道理的。但想象一下,那条河有一条小支流。然后,由于某种原因,河流汇入分支,分支从那里继续。从技术上讲,这条河现在已经消失了,它现在在支流中。但是,不知怎的,这条支流神奇地汇回了河里。你问哪条河?我不知道。这条河现在应该在支流中,但不知何故它仍然存在,我可以将支流合并回河流中。所以,河在河中。有点说不过去。这正是将基本分支合并到要素分支时发生的情况,然后在要素分支完成后,再次将其合并回基本分支。心智模型被打破了。因此,你最终得到了一个没有太大帮助的分支可视化。

使用merge时的Git历史示例:

请注意,许多提交都是从Merge分支“main”开始的。。。。如果你重新创建数据库,它们甚至都不存在(在那里,你只会有拉请求合并提交)。还有许多视觉分支合并循环(主到要素到主)。

使用rebase时的Git历史示例:

Git历史更加清晰,合并提交更少,没有任何杂乱的可视化分支合并循环。

rebase有什么缺点/缺陷吗?

Yes:

因为重基会移动提交(从技术上讲是重新执行),所以所有移动的提交的提交日期将是重基的时间,而git历史记录看起来可能会丢失初始提交时间。因此,如果出于某种原因,所有工具都需要提交的确切日期,那么合并是更好的选择。但通常情况下,干净的git历史要比准确的提交日期有用得多。如果需要,authordate字段将继续保存原始提交日期。如果重基分支有多个更改同一行的提交,并且该行也在基分支中更改,则可能需要多次解决该行的合并冲突,这在合并时是不需要的。因此,平均而言,需要解决的合并冲突更多。

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

经常退款。我通常建议每天至少做一次。尽量将同一行中的更改压缩为一次提交。

其他回答

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

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

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

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

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

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

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

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

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

这个答案广泛围绕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

吉特重读

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

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

例如,如果您有一个分支主节点,您可以创建一个分支来实现一个新特性,并将其命名为酷特性,当然,主分支是新特性的基础。

现在,在某一点上,您希望添加在主分支中实现的新特性。您可以切换到master并合并酷功能分支:

$ git checkout master
$ git merge cool-feature

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

$ git checkout cool-feature
$ git rebase master

然后将其合并到master中:

$ git checkout master
$ git merge cool-feature

这一次,由于主题分支具有与master相同的提交,再加上具有新特性的提交,因此合并将是一个快速前进。

我什么时候使用git rebase?几乎不会,因为它改写了历史。gitmerge几乎总是首选,因为它尊重项目中实际发生的事情。

为了补充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。但“先重新创建基础,然后合并”可以是一个有效的工作流,它可以先孤立地解决冲突,然后再恢复工作。