将 develop 合并为 master 和 master 合并为 develop 之间的区别?
Difference between merging develop into master and master into develop?
合并 develop 为 master 和 master 合并为 develop 的区别?我尝试将 develop 合并到 master 中,它给了我 34 个文件和 1,312 个添加和 324 个删除,我尝试将 master 合并到 develop 中,它给了我 251 个文件和 87,123 个添加和 1,321 个删除。我的猜测是它需要时间从 master 分离出来然后进行所有更改并将其与我们要合并到的分支中的文件中更改的文件进行比较?我说得对吗?
这意味着要使两个分支相同,我们需要将 master 合并到 develop 中,然后在两个分支每天更改 1 个月 + 十几个开发人员时每次将 develop 合并到 master ?
git-diff 给我们带来了什么?它是否给出了两个分支的所有差异,或者如果我们尝试将分支 1 合并到分支 2 中会得到什么?
为了理解这个问题的答案,让我们从一些关于 Git 的事实开始:
Git 存储 提交 ,而不是文件或分支。提交 是 存储库中的历史记录:每个提交都有一个唯一编号(哈希 ID 或 object ID aka OID),每个都包含两件事:每个文件的完整快照,以及一些元数据。任何一次提交中的元数据都包含一个先前提交哈希 ID 的列表,它可以让 Git 将后来的提交与先前的提交联系起来。大多数(所有“普通”)提交中只有一个先前的哈希 ID,它将提交链接到它的 parent。这允许 Git 向后工作,从最近的提交到最早的提交。
分支名称如master
或main
、develop
、br1
、feature/tall
等,只包含one 提交哈希 ID。根据定义,名称中存储的任何哈希 ID 都是该分支上的 最新 提交。
仅从这两个事实我们就可以开始可视化提交:
... <-F <-G <-H <--br1
这里我们有一个像 br1
这样的分支名称选择,或者 指向 ,last 提交那个分支。该提交有一些哈希 ID,我们将其称为 H
,这样我们就不必生成一些随机的东西并尝试记住它。
提交 H
包含所有文件的快照,但也包含元数据以说明谁 提交 提交 H
,原因(他们的日志消息),等等。提交 H
的元数据存储了之前一次提交的哈希 ID,我们将其称为 G
。所以提交 H
指向更早的提交 G
.
提交 G
,作为提交,存储完整快照和元数据。 G
中的元数据使 G
向后指向更早的提交 F
,这也是一个提交,因此它向后指向另一个更早的提交。
当我们查看带有git diff
或git show
的提交时,我们实际上给了Git两个 提交哈希 ID。我们从提交本身开始,例如 H
,也许使用分支名称 br1
:
git show br1
Git 使用它来定位 H
,然后使用 H
定位它的 parent 提交 G
。 Git 然后将 两个 快照提取到内存中的一个临时区域,并比较它们。毕竟,我们只对更改 的文件感兴趣。 (这得益于提交快照 de-duplicate 文件内容,因此如果 H
和 G
主要共享大部分文件,Git 可以立即分辨出来,甚至不必费心提取这些文件。)
对于 确实 更改的文件,Git 计算出一个“更改方法”——一个 diff——并显示作为“发生了什么”。这对于像提交 H
这样的普通提交非常有效。但它因合并而崩溃。
合并
为了理解git merge
,我们从合并的目标开始:合并工作。让我们画一张有某种分叉的提交的图片,这样我们就有了两个不同的工作链,像这样:
I--J <-- br1
/
...--G--H
\
K--L <-- br2
也就是说,此时有两个“最新”提交。提交 J
是最新的;它的parent是I
,它的parent是H
,它的parent是G
等等。但是提交 L
是 也是 最新的;它的parent是K
,它的parent是H
,它的parent是G
等等。这里 J
是最新的 br1
commit 而 L
是最新的 br2
commit .
一如既往,每次提交都包含所有文件的完整快照。要在两个分支上合并 work,我们需要找到 changes。我们该怎么做?好吧,我们已经知道答案了:我们可以使用 git diff
或 git show
来选择两个提交并 比较 它们。
每个人都首先尝试的是选择提交 J
和 L
并比较它们,但这是行不通的。但这显示了这两个“最新”之间的不同之处,这通常不是我们想要的。例如,Alice 可能通过修复 README 中的拼写错误并添加功能 1 来实现 br1
,而 Bob 通过修复其他文档中的不同拼写错误并添加功能 2 来实现 br2
。如果我们 diff J
vs L
,Git 给我们的方法是: 删除 Alice 的修复和特征,并添加 Bob 的修复和特征。我们想要的是添加Alice的修复和功能,以及Bob的修复和功能。
切入正题,这里的技巧是从提交 H
开始。那是最好的 shared 提交:实际上是在两个分支上的提交。通过从 J
开始并向后工作以及 也 从 L
开始并向后工作,我们发现 H
是最好的 shared 提交。所以我们 diff H
vs J
看看 Alice 做了什么,然后——分别地!——diff H
vs L
看看 Bob 做了什么。 那 为我们提供了要组合的更改集。
Git 将尽最大努力组合这些 change-recipes,使用一些非常简单的规则:
- 如果没有人动过一个文件,使用它的任何版本:三个都是一样的。
- 如果一个分支涉及某个文件而另一个分支根本不涉及它,则使用更改它的一个分支中更改的文件。
- 如果两者都涉及某个文件,请尝试合并更改,line-by-line。如果它们在 不同的 non-overlapping 行上,则可以将它们合并。 Git 添加了行也不能 abut 的规则。如果它们确实重叠,则它们必须 100% 相同。否则,Git 将声明一个 合并冲突 并让你,程序员,收拾烂摊子。
这些规则出人意料地经常起作用,因此 git merge
可以从两个分支中获取两组更改,将 combined 更改应用到 [= 中的快照42=],并将其用于新快照。根据您喜欢的查看方式,结果是我们在添加 br2
更改的同时保留 br1
更改,或者在添加 br1
的同时保留 br2
更改变化。请注意,与普通数学加法一样,无论 addends (that is, we don't need to define a separate "augend" vs "addend" because the operation is commutative).1
的顺序如何,结果都是相同的
为新提交创建了一个快照,Git然后创建新提交。您提供一条日志消息,在其中解释为什么要进行合并——或者您使用蹩脚的默认消息,例如 merge branch br2
,这是大多数人真正做的——然后 Git 进行新的提交就像任何提交一样:它有一个快照和元数据。新提交的特别之处在于,它不仅有 一个 parent,还有 两个:
I--J
/ \
...--G--H M
\ /
K--L
请注意,我已将此图片上的分支名称存档。每当您进行 any 新提交时——无论是 git commit
、git merge
、git cherry-pick
或 git revert
或其他什么——Git 将自动为您更新 当前分支名称 ,因此 M
现在是最新的提交。但是哪个分支 name 得到了更新?嗯,这取决于哪个分支名称 git status
说你是 on
:
$ git status
On branch `br1`
...
$ git merge br2
结果:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
也就是说,你“在”br1
——这就是附加到 br1
的 HEAD
在这里的意思——你仍然在,所以新的提交也是最新的对于 br1
。但是如果 git status
说你是 On branch br2
而你有 运行 git merge br1
,那么更新的就是名字 br2
。
1注意在git merge
中加入options,如-s ours
或-X theirs
例如,更改此操作:操作不再是可交换的。
这回答了你问题的第一部分
[What is the d]ifference between merging develop into master and master into develop?
一个推进名字master
,一个推进名字develop
。也就是说,您将拥有:
o--...--o
/ \
...--o M <-- master (HEAD)
\ /
o--...--o <-- develop
或:
o--...--o <-- master (HEAD)
/ \
...--o M <-- develop (HEAD)
\ /
o--...--o
完成后。 M
中的 快照 将是相同的。
其余的要么不重要要么至关重要
因为 commits 很重要,所以用于访问合并结果的 name 不是很重要。好吧,除了一件事:如果你继续使用那个名字来进行更多次提交,并且想要访问这些“更多提交”,这个名字你有 Git 提前是绝对关键的。
即:
o--...--o
/ \
...--o M--N <-- master (HEAD)
\ /
o--...--o <-- develop
和:
o--...--o
/ \
...--o M <-- master (HEAD)
\ /
o--...--o--N <-- develop
是完全不同的图片。那是因为我们通过从某个现有提交的快照开始,更改一些内容并提交结果来进行 new 提交。因此,N
的快照中的内容将取决于您是否从提交 M
(合并结果)开始。
但请注意,我们可以随时重命名这两个分支:
o--...--o
/ \
...--o M <-- smorgas (HEAD)
\ /
o--...--o <-- bord
重要的不是 names,而是提交。因此,您应该按照自己的方式安排 commits,并使用有助于实现目标的名称。要正确地做到这一点,您首先需要很好地理解您的实际目标 。对 Git 重要的是 提交; 分支名称 只是 Git 帮助您找到正确的方法OID,以便您可以找到您关心的提交。
剩下的
I tried to merge develop into master and it gave me 34 files and 1,312 additions and 324 removals and I tried to merge master into develop and it gave me 251 files and 87,123 additions and 1,321 removals.
任何时候 Git 说“添加了 A 个文件,删除了 R 个文件,C 文件已更改”(加上添加或删除的行数 to/from 已更改的文件),Git 有 运行 git diff
。当我们有这个:
...--F--G--H--I--J
并且我们使用提交 H
和 git show
,这里很简单:Git 比较提交 G
,parent H
,提交 H
,以获得那组数字。但是当我们有:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
和 Git 试图告诉我们一些关于提交 M
的事情,好吧,M
有 两个 parent ,不止一个。那么 Git 在 git diff
中使用了哪一个 运行 得出了数字?
这里的答案是Git使用合并的先parent。当你 运行:
git switch somebranch
git merge otherbranch
并获得一个新的合并提交 on somebranch
,它的 first parent 是提交刚才 是 在 somebranch
的顶端,它的 第二个 parent 是 是(仍然)在otherbranch
的顶端。所以如果我们切换到 br2
并合并 br1
,你会在这里得到不同的数字。如果我们将 parent 数字添加到图中,我们可以直观地展示它是如何工作的:
I--J
/ \₁
...--G--H M <-- br1 (HEAD)
\ /²
K--L <-- br2
对比:
I--J <-- br1
/ \²
...--G--H M <-- br2 (HEAD)
\ /₁
K--L
所以你会看到不同的统计数据,即使快照在M
中是相同的,因为Git 正在比较 M
与 J
或 L
.
It means that for both branches to be the same, we need to merge master into develop and then merge develop into master every time ...
你可以这样做,但要小心!并非每个 git merge
实际上都会进行 合并提交 。如果您强制 git merge
进行合并提交,并且您开始于:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
和运行 git switch br2 && git merge --no-ff br1
,你得到这个:
I--J
/ \
...--G--H M <-- br1
\ / \
K--L---N <-- br2 (HEAD)
其中 N
的第一个 parent 是 L
,N
的第二个 parent 是 M
。 Commits M
and N
will (generally2) have the same snapshot, but they have different numbers and are different提交。
但是git merge
并不总是进行合并提交。考虑以下分支结构:
...--G--H <-- main (HEAD)
\
I--J <-- feature
这里我们使用了两次提交来开发一个新功能。我们现在准备合并它。如果我们 运行 git merge feature
,真正的合并必须:
- 找到合并基础:那是提交
H
;
- 将
H
中的快照与我们当前的快照 H
进行比较,以查看我们更改了什么,但这显然什么都没有 因为 H
是 H
;
- 将
H
中的快照与他们在 J
中的快照进行比较,以查看他们更改了什么;
- 将我们的 non-changed 内容添加到他们的更改内容中,并将这些更改应用于
H
中的内容,提供他们的快照;和
- 进行新的合并提交。
结果如下所示:
...--G--H------M <-- main (HEAD)
\ /
I--J <-- feature
并且 M
中的快照与 J
中的快照匹配。但是 Git 走捷径。它对自己说:好吧,呃,如果我 diff commit H
反对自己,那显然是空的。合并结果将包含来自 J
的快照。如果我只使用提交 J
? 如果你允许它这样做,git merge
将然后 使用提交 J,给出:
...--G--H
\
I--J <-- feature, main (HEAD)
之后就没有理由为图中的扭结烦恼了:
...--G--H--I--J <-- feature, main (HEAD)
如果让 Git 这样做,这就是切换到 develop
和 git merge master
:
时会得到的那种合并
o--...--o
/ \
...--o M <-- develop (HEAD), master
\ /
o--...--o
现在只有一个提交 M
在 两个分支 上是最新的。
如果你现在在 develop
上进行新的提交,你会得到:
o--...--o
/ \
...--o M <-- master
\ / \
o--...--o N <-- develop (HEAD)
请注意提交 N
的 parent 是如何提交 M
:这是一个普通的 single-parent non-merge 提交。这与您首先 删除 名称 develop
时得到的结果相同:
o--...--o
/ \
...--o M <-- master (HEAD)
\ /
o--...--o [the name develop *was* here, but now is deleted]
然后从提交 M
创建一个 new develop
并使用:
git branch develop # or git switch -c develop
以便新名称指向提交 M
。
一些网络托管网站(例如 GitHub)不允许您通过其网络界面执行 fast-forward not-really-a-merge 操作。对于这些站点,您必须使用 command-line Git,或删除和 re-create 分支名称,以获得这种特殊效果 如果您需要 。何时以及是否需要它是您的选择!
2您可以通过多种方式解决这个问题,包括选项和 so-called evil merge.
结论
您需要知道 Git 的实际作用 和 您想要的。人们为 Git 提出了各种花哨的分支模型,但只是选择一个并遵循它而不理解 为什么 某人 选择 模型可能会给你带来麻烦。 Git 中分支名称的要点是 使用 human-readable 名称自动查找最新的提交 ,因为人类这样做,你需要了解人类在想什么; 这,而不是 Git 本身,才是真正困难的原因。
合并 develop 为 master 和 master 合并为 develop 的区别?我尝试将 develop 合并到 master 中,它给了我 34 个文件和 1,312 个添加和 324 个删除,我尝试将 master 合并到 develop 中,它给了我 251 个文件和 87,123 个添加和 1,321 个删除。我的猜测是它需要时间从 master 分离出来然后进行所有更改并将其与我们要合并到的分支中的文件中更改的文件进行比较?我说得对吗?
这意味着要使两个分支相同,我们需要将 master 合并到 develop 中,然后在两个分支每天更改 1 个月 + 十几个开发人员时每次将 develop 合并到 master ?
git-diff 给我们带来了什么?它是否给出了两个分支的所有差异,或者如果我们尝试将分支 1 合并到分支 2 中会得到什么?
为了理解这个问题的答案,让我们从一些关于 Git 的事实开始:
Git 存储 提交 ,而不是文件或分支。提交 是 存储库中的历史记录:每个提交都有一个唯一编号(哈希 ID 或 object ID aka OID),每个都包含两件事:每个文件的完整快照,以及一些元数据。任何一次提交中的元数据都包含一个先前提交哈希 ID 的列表,它可以让 Git 将后来的提交与先前的提交联系起来。大多数(所有“普通”)提交中只有一个先前的哈希 ID,它将提交链接到它的 parent。这允许 Git 向后工作,从最近的提交到最早的提交。
分支名称如
master
或main
、develop
、br1
、feature/tall
等,只包含one 提交哈希 ID。根据定义,名称中存储的任何哈希 ID 都是该分支上的 最新 提交。
仅从这两个事实我们就可以开始可视化提交:
... <-F <-G <-H <--br1
这里我们有一个像 br1
这样的分支名称选择,或者 指向 ,last 提交那个分支。该提交有一些哈希 ID,我们将其称为 H
,这样我们就不必生成一些随机的东西并尝试记住它。
提交 H
包含所有文件的快照,但也包含元数据以说明谁 提交 提交 H
,原因(他们的日志消息),等等。提交 H
的元数据存储了之前一次提交的哈希 ID,我们将其称为 G
。所以提交 H
指向更早的提交 G
.
提交 G
,作为提交,存储完整快照和元数据。 G
中的元数据使 G
向后指向更早的提交 F
,这也是一个提交,因此它向后指向另一个更早的提交。
当我们查看带有git diff
或git show
的提交时,我们实际上给了Git两个 提交哈希 ID。我们从提交本身开始,例如 H
,也许使用分支名称 br1
:
git show br1
Git 使用它来定位 H
,然后使用 H
定位它的 parent 提交 G
。 Git 然后将 两个 快照提取到内存中的一个临时区域,并比较它们。毕竟,我们只对更改 的文件感兴趣。 (这得益于提交快照 de-duplicate 文件内容,因此如果 H
和 G
主要共享大部分文件,Git 可以立即分辨出来,甚至不必费心提取这些文件。)
对于 确实 更改的文件,Git 计算出一个“更改方法”——一个 diff——并显示作为“发生了什么”。这对于像提交 H
这样的普通提交非常有效。但它因合并而崩溃。
合并
为了理解git merge
,我们从合并的目标开始:合并工作。让我们画一张有某种分叉的提交的图片,这样我们就有了两个不同的工作链,像这样:
I--J <-- br1
/
...--G--H
\
K--L <-- br2
也就是说,此时有两个“最新”提交。提交 J
是最新的;它的parent是I
,它的parent是H
,它的parent是G
等等。但是提交 L
是 也是 最新的;它的parent是K
,它的parent是H
,它的parent是G
等等。这里 J
是最新的 br1
commit 而 L
是最新的 br2
commit .
一如既往,每次提交都包含所有文件的完整快照。要在两个分支上合并 work,我们需要找到 changes。我们该怎么做?好吧,我们已经知道答案了:我们可以使用 git diff
或 git show
来选择两个提交并 比较 它们。
每个人都首先尝试的是选择提交 J
和 L
并比较它们,但这是行不通的。但这显示了这两个“最新”之间的不同之处,这通常不是我们想要的。例如,Alice 可能通过修复 README 中的拼写错误并添加功能 1 来实现 br1
,而 Bob 通过修复其他文档中的不同拼写错误并添加功能 2 来实现 br2
。如果我们 diff J
vs L
,Git 给我们的方法是: 删除 Alice 的修复和特征,并添加 Bob 的修复和特征。我们想要的是添加Alice的修复和功能,以及Bob的修复和功能。
切入正题,这里的技巧是从提交 H
开始。那是最好的 shared 提交:实际上是在两个分支上的提交。通过从 J
开始并向后工作以及 也 从 L
开始并向后工作,我们发现 H
是最好的 shared 提交。所以我们 diff H
vs J
看看 Alice 做了什么,然后——分别地!——diff H
vs L
看看 Bob 做了什么。 那 为我们提供了要组合的更改集。
Git 将尽最大努力组合这些 change-recipes,使用一些非常简单的规则:
- 如果没有人动过一个文件,使用它的任何版本:三个都是一样的。
- 如果一个分支涉及某个文件而另一个分支根本不涉及它,则使用更改它的一个分支中更改的文件。
- 如果两者都涉及某个文件,请尝试合并更改,line-by-line。如果它们在 不同的 non-overlapping 行上,则可以将它们合并。 Git 添加了行也不能 abut 的规则。如果它们确实重叠,则它们必须 100% 相同。否则,Git 将声明一个 合并冲突 并让你,程序员,收拾烂摊子。
这些规则出人意料地经常起作用,因此 git merge
可以从两个分支中获取两组更改,将 combined 更改应用到 [= 中的快照42=],并将其用于新快照。根据您喜欢的查看方式,结果是我们在添加 br2
更改的同时保留 br1
更改,或者在添加 br1
的同时保留 br2
更改变化。请注意,与普通数学加法一样,无论 addends (that is, we don't need to define a separate "augend" vs "addend" because the operation is commutative).1
为新提交创建了一个快照,Git然后创建新提交。您提供一条日志消息,在其中解释为什么要进行合并——或者您使用蹩脚的默认消息,例如 merge branch br2
,这是大多数人真正做的——然后 Git 进行新的提交就像任何提交一样:它有一个快照和元数据。新提交的特别之处在于,它不仅有 一个 parent,还有 两个:
I--J
/ \
...--G--H M
\ /
K--L
请注意,我已将此图片上的分支名称存档。每当您进行 any 新提交时——无论是 git commit
、git merge
、git cherry-pick
或 git revert
或其他什么——Git 将自动为您更新 当前分支名称 ,因此 M
现在是最新的提交。但是哪个分支 name 得到了更新?嗯,这取决于哪个分支名称 git status
说你是 on
:
$ git status
On branch `br1`
...
$ git merge br2
结果:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
也就是说,你“在”br1
——这就是附加到 br1
的 HEAD
在这里的意思——你仍然在,所以新的提交也是最新的对于 br1
。但是如果 git status
说你是 On branch br2
而你有 运行 git merge br1
,那么更新的就是名字 br2
。
1注意在git merge
中加入options,如-s ours
或-X theirs
例如,更改此操作:操作不再是可交换的。
这回答了你问题的第一部分
[What is the d]ifference between merging develop into master and master into develop?
一个推进名字master
,一个推进名字develop
。也就是说,您将拥有:
o--...--o
/ \
...--o M <-- master (HEAD)
\ /
o--...--o <-- develop
或:
o--...--o <-- master (HEAD)
/ \
...--o M <-- develop (HEAD)
\ /
o--...--o
完成后。 M
中的 快照 将是相同的。
其余的要么不重要要么至关重要
因为 commits 很重要,所以用于访问合并结果的 name 不是很重要。好吧,除了一件事:如果你继续使用那个名字来进行更多次提交,并且想要访问这些“更多提交”,这个名字你有 Git 提前是绝对关键的。
即:
o--...--o
/ \
...--o M--N <-- master (HEAD)
\ /
o--...--o <-- develop
和:
o--...--o
/ \
...--o M <-- master (HEAD)
\ /
o--...--o--N <-- develop
是完全不同的图片。那是因为我们通过从某个现有提交的快照开始,更改一些内容并提交结果来进行 new 提交。因此,N
的快照中的内容将取决于您是否从提交 M
(合并结果)开始。
但请注意,我们可以随时重命名这两个分支:
o--...--o
/ \
...--o M <-- smorgas (HEAD)
\ /
o--...--o <-- bord
重要的不是 names,而是提交。因此,您应该按照自己的方式安排 commits,并使用有助于实现目标的名称。要正确地做到这一点,您首先需要很好地理解您的实际目标 。对 Git 重要的是 提交; 分支名称 只是 Git 帮助您找到正确的方法OID,以便您可以找到您关心的提交。
剩下的
I tried to merge develop into master and it gave me 34 files and 1,312 additions and 324 removals and I tried to merge master into develop and it gave me 251 files and 87,123 additions and 1,321 removals.
任何时候 Git 说“添加了 A 个文件,删除了 R 个文件,C 文件已更改”(加上添加或删除的行数 to/from 已更改的文件),Git 有 运行 git diff
。当我们有这个:
...--F--G--H--I--J
并且我们使用提交 H
和 git show
,这里很简单:Git 比较提交 G
,parent H
,提交 H
,以获得那组数字。但是当我们有:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
和 Git 试图告诉我们一些关于提交 M
的事情,好吧,M
有 两个 parent ,不止一个。那么 Git 在 git diff
中使用了哪一个 运行 得出了数字?
这里的答案是Git使用合并的先parent。当你 运行:
git switch somebranch
git merge otherbranch
并获得一个新的合并提交 on somebranch
,它的 first parent 是提交刚才 是 在 somebranch
的顶端,它的 第二个 parent 是 是(仍然)在otherbranch
的顶端。所以如果我们切换到 br2
并合并 br1
,你会在这里得到不同的数字。如果我们将 parent 数字添加到图中,我们可以直观地展示它是如何工作的:
I--J
/ \₁
...--G--H M <-- br1 (HEAD)
\ /²
K--L <-- br2
对比:
I--J <-- br1
/ \²
...--G--H M <-- br2 (HEAD)
\ /₁
K--L
所以你会看到不同的统计数据,即使快照在M
中是相同的,因为Git 正在比较 M
与 J
或 L
.
It means that for both branches to be the same, we need to merge master into develop and then merge develop into master every time ...
你可以这样做,但要小心!并非每个 git merge
实际上都会进行 合并提交 。如果您强制 git merge
进行合并提交,并且您开始于:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
和运行 git switch br2 && git merge --no-ff br1
,你得到这个:
I--J
/ \
...--G--H M <-- br1
\ / \
K--L---N <-- br2 (HEAD)
其中 N
的第一个 parent 是 L
,N
的第二个 parent 是 M
。 Commits M
and N
will (generally2) have the same snapshot, but they have different numbers and are different提交。
但是git merge
并不总是进行合并提交。考虑以下分支结构:
...--G--H <-- main (HEAD)
\
I--J <-- feature
这里我们使用了两次提交来开发一个新功能。我们现在准备合并它。如果我们 运行 git merge feature
,真正的合并必须:
- 找到合并基础:那是提交
H
; - 将
H
中的快照与我们当前的快照H
进行比较,以查看我们更改了什么,但这显然什么都没有 因为H
是H
; - 将
H
中的快照与他们在J
中的快照进行比较,以查看他们更改了什么; - 将我们的 non-changed 内容添加到他们的更改内容中,并将这些更改应用于
H
中的内容,提供他们的快照;和 - 进行新的合并提交。
结果如下所示:
...--G--H------M <-- main (HEAD)
\ /
I--J <-- feature
并且 M
中的快照与 J
中的快照匹配。但是 Git 走捷径。它对自己说:好吧,呃,如果我 diff commit H
反对自己,那显然是空的。合并结果将包含来自 J
的快照。如果我只使用提交 J
? 如果你允许它这样做,git merge
将然后 使用提交 J,给出:
...--G--H
\
I--J <-- feature, main (HEAD)
之后就没有理由为图中的扭结烦恼了:
...--G--H--I--J <-- feature, main (HEAD)
如果让 Git 这样做,这就是切换到 develop
和 git merge master
:
o--...--o
/ \
...--o M <-- develop (HEAD), master
\ /
o--...--o
现在只有一个提交 M
在 两个分支 上是最新的。
如果你现在在 develop
上进行新的提交,你会得到:
o--...--o
/ \
...--o M <-- master
\ / \
o--...--o N <-- develop (HEAD)
请注意提交 N
的 parent 是如何提交 M
:这是一个普通的 single-parent non-merge 提交。这与您首先 删除 名称 develop
时得到的结果相同:
o--...--o
/ \
...--o M <-- master (HEAD)
\ /
o--...--o [the name develop *was* here, but now is deleted]
然后从提交 M
创建一个 new develop
并使用:
git branch develop # or git switch -c develop
以便新名称指向提交 M
。
一些网络托管网站(例如 GitHub)不允许您通过其网络界面执行 fast-forward not-really-a-merge 操作。对于这些站点,您必须使用 command-line Git,或删除和 re-create 分支名称,以获得这种特殊效果 如果您需要 。何时以及是否需要它是您的选择!
2您可以通过多种方式解决这个问题,包括选项和 so-called evil merge.
结论
您需要知道 Git 的实际作用 和 您想要的。人们为 Git 提出了各种花哨的分支模型,但只是选择一个并遵循它而不理解 为什么 某人 选择 模型可能会给你带来麻烦。 Git 中分支名称的要点是 使用 human-readable 名称自动查找最新的提交 ,因为人类这样做,你需要了解人类在想什么; 这,而不是 Git 本身,才是真正困难的原因。