git 合并是否只在本地进行更改?
Does git merge make changes only in local?
如果你
git merge branchA branchB
但你打算反其道而行之,即
git merge branchB branchA
此合并是否仅影响您的本地存储库,或者相反,它是否影响远程存储库?
git 合并创建合并提交。有两个父项而不是一个的特殊提交。在所有其他方面,它的行为就像一个普通的提交。要将它与遥控器同步,您需要 push
它。
TL;DR
新的提交是纯本地的。但是你的命令有误。
长
如果你运行:
git merge branchA branchB
你正在合并 三个 东西,Git 称之为 octopus 合并。不要那样做!1 语法实际上是:
git switch branchA
git merge branchB
原因是git merge
,和git commit
一样,一般会在当前b运行ch中添加一个新的commit。因此,在 other b运行ch 上调用 git merge
之前,您必须选择两个 b运行ches 中的哪一个应该是“当前的”。
1一旦你成为 Git Guru,或者任何你喜欢的称呼,你就可以做到。
B运行ch 名称和提交
在理解这些内容之前,您必须很好地理解许多关键概念。我们应该首先定义一个 repository 和 commits:
Git 存储库的核心是 提交 的大型数据库。还有一个通常小得多的 names 数据库,我们稍后也会看到。
每个 Git 提交存储两件事:
提交具有每个文件的完整快照,及时冻结,就像存档一样。 in 提交的文件存储在特殊的 read-only、Git-only 压缩和 de-duplicated 中形式。 de-duplication 处理了这样一个事实,即大多数提交大多来自早期提交的 re-use 文件:这样,他们就不会使用 space.
提交还存储了一些元数据。提交中的元数据会告诉您和 Git 关于该特定提交的信息:例如,是谁提交的(姓名和电子邮件地址)以及何时提交的(date-and-time 标记)。元数据包括提交者的日志消息,告诉您 他们为什么 进行该提交。还有一些严格针对 Git 本身的信息。
每个提交都有编号,带有一个丑陋的大哈希 ID(或更正式地说,object ID 或 OID).这个数字对于这个特定的提交是唯一的:一旦它被用于 that 提交,它就永远不能用于 任何其他提交 。这就是为什么这个数字如此之大。 Git 使用 hexadecimal 中表示的数字来 在大 all-objects 数据库中找到 提交。 Git 需要 这个数字来找到提交。因此,您可能必须记住每个提交哈希 ID,但这太可怕了。
为了避免必须记住哈希 ID,Git 将 最新 提交的哈希 ID 保存在第二个数据库中。每个 b运行ch 有一个条目,每个标签有一个条目,依此类推:第二个数据库保存名称,无论它们是 b运行ch 名称、标签名称还是 remote-tracking 名称,或任何其他类型的名称。每个名称记住一 (1) 个哈希 ID。
你可能——应该,真的——想知道只记住一个哈希ID有什么好处,当一个b运行ch 是(有时 2)许多提交。答案在提交元数据中。
每个 commit,存储在 objects 数据库中,保存一些元数据,元数据 in 任何给定的提交包括 previous 提交哈希 ID 的列表。 Git 将这些 previous-commit-hash-IDs 称为提交的 parents。大多数提交只有一个 parent.
如果我们绘制一个提交链,全部排成一行,较新的提交向右,这些哈希 ID 意味着每个提交指向 向后 恰好是一个较早的提交,然后向后指向 still-earlier 提交:
... <-a123456 <-b789abc <-def01234 <--latest
在这里,b运行ch 名称 latest
包含哈希 ID def01234
:我们 b运行ch 上的最新提交名为 latest
。提交 def01234
持有哈希 ID b789abc
,这是其 parent 提交:该提交早于一个提交。提交 b789abc
依次持有哈希 ID a123456
.
我们说 b运行ch name 指向 b运行 的 tip 提交ch,并且提交本身指向他们的 parents。只要这是一个很好的简单线条,一切都非常甜蜜:
... <-F <-G <-H <-- branch
我们可以偷懒画成这样:
...--F--G--H <-- branch
(提交的编号方案意味着任何提交的任何部分,包括它的任何快照或元数据,都不能更改,因此因为提交 H
向后指向提交 G
,它永远如此。提交 G
永远向后指向 F
,依此类推。这些无法更改,因此箭头 必须 向后移动,因为哈希ID 是不可预测的。3 这允许这里的惰性。)
2Git中的b运行ch这个词被严重滥用了,几乎到了完全失去任何意义。另见 What exactly do we mean by "branch"?
3提交的哈希 ID 包括您进行提交的确切秒数等。除非您知道每次未来提交的时间,否则您不知道它们将拥有什么哈希 ID。
进行新的提交
当您进行 new 提交时,您会在某些 b运行ch 上执行此操作。 Git 记住你正在“使用”哪个 b运行ch 名称——这样 git status
可以说 on branch main
或 on branch br1
或其他——通过填充 b运行ch的名字在一个特殊的名字里,HEAD
.4
Git 进行新提交的方式非常简单。假设我们正在进行第三次提交,在我们非常小的存储库中有这三个提交:
A--B--C <-- main (HEAD)
我们照常做 edit-and-add-and-git commit
。 Git 使用 user.name
和 user.email
以及当前日期和时间和您输入的任何日志消息以及源快照打包一些元数据。 Git 将所有这些存储为一个新的提交,它获得一个新的、唯一的、random-looking 哈希 ID,但我们只称这个提交为 D
。 Git 确保新提交 D
的 parent 是现有提交 C
:Git 得到 C
的哈希 ID,通过读取 HEAD
,表示 main
,然后读取 b运行ch 名称 main
,表示 [=57= 的哈希 ID 是什么] ].
此时的结果是这样的:
A--B--C <-- main (HEAD)
\
D
新提交 D
向后指向 C
。但是现在提交 D
是 latest 提交。所以 Git 使用名称 HEAD
来查找 b运行ch 名称 main
,并且 写入 D
的哈希 ID,无论它是什么是,进入那个 b运行ch name。结果是:
A--B--C
\
D <-- main (HEAD)
而且没有真正的理由再在图表中画出扭结。
4HEAD
传统上存储在一个文件中,.git/HEAD
;如果你喜欢,你可以看看。它有魔法字符串ref:
,一个space,然后是当前b运行ch的全名,然后是一个换行符。请注意,从 git worktree add
添加的工作树会得到 不同的 HEAD
,因此您不应指望这一点。
使用多个b运行ch名称
现在让我们看看当我们创建一个新的 b运行ch name、develop
时我们的小 four-commit 存储库发生了什么:
A--B--C--D <-- develop, main (HEAD)
我们现在有 两个 名称,都指向提交 D
。
如果我们现在进行新的提交,保持 main
,我们将得到:
E <-- main (HEAD)
/
A--B--C--D <-- develop
(这次有理由在图形绘制中保持扭结,这样名称 develop
仍然可以指向提交 D
)。
练习:哪些提交在哪个 b运行ch 上? 提交 A-B-C-D
之前都在 main
上,对吗?现在 E
在 main
。 A-B-C-D
关闭 还是 main
?他们显然在 develop
。我会将答案放在脚注中,以便您在跳到剧透之前可以考虑一下。5
与此同时,我们现在可以 git checkout develop
或 git switch develop
。我们会得到这个:
E <-- main
/
A--B--C--D <-- develop (HEAD)
存储库中的提交集根本没有改变,但我们现在再次使用提交 D
,而不是提交 E
。名字 main
为我们记住了 E
的散列 ID,这样我们就不必记住了;名称 develop
记住了 D
,并且提交 D
是我们现在 使用 的提交。
如果我们现在再次提交,我们会得到:
E <-- main
/
A--B--C--D
\
F <-- develop (HEAD)
练习(完成前一个练习后很容易):哪些提交在哪些 b运行 上?如果我们将其绘制为:
有什么区别吗?
E <-- main
/
A--B--C--D--F <-- develop (HEAD)
或:
A--B--C--D--E <-- main
\
F <-- develop (HEAD)
? (答案应该很明显;如果不是,请从 运行 到 Think Like (a) Git。)
5答案是 所有 提交都在 main
,A-B-C-D
在 develop
。在 Git 中,一个提交经常同时“在”许多 b运行 上!任何给定的提交都在 any b运行ch 上,从 branch-tip 提交开始,我们可以通过 parent 链接,以达到该提交。
一个很好的思考方式是,提交与其说是“在”一些 b运行ch 上,不如说是“包含在”一些 b运行ches 中,复数形式。 git branch --contains
sub-command/option 可以告诉您哪些名称“包含”提交(您向命令提供提交的哈希 ID,它会找到所有 b运行ch 名称)。 (同样的概念适用于标签名称和 git tag --contains
,尽管它在这里通常没那么有用。)
正在合并
假设我们开始于:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
也就是说,我们“在”b运行ch br1
,其最新(“提示”)提交是 J
; br2
的提示提交是 L
.
我们现在运行:
git merge br2
结合工作。我们将合并的工作是通过从 merge base 提交中执行 git diff
s 来定义的——both 上的“最佳”提交b运行ches, 其中在这种情况下是提交 H
——针对两个提示提交。这些差异显示了我们在 br1
上对第一个差异所做的操作,以及他们在 br2
上对第二个差异所做的操作。 Git 的工作是合并更改,然后将合并的更改 应用到H
中的快照。
这会保留我们的更改并添加他们的更改,或者,如果您喜欢这样想,则保留他们的更改并添加我们的更改。通过将 两组更改加在一起,并将总和应用于基数,Git 合并了工作。冲突,如果有的话,是因为这两个差异不能顺利地“加起来”。
不过,假设一切顺利,Git 通过将这些 combined-changes 应用到 H
中的快照并进行新提交 M
来拍摄它获得的快照。这个新提交是一个合并提交。 合并提交的定义是一个有两个或更多parent的提交。(大多数合并只有两个。)我们可以像这样绘制这个新提交:
I--J
/ \
...--G--H M
\ /
K--L
我故意把 b运行ch 名字去掉了,因为你的问题(部分)涉及 b运行ch 名字会发生什么。所以:b运行ch 名称发生了什么变化? 好吧,还记得我们使用 git commit
时发生了什么吗? Git 进行了新的提交,然后将其哈希 ID(无论是什么)填充到 current b运行ch name 中。 合并工作与常规提交相同;他们只是有一个额外的 parent. 所以提交 M
的哈希 ID 进入 name br1
,像这样:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
我们在br1
之前我们运行git merge
,我们仍然在br1
].我们只是向它添加了一个提交,我们添加的提交类型是合并提交。
但是等等:有一个特例
我们从上面的两个分支开始合并 运行ches。让我们看看如果我们从 less-complicated 情况开始会发生什么:
...--G--H <-- main (HEAD)
\
I--J <-- develop
我们现在运行git merge develop
。 Git 需要找到 merge base——在 both b运行ches 上的最佳提交——然后这样做,事实证明这是提交 H
。但是 我们现在正在使用提交 H
。 Git 必须将提交 H
的快照与提交 H
的快照进行比较,以查看我们更改了什么。这将是,嗯,什么都没有。
同时 Git 将不得不比较 H
-vs-J
以查看它们发生了什么变化。然后 Git 会将他们的更改(某物)添加到我们的更改(无)中,然后得到......好吧,无论 他们的 更改是什么。不会有任何冲突,所以这将始终有效。然后 Git 必须将他们的更改应用到提交 H
并进行新的合并提交:
...--G--H------M <-- main (HEAD)
\ /
I--J <-- develop
您可以让 Git 执行此操作,但它不会自行执行。 相反,Git 自言自语:啊哈,我可以作弊!我可以用一个short-cut!我为新合并提交 M
制作的快照将与现有提交 J
中的快照完全匹配!我想我会这样做...
...--G--H
\
I--J <-- develop, main (HEAD)
也就是说,Git“滑动 b运行ch 名称”main
forward,并检查提交中的所有文件J
。它这样做 而不是合并 。然后,它相当具有欺骗性地称其为 fast-forward 合并 ,但它根本不是合并,它只是一个 check-out!这是一个 check-out 操作,将当前 b运行ch 名称向前拖动。
在这种情况下,您不会获得 任何 新提交。您得到 fast-forward 而不是 合并。
这都是本地的
无论哪种合并——真正的合并,还是 fast-forward 伪造的合并——git merge
执行,结果 仅在您的存储库中 。您要么有新的合并提交,要么没有,并且您的 current b运行ch name 指向新提交,或指向 now-tip-of-both-branches共享提交。
任何其他 Git 存储库中没有发生任何事情。您刚刚更新了您自己的存储库的名称数据库,以便当前的 b运行ch 名称选择新的或其他提交,并且可能添加了一个新的提交(并支持 objects)到 objects 数据库。
章鱼合并
为了展示章鱼合并的样子,让我们画一个 main-line 和三个特征:
_-I <-- feature/two
/
/ J <-- feature/three
/ /
...--G--H <-- main (HEAD)
\
K--L <-- feature/one
我们现在可以运行:
git merge feature/one feature/two feature/three
Git会尽量把所有的工作结合起来——这里的细节非常杂乱和复杂,Git通过要求有根本没有合并冲突——如果一切顺利,Git 将创建一个新的合并提交,在这种情况下,四个 parents:
_-I_ <-- feature/two
/ \
/ J | <-- feature/three
/ / \|
...--G--H---M <-- main (HEAD)
\ /
K--L <-- feature/one
提交 M
指向提交 H
,这是我们在 运行 git merge
时使用的提交。但它也指向回到提交 L
(来自 feature/one
)、I
(来自 feature/two
)和 J
(来自 feature/three
)。
现在可以删除所有三个 feature/*
名称,因为我们可以通过提交 M
向后工作(使用适当的侧向 jink)找到这些提交。这不会执行您无法使用多个 git merge
命令完成的任何操作:您可以合并 feature/one
,然后合并 feature/two
,然后合并 feature/three
。事实上,它不能做那些 可以 做的事情,即解决合并冲突。 较弱是使用它的主要原因,但令人困惑是不使用它的主要原因。
git merge branchA branchB
纯粹是本地的,另外,如果它是错误的命令并且你刚刚给出了它,你可以通过说以下命令立即撤消它:
git reset --hard @~1
请注意,您的命令可能并不像您认为的那样:它不是 意思是“将 branchA 合并到 branchB”。所以在纠正你的错误之后,你可能需要在给出“正确”的命令之前更加认真地思考一下。
如果你
git merge branchA branchB
但你打算反其道而行之,即
git merge branchB branchA
此合并是否仅影响您的本地存储库,或者相反,它是否影响远程存储库?
git 合并创建合并提交。有两个父项而不是一个的特殊提交。在所有其他方面,它的行为就像一个普通的提交。要将它与遥控器同步,您需要 push
它。
TL;DR
新的提交是纯本地的。但是你的命令有误。
长
如果你运行:
git merge branchA branchB
你正在合并 三个 东西,Git 称之为 octopus 合并。不要那样做!1 语法实际上是:
git switch branchA
git merge branchB
原因是git merge
,和git commit
一样,一般会在当前b运行ch中添加一个新的commit。因此,在 other b运行ch 上调用 git merge
之前,您必须选择两个 b运行ches 中的哪一个应该是“当前的”。
1一旦你成为 Git Guru,或者任何你喜欢的称呼,你就可以做到。
B运行ch 名称和提交
在理解这些内容之前,您必须很好地理解许多关键概念。我们应该首先定义一个 repository 和 commits:
Git 存储库的核心是 提交 的大型数据库。还有一个通常小得多的 names 数据库,我们稍后也会看到。
每个 Git 提交存储两件事:
提交具有每个文件的完整快照,及时冻结,就像存档一样。 in 提交的文件存储在特殊的 read-only、Git-only 压缩和 de-duplicated 中形式。 de-duplication 处理了这样一个事实,即大多数提交大多来自早期提交的 re-use 文件:这样,他们就不会使用 space.
提交还存储了一些元数据。提交中的元数据会告诉您和 Git 关于该特定提交的信息:例如,是谁提交的(姓名和电子邮件地址)以及何时提交的(date-and-time 标记)。元数据包括提交者的日志消息,告诉您 他们为什么 进行该提交。还有一些严格针对 Git 本身的信息。
每个提交都有编号,带有一个丑陋的大哈希 ID(或更正式地说,object ID 或 OID).这个数字对于这个特定的提交是唯一的:一旦它被用于 that 提交,它就永远不能用于 任何其他提交 。这就是为什么这个数字如此之大。 Git 使用 hexadecimal 中表示的数字来 在大 all-objects 数据库中找到 提交。 Git 需要 这个数字来找到提交。因此,您可能必须记住每个提交哈希 ID,但这太可怕了。
为了避免必须记住哈希 ID,Git 将 最新 提交的哈希 ID 保存在第二个数据库中。每个 b运行ch 有一个条目,每个标签有一个条目,依此类推:第二个数据库保存名称,无论它们是 b运行ch 名称、标签名称还是 remote-tracking 名称,或任何其他类型的名称。每个名称记住一 (1) 个哈希 ID。
你可能——应该,真的——想知道只记住一个哈希ID有什么好处,当一个b运行ch 是(有时 2)许多提交。答案在提交元数据中。
每个 commit,存储在 objects 数据库中,保存一些元数据,元数据 in 任何给定的提交包括 previous 提交哈希 ID 的列表。 Git 将这些 previous-commit-hash-IDs 称为提交的 parents。大多数提交只有一个 parent.
如果我们绘制一个提交链,全部排成一行,较新的提交向右,这些哈希 ID 意味着每个提交指向 向后 恰好是一个较早的提交,然后向后指向 still-earlier 提交:
... <-a123456 <-b789abc <-def01234 <--latest
在这里,b运行ch 名称 latest
包含哈希 ID def01234
:我们 b运行ch 上的最新提交名为 latest
。提交 def01234
持有哈希 ID b789abc
,这是其 parent 提交:该提交早于一个提交。提交 b789abc
依次持有哈希 ID a123456
.
我们说 b运行ch name 指向 b运行 的 tip 提交ch,并且提交本身指向他们的 parents。只要这是一个很好的简单线条,一切都非常甜蜜:
... <-F <-G <-H <-- branch
我们可以偷懒画成这样:
...--F--G--H <-- branch
(提交的编号方案意味着任何提交的任何部分,包括它的任何快照或元数据,都不能更改,因此因为提交 H
向后指向提交 G
,它永远如此。提交 G
永远向后指向 F
,依此类推。这些无法更改,因此箭头 必须 向后移动,因为哈希ID 是不可预测的。3 这允许这里的惰性。)
2Git中的b运行ch这个词被严重滥用了,几乎到了完全失去任何意义。另见 What exactly do we mean by "branch"?
3提交的哈希 ID 包括您进行提交的确切秒数等。除非您知道每次未来提交的时间,否则您不知道它们将拥有什么哈希 ID。
进行新的提交
当您进行 new 提交时,您会在某些 b运行ch 上执行此操作。 Git 记住你正在“使用”哪个 b运行ch 名称——这样 git status
可以说 on branch main
或 on branch br1
或其他——通过填充 b运行ch的名字在一个特殊的名字里,HEAD
.4
Git 进行新提交的方式非常简单。假设我们正在进行第三次提交,在我们非常小的存储库中有这三个提交:
A--B--C <-- main (HEAD)
我们照常做 edit-and-add-and-git commit
。 Git 使用 user.name
和 user.email
以及当前日期和时间和您输入的任何日志消息以及源快照打包一些元数据。 Git 将所有这些存储为一个新的提交,它获得一个新的、唯一的、random-looking 哈希 ID,但我们只称这个提交为 D
。 Git 确保新提交 D
的 parent 是现有提交 C
:Git 得到 C
的哈希 ID,通过读取 HEAD
,表示 main
,然后读取 b运行ch 名称 main
,表示 [=57= 的哈希 ID 是什么] ].
此时的结果是这样的:
A--B--C <-- main (HEAD)
\
D
新提交 D
向后指向 C
。但是现在提交 D
是 latest 提交。所以 Git 使用名称 HEAD
来查找 b运行ch 名称 main
,并且 写入 D
的哈希 ID,无论它是什么是,进入那个 b运行ch name。结果是:
A--B--C
\
D <-- main (HEAD)
而且没有真正的理由再在图表中画出扭结。
4HEAD
传统上存储在一个文件中,.git/HEAD
;如果你喜欢,你可以看看。它有魔法字符串ref:
,一个space,然后是当前b运行ch的全名,然后是一个换行符。请注意,从 git worktree add
添加的工作树会得到 不同的 HEAD
,因此您不应指望这一点。
使用多个b运行ch名称
现在让我们看看当我们创建一个新的 b运行ch name、develop
时我们的小 four-commit 存储库发生了什么:
A--B--C--D <-- develop, main (HEAD)
我们现在有 两个 名称,都指向提交 D
。
如果我们现在进行新的提交,保持 main
,我们将得到:
E <-- main (HEAD)
/
A--B--C--D <-- develop
(这次有理由在图形绘制中保持扭结,这样名称 develop
仍然可以指向提交 D
)。
练习:哪些提交在哪个 b运行ch 上? 提交 A-B-C-D
之前都在 main
上,对吗?现在 E
在 main
。 A-B-C-D
关闭 还是 main
?他们显然在 develop
。我会将答案放在脚注中,以便您在跳到剧透之前可以考虑一下。5
与此同时,我们现在可以 git checkout develop
或 git switch develop
。我们会得到这个:
E <-- main
/
A--B--C--D <-- develop (HEAD)
存储库中的提交集根本没有改变,但我们现在再次使用提交 D
,而不是提交 E
。名字 main
为我们记住了 E
的散列 ID,这样我们就不必记住了;名称 develop
记住了 D
,并且提交 D
是我们现在 使用 的提交。
如果我们现在再次提交,我们会得到:
E <-- main
/
A--B--C--D
\
F <-- develop (HEAD)
练习(完成前一个练习后很容易):哪些提交在哪些 b运行 上?如果我们将其绘制为:
有什么区别吗? E <-- main
/
A--B--C--D--F <-- develop (HEAD)
或:
A--B--C--D--E <-- main
\
F <-- develop (HEAD)
? (答案应该很明显;如果不是,请从 运行 到 Think Like (a) Git。)
5答案是 所有 提交都在 main
,A-B-C-D
在 develop
。在 Git 中,一个提交经常同时“在”许多 b运行 上!任何给定的提交都在 any b运行ch 上,从 branch-tip 提交开始,我们可以通过 parent 链接,以达到该提交。
一个很好的思考方式是,提交与其说是“在”一些 b运行ch 上,不如说是“包含在”一些 b运行ches 中,复数形式。 git branch --contains
sub-command/option 可以告诉您哪些名称“包含”提交(您向命令提供提交的哈希 ID,它会找到所有 b运行ch 名称)。 (同样的概念适用于标签名称和 git tag --contains
,尽管它在这里通常没那么有用。)
正在合并
假设我们开始于:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
也就是说,我们“在”b运行ch br1
,其最新(“提示”)提交是 J
; br2
的提示提交是 L
.
我们现在运行:
git merge br2
结合工作。我们将合并的工作是通过从 merge base 提交中执行 git diff
s 来定义的——both 上的“最佳”提交b运行ches, 其中在这种情况下是提交 H
——针对两个提示提交。这些差异显示了我们在 br1
上对第一个差异所做的操作,以及他们在 br2
上对第二个差异所做的操作。 Git 的工作是合并更改,然后将合并的更改 应用到H
中的快照。
这会保留我们的更改并添加他们的更改,或者,如果您喜欢这样想,则保留他们的更改并添加我们的更改。通过将 两组更改加在一起,并将总和应用于基数,Git 合并了工作。冲突,如果有的话,是因为这两个差异不能顺利地“加起来”。
不过,假设一切顺利,Git 通过将这些 combined-changes 应用到 H
中的快照并进行新提交 M
来拍摄它获得的快照。这个新提交是一个合并提交。 合并提交的定义是一个有两个或更多parent的提交。(大多数合并只有两个。)我们可以像这样绘制这个新提交:
I--J
/ \
...--G--H M
\ /
K--L
我故意把 b运行ch 名字去掉了,因为你的问题(部分)涉及 b运行ch 名字会发生什么。所以:b运行ch 名称发生了什么变化? 好吧,还记得我们使用 git commit
时发生了什么吗? Git 进行了新的提交,然后将其哈希 ID(无论是什么)填充到 current b运行ch name 中。 合并工作与常规提交相同;他们只是有一个额外的 parent. 所以提交 M
的哈希 ID 进入 name br1
,像这样:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
我们在br1
之前我们运行git merge
,我们仍然在br1
].我们只是向它添加了一个提交,我们添加的提交类型是合并提交。
但是等等:有一个特例
我们从上面的两个分支开始合并 运行ches。让我们看看如果我们从 less-complicated 情况开始会发生什么:
...--G--H <-- main (HEAD)
\
I--J <-- develop
我们现在运行git merge develop
。 Git 需要找到 merge base——在 both b运行ches 上的最佳提交——然后这样做,事实证明这是提交 H
。但是 我们现在正在使用提交 H
。 Git 必须将提交 H
的快照与提交 H
的快照进行比较,以查看我们更改了什么。这将是,嗯,什么都没有。
同时 Git 将不得不比较 H
-vs-J
以查看它们发生了什么变化。然后 Git 会将他们的更改(某物)添加到我们的更改(无)中,然后得到......好吧,无论 他们的 更改是什么。不会有任何冲突,所以这将始终有效。然后 Git 必须将他们的更改应用到提交 H
并进行新的合并提交:
...--G--H------M <-- main (HEAD)
\ /
I--J <-- develop
您可以让 Git 执行此操作,但它不会自行执行。 相反,Git 自言自语:啊哈,我可以作弊!我可以用一个short-cut!我为新合并提交 M
制作的快照将与现有提交 J
中的快照完全匹配!我想我会这样做...
...--G--H
\
I--J <-- develop, main (HEAD)
也就是说,Git“滑动 b运行ch 名称”main
forward,并检查提交中的所有文件J
。它这样做 而不是合并 。然后,它相当具有欺骗性地称其为 fast-forward 合并 ,但它根本不是合并,它只是一个 check-out!这是一个 check-out 操作,将当前 b运行ch 名称向前拖动。
在这种情况下,您不会获得 任何 新提交。您得到 fast-forward 而不是 合并。
这都是本地的
无论哪种合并——真正的合并,还是 fast-forward 伪造的合并——git merge
执行,结果 仅在您的存储库中 。您要么有新的合并提交,要么没有,并且您的 current b运行ch name 指向新提交,或指向 now-tip-of-both-branches共享提交。
任何其他 Git 存储库中没有发生任何事情。您刚刚更新了您自己的存储库的名称数据库,以便当前的 b运行ch 名称选择新的或其他提交,并且可能添加了一个新的提交(并支持 objects)到 objects 数据库。
章鱼合并
为了展示章鱼合并的样子,让我们画一个 main-line 和三个特征:
_-I <-- feature/two
/
/ J <-- feature/three
/ /
...--G--H <-- main (HEAD)
\
K--L <-- feature/one
我们现在可以运行:
git merge feature/one feature/two feature/three
Git会尽量把所有的工作结合起来——这里的细节非常杂乱和复杂,Git通过要求有根本没有合并冲突——如果一切顺利,Git 将创建一个新的合并提交,在这种情况下,四个 parents:
_-I_ <-- feature/two
/ \
/ J | <-- feature/three
/ / \|
...--G--H---M <-- main (HEAD)
\ /
K--L <-- feature/one
提交 M
指向提交 H
,这是我们在 运行 git merge
时使用的提交。但它也指向回到提交 L
(来自 feature/one
)、I
(来自 feature/two
)和 J
(来自 feature/three
)。
现在可以删除所有三个 feature/*
名称,因为我们可以通过提交 M
向后工作(使用适当的侧向 jink)找到这些提交。这不会执行您无法使用多个 git merge
命令完成的任何操作:您可以合并 feature/one
,然后合并 feature/two
,然后合并 feature/three
。事实上,它不能做那些 可以 做的事情,即解决合并冲突。 较弱是使用它的主要原因,但令人困惑是不使用它的主要原因。
git merge branchA branchB
纯粹是本地的,另外,如果它是错误的命令并且你刚刚给出了它,你可以通过说以下命令立即撤消它:
git reset --hard @~1
请注意,您的命令可能并不像您认为的那样:它不是 意思是“将 branchA 合并到 branchB”。所以在纠正你的错误之后,你可能需要在给出“正确”的命令之前更加认真地思考一下。