合并开发为主和主开发有什么区别?我尝试将 development 合并到 master 中,它给了我 34 个文件和 1,312 个添加和 324 个删除,我试图将 master 合并到开发中,它给了我 251 个文件和 87,123 个添加和 1,321 个删除。我的猜测是,它需要时间从 master 中分离出来,然后进行所有更改并将其与该分支上更改的文件从我们要合并到的分支中的文件进行比较?我说的对吗?
这意味着要使两个分支相同,我们需要将 master 合并到 development 中,然后在每次两个分支每天更换 1 个月 + 由十几个开发人员更改时将开发合并到master?
git-diff 给了我们什么?它是否给了我们两个分支的所有差异,或者如果我们尝试将分支 1 合并到分支 2 中会得到什么?
为了理解这个问题的答案,让我们从一些关于 Git 的事实开始:
-
Git 存储提交,而不是文件或分支。 提交是存储库中的历史记录:每个提交都有一个唯一的编号(哈希ID 或对象 ID又名 OID),每个提交都包含两件事:每个文件的完整快照,以及一些元数据。 任何一个提交中的元数据都包含以前提交的哈希 ID 列表,这允许 Git 将后来的提交与以前的提交相关联。 大多数(所有"普通")提交中只有一个先前的哈希 ID,它将提交链接到其父级。 这允许 Git 从最新的提交到最早的提交向后工作。
-
分支名称如
master
或main
、develop
、br1
、feature/tall
等,只包含一个提交哈希ID。根据定义,名称中存储的任何哈希 ID 都是该分支上的最新提交。
仅从这两个事实,我们就可以开始可视化提交:
... <-F <-G <-H <--br1
在这里,我们有一个分支名称,例如br1
,它选择或指向该分支上的最后一个提交。 该提交有一些哈希 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
来定位其父提交G
。 然后,Git 将两个快照提取到内存中的临时区域,并进行比较。 毕竟,我们只对更改的文件感兴趣。 (这得益于提交快照会删除重复的文件内容,因此,如果H
和G
主要共享大部分文件,Git 可以立即分辨出来,甚至懒得提取这些文件。
对于确实发生了更改的文件,Git 会找出一个"更改配方"(差异),并将其显示为"发生了什么"。 这适用于普通提交,例如提交H
。 但它会随着合并而崩溃。
合并
为了理解git merge
,我们从合并的目标开始:合并工作。 让我们绘制一些提交图片,其中存在某种分支,这样我们就有两个不同的工作链,如下所示:
I--J <-- br1
/
...--G--H
K--L <-- br2
也就是说,此时有两个"最新"提交。 提交J
是最新的;它的父级是I
,其父级是H
,其父级是G
等等。 但提交L
也是最新的;它的父级是K
,其父级是H
,其父级是G
等等。 这里J
是最新的br1
提交,L
是最新的br2
提交。
与往常一样,每次提交都会保存所有文件的完整快照。 为了合并两个分支的工作,我们需要找到变化。 我们如何做到这一点? 好吧,我们已经知道一个答案:我们可以使用git diff
或git show
来选择两个提交并进行比较。
每个人都首先尝试的事情(不起作用)是选择提交J
并L
并进行比较。 但这显示了这两个"最新"之间的区别,这通常不是我们想要的。 例如,也许 Alice 通过修复自述文件中的拼写错误并添加功能 1 来br1
,而 Bob 通过修复其他文档中的不同拼写错误并添加功能 2 来br2
。 如果我们 差异J
与L
,Git 会给我们的配方是:从 Alice 中删除修复和功能,然后从 Bob 中添加修复和功能。 我们想要的是添加 Alice 的修复程序和功能,以及 Bob 的修复程序和功能。
要切入正题,这里的诀窍是从提交H
开始。 这是最好的共享提交:实际上在两个分支上的提交。 通过从J
开始并向后工作,也从L
开始并向后工作,我们发现H
是最好的共享提交。 因此,我们比较H
与J
,看看爱丽丝做了什么,然后——分别!——H
与L
的差异,看看鲍勃做了什么。这为我们提供了要合并的更改集。
然后,Git 将尽最大努力组合这些更改配方,使用一些非常简单的规则:
- 如果没有人接触过文件,请使用它的任何版本:这三个版本都是一样的。
- 如果一个分支接触某个文件,而另一个分支根本没有触及它,请使用更改它的一个分支中更改的文件。
- 如果两者都触及了某个文件,请尝试逐行合并更改。 如果它们位于不同的、不重叠的线上,则可以将它们组合在一起。 Git 添加了行也不得相邻的规则。 如果它们确实重叠,它们必须 100% 相同。 否则,Git 将声明合并冲突,并让你,程序员,清理混乱。
这些规则经常出人意料地起作用,因此git merge
可以从两个分支中获取两组更改,将组合的更改应用于H
中的快照,并将其用于新快照。 根据您希望如何查看此内容,结果是我们在添加br2
更改时保留br1
更改,或者在添加br1
更改时保留br2
更改。 请注意,与普通的数学加法一样,无论加法的顺序如何,结果都是相同的(也就是说,我们不需要定义单独的"augend"与"addend",因为操作是可交换的)。1
为新提交提供快照后,Git然后进行新提交。 你提供一个日志消息,在其中解释你为什么进行合并——或者你使用蹩脚的默认消息,例如merge branch br2
,这是大多数人真正做的事情——Git 会做出一个新的提交,就像任何提交一样:它有一个快照和元数据。 新提交的特别之处在于,它不仅仅是一个父级,而是有两个:
I--J
/
...--G--H M
/
K--L
请注意,我已经从这张图片中提交了分支名称。 每当您进行任何新提交时(无论是使用git commit
、git merge
、git cherry-pick
、git revert
还是其他什么),Git 都会自动更新当前的分支名称,以便M
现在是最新的提交。 但是哪个分支名称会更新? 好吧,这取决于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
添加选项(例如-s ours
或-X theirs
)会更改以下内容:操作不再是可交换的。
这回答了您问题的第一部分
发展合并为主,主发展为发展,这有什么区别?
一个将名称推进master
,另一个将名称推进develop
。 也就是说,您将拥有:
o--...--o
/
...--o M <-- master (HEAD)
/
o--...--o <-- develop
或:
o--...--o <-- master (HEAD)
/
...--o M <-- develop (HEAD)
/
o--...--o
当你完成时。 无论哪种方式,M
中的快照都是相同的。
其余的要么不重要,要么至关重要
由于提交很重要,因此用于访问合并结果的名称不是很重要。 好吧,除了一件事:如果你继续使用该名称进行更多提交,并希望访问这些">更多提交",那么您拥有 Git advance 的名称绝对至关重要。
那是:
o--...--o
/
...--o M--N <-- master (HEAD)
/
o--...--o <-- develop
和:
o--...--o
/
...--o M <-- master (HEAD)
/
o--...--o--N <-- develop
是完全不同的图片。 这是因为我们通过从一些现有提交的快照开始,更改一些内容并提交结果来进行新的提交。 因此,N
快照中的内容将取决于您是否从提交M
(合并结果)开始。
但请注意,我们可以随时重命名这两个分支:
o--...--o
/
...--o M <-- smorgas (HEAD)
/
o--...--o <-- bord
重要的不是名称,而是提交。 因此,您应该按照您想要的方式排列提交,并使用有助于实现目标的名称。 要正确地做到这一点,你需要很好地掌握你的目标实际上是什么。 对 Git 来说,重要的是提交;分支名称只是 Git 帮助你找到正确的 OID 的方式,这样你就可以找到你关心的提交。
其余的
我尝试将 development 合并到 master 中,它给了我 34 个文件和 1,312 个添加和 324 个删除,我试图将 master 合并到开发中,它给了我 251 个文件和 87,123 个添加和 1,321 个删除。
每当 Git 说">添加了 A文件、删除了 R文件、更改了 C 文件"(加上在更改的文件中添加或删除的行数)时,Git 都会运行git diff
。 当我们有这个:
...--F--G--H--I--J
我们将提交H
与git show
一起使用,这里很简单:Git 将H
的父级 commitG
与 commitH
进行比较,以获得这组数字。 但是当我们有:
I--J
/
...--G--H M <-- br1 (HEAD)
/
K--L <-- br2
Git 试图告诉我们一些关于提交M
的事情,好吧,M
有两个父母,而不仅仅是一个。 那么 Git 在运行git diff
中使用了哪一个来得出数字呢?
这里的答案是 Git 使用合并的第一个父级。 运行时:
git switch somebranch
git merge otherbranch
并获得一个继续进行somebranch
的新合并提交,它的第一个父级是刚才处于somebranch
顶端的提交,它的第二个父级是(仍然)在otherbranch
顶端的提交。因此,如果我们切换到br2
并合并br1
,您将在此处获得不同的数字。 如果我们将父数字添加到图中,我们可以直观地显示它是如何工作的:
I--J
/ ₁
...--G--H M <-- br1 (HEAD)
/²
K--L <-- br2
与:
I--J <-- br1
/ ²
...--G--H M <-- br2 (HEAD)
/₁
K--L
因此,即使M
中的快照无论哪种方式都相同,您也会看到不同的统计信息,因为 Git 正在比较M
与J
或 vsL
。
这意味着要使两个分支相同,我们需要将 master 合并到开发中,然后每次都将 development 合并到master中......
你可以这样做,但要小心! 并非每个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
的第一个父级是L
,N
的第二个父级是M
。 提交M
和N
将(通常为2)具有相同的快照,但它们具有不同的数字并且是不同的提交。
但是git merge
并不总是进行合并提交。 请考虑以下分支结构:
...--G--H <-- main (HEAD)
I--J <-- feature
在这里,我们使用了两个提交来开发一个新功能。 我们现在已准备好合并它。 如果我们运行git merge feature
,真正的合并必须:
- 找到合并库:即提交
H
; - 将
H
中的快照与我们当前的快照H
进行比较以查看我们更改了什么,但这显然什么都不是,因为H
H
; - 将
H
中的快照与J
中的快照进行比较,以查看它们更改了哪些内容;
将我们中未更改的内容添加到他们更改的内容中, - 并将这些更改应用于
H
中的内容,提供快照; - 进行新的合并提交。
结果如下所示:
...--G--H------M <-- main (HEAD)
/
I--J <-- feature
M
中的快照与J
中的快照相匹配。 但 Git 走捷径。 它对自己说:好吧,嘟嘟,如果我对自己H
diff 提交,那显然是空的。 合并结果将具有来自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
的父级是如何提交M
的:它是一个普通的单父非合并提交。 这与您在拥有以下内容时首先删除名称develop
时得到的相同:
o--...--o
/
...--o M <-- master (HEAD)
/
o--...--o [the name develop *was* here, but now is deleted]
然后使用以下命令从提交M
创建新develop
:
git branch develop # or git switch -c develop
以便新名称指向提交M
.
一些网络托管网站(例如,GitHub)不允许您通过他们的网络界面进行快进而不是真正的合并操作。 对于这些站点,如果需要,必须使用命令行 Git 或删除并重新创建分支名称才能获得此特定效果。 不过,何时以及是否想要它是您的选择!
2您可以通过多种方式击败它,包括选项和所谓的邪恶合并。
<小时 />结论
你需要知道 Git 实际上做了什么以及你想要什么。 人们为 Git 想出了各种花哨的分支模型,但只是选择一个并遵循它而不了解为什么有人选择该模型可能会给您带来麻烦。 在 Git 中,分支名称的要点是使用人类可读的名称自动查找最新的提交,并且由于人类这样做,因此您需要了解人类在想什么;这,而不是 Git 本身,才是真正困难的原因。