git pull --allow-unrelated-history 究竟是如何工作的?

Arr*_*ton 6 git github gitlab

是的,所以我搜索了其他一些 SO 线程,还检查了这个:https : //git-scm.com/docs/git-merge

我知道 --allow-unrelated-histories 允许两个项目连接在一起,但是,我不明白它究竟如何工作的

它只是这样工作吗?https://imgur.com/a/ZFVVs7s

上面的 git 站点显示了这个图:

      A---B---C topic
     /         \
D---E---F---G---H master
Run Code Online (Sandbox Code Playgroud)

然而,对我来说,这让他们看起来没有不相关的历史,因为主题分支是从“E”中分离出来的。即使主题分支在 'D' 从 master 分支,他们仍然会共享 'D' 分支。

有没有人能够解释(最好用视觉效果)允许无关历史究竟是如何工作的?我正在尝试 git pull,但我的一名团队成员编辑了我从中提取的分支,现在我必须使用 --allow-unrelated-history。

谢谢!

tor*_*rek 10

您的图表(来自git-scm.com/docs/git-merge)显示具有共同祖先提交的合并是对的。值得注意的是,这是一个共享提交;在 Git 中,术语分支有点棘手。(请参阅我们所说的“分支”究竟是什么意思?

无论如何,我认为如果你忘记了它的git pull存在,它会有所帮助。所有git pull不为你运行两个不同的Git命令。你最好,直到你对 Git 有很好的经验,使用单独的git fetchgit merge命令。(请注意,git pull --rebase将第二个命令切换为git rebase,但我们不会在此处详细介绍。) 使用git pull运行其他两个命令有几个问题。其中之一是git pull使用一个奇怪的只拉语法,从不同的所有其他Git命令,其中包括git mergegit pull运行。也就是说git pull origin xyz,您将运行git merge origin/xyz. 要查看那是什么,您可以运行git log origin/xyz、 或git show origin/xyz等。这些总是拼写为origin/xyz,带斜杠,除非使用git pull— 所以不要使用git pull. :-) 让我们把它分成两个单独的命令。

  • git pull运行的第一个命令是git fetch,您可以随时运行它:git fetch调用其他一些 Git,询问它为您提交了什么,以什么名称(通常是分支和标签名称)。它收集了这些提交(和他们的课程的文件),并为每一个自己的分支名称,创建或更新您的远程跟踪名称。所以这origin/master就是来自,例如:git fetch看到他们有一个master,他们的主人是提交badf00d或其他什么,并创建或更新你origin/master要记住:origin的主人是badf00d我最后一次检查。

  • git pull为您运行的第二个命令是所有有趣的操作所在。第二个命令应该在任何旧的时间运行,在任何旧的分支上,因为无论你运行 Git 的第二个命令,这个命令都必须在正确的分支上:你想合并的那个,或者你想合并的那个变基。我发现在这里使用单独的命令git merge会有所帮助,因为它会更清楚地影响当前分支,即使您将命名类似origin/master.

现在我们知道--allow-unrelated-histories确实是一个选项git merge,让我们深入研究git merge,看看它的作用。首先,我们将看看它在一个共同起点的情况下会做什么,然后再看看它在没有共同起点的情况下会做什么。

合并是关于从一个共同的起点开始合并变化

考虑你上面引用的图表,我将重画一点:

     A--B--C   <-- topic
    /
D--E--F--G   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

这表明无论谁一直在工作topic,他们都是从检查 commit 开始的E。也许,在那个时候,提交E了最后一次提交 master

D--E   <-- master, topic
Run Code Online (Sandbox Code Playgroud)

从那以后,有人在 上添加了两个提交master,分别是FG,而有人(可能是其他人)在 上添加了三个提交topic,现在是A-B-C链(A其父提交是E)。

每个提交代表所有源文件的完整快照。因此,commitE中的所有文件——嗯,当你或任何人提交时,它拥有的所有文件——E以这种形式永久保存。您或任何人对该保存状态中的任何文件进行的任何更改并保存在,例如, commit A,都会导致这些文件在A. 任何不变的文件A只是恰好匹配的文件E

为简单起见,我们假设这里有两个人,“你”和“他们”,你在 上进行了更改master,最终导致 commit G。然后他们A通过了C。所以你和他们都从在 commit 中永远保存的任何东西开始E。你最终得到了你永远保存在G. 因此,Git 可以通过简单的比较 commit与 commit来找出您更改的内容。同样,他们最终在,因此 Git 可以通过类似的简单比较vs来找出他们改变了什么:git diffEGCgit diffEC

  • git diff --find-renames hash-of-E hash-of-G: 你改变了什么
  • git diff --find-renames hash-of-E hash-of-C: 他们改变了什么

Git 然后从 commit 中检出文件E,即你们俩开始的文件,您对这些文件的更改组合起来,并使用其中的组合更改构建一个新的提交。这决定了哪些文件/内容进入 commit H

     A--B--C   <-- topic
    /       \
D--E--F--G---H   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

新提交H的第一个父级是G,它是master您检出的分支的尖端。它的第二个父级是C,您告诉git merge它合并的那个。

请注意,当 Git 进行所有这些更改组合时,它可以轻松处理两个分支提示中完全相同的所有文件,因为无论合并基础中的情况如何,这两个提示都匹配,因此两者文件是相同的,任何一个都可以正常工作。如果您更改了文件 X 而他们没有更改,并且他们更改了文件 Y 而您没有更改,那么它也很容易,因为同样,它可以只使用您或他们的这些文件的版本。只有当你们以不同的方式接触同一个文件时,Git 才必须努力工作。

无关的历史

当两组提交之间没有共同的联系时,就会发生相关的历史记录:

A--B--C   <-- master (HEAD)

J--K--L   <-- theirs
Run Code Online (Sandbox Code Playgroud)

您的提交从 开始C并向后工作,结束于A。之前没有提交AA没有父母。

在这种情况下,他们的提交开始于L(我跳过了很多字母以留出空间来插入我们的合并)。 L的父母是K,而K父母是J,但J也没有父母。所以根本没有共同的起点。

如果你告诉 Git 合并这些,Git 只是假装有一个。假装起点没有文件。Git运行:

  • git diff empty-tree hash-of-C: 你改变了什么
  • git diff empty-tree hash-of-L: 他们改变了什么

当然,你从这个差异中改变的是你添加了每个文件(在你的 commit 中C)。他们改变的是他们添加了每个文件(在他们的 commit 中L)。

如果文件有不同的名称,它们就是不同的文件,没有问题:Git 取你的,或者他们的。如果它们具有相同的名称,但内容完全相同,那么这里也不存在冲突:Git 可以使用您的(或他们的)。您和他们的名称相同但内容不同的所有文件都会出现问题。就 Git 而言,你从头开始创建你的,他们也是如此,所以一切都是冲突的。您必须选择获胜的内容或从“所有冲突”输入中构建一个新文件。

一旦您解决了这些冲突中的任何一个并运行git merge --continue以完成 Git,Git 会像往常一样进行合并提交:

A--B--C--D   <-- master (HEAD)
        /
J--K---L   <-- theirs
Run Code Online (Sandbox Code Playgroud)

新的承诺有两个父母,CL,并保存,永远,快照,你建通过固定的矛盾了Git报道,否则任何文件都完全一样C,并L或只 C,或仅在L

(“永远”有点太强了:保存的文件的持续时间与提交本身一样长。但是,默认情况下,每个提交都将永远存在。如果您让提交消失,文件也会如此。)


Mar*_*ger 2

合并不相关的历史是这样的:你想象每条历史的根源之前都有一个共同的祖先,但没有任何内容。这就是合并基础。