我一直在寻找有关这个问题的更多信息几个小时了。我正在清理存储在我的PC上的一些旧的git存储库,并确保它们完全提交并在我删除本地副本之前向上游推送到GitLab。大多数回购,当我试图拉动时,给出错误"致命:拒绝合并不相关的历史"。在Google上搜索此错误会显示一些StackOverflow帖子,建议我使用" - allow-unrelated-histories"解决这个问题,但这并不能帮助我理解为什么它会在一开始就发生。
我从GitLab克隆一个较小的repos,并通过文件比较所有工作文件来执行文件。它们完全相同。我用另一个小的回购做同样的事情并得到相同的结果。我决定查看日志。本地和克隆的副本包含完全相同的提交集,而本地具有空的临时区域。
这是当我注意到本地和克隆的repos每次提交都有不同的作者信息时。考虑到其他一切都是一样的,包括提交时间到第二个,我只能假设这是问题所在。我不明白为什么回购的本地和上游副本有不同的作者信息。根据我的知识,我没有积极地改写我的本地历史,而GitLab这样做本身就具有破坏性。
tl;博士:Git拒绝合并不相关的历史记录。研究和比较本地和远程repos,两者都与本地和远程之间每次提交时作者信息不同的奇怪例外相同。不确定为什么或正确的解决方案是什么。
答案 0 :(得分:1)
鉴于您正在做的事情 - 组合存储库,其中有人将所有提交重新复制到新的哈希ID - 这是正常的。它也基本上是不可恢复的,这就是为什么使用git filter-branch
重写所有历史记录有点问题。
"无关的历史记录"意思是:有两个历史 - 两个Git提交图中的提交集合 - 彼此之间没有链接。关键是要了解Git提交图的工作原理。
Git存储库中的历史是(是?)提交。每个提交都有一个哈希ID;这是一个非常真实的意义上的真实姓名"提交。提交本身实际上的内容相当小。这是Git自己的Git存储库提交:
$ git cat-file -p HEAD | sed 's/@/ /'
tree 4ec41fbdfd4e9569fceb3e25d4c1945f1944af0e
parent e66e8f9be8af90671b85a758b62610bd1162de2d
author Junio C Hamano <gitster pobox.com> 1528116101 +0900
committer Junio C Hamano <gitster pobox.com> 1528116101 +0900
Git 2.18-rc1
Signed-off-by: Junio C Hamano <gitster pobox.com>
此提交的哈希ID为3e5524907b43337e82a24afbc822078daf7a868f
。无论谁有任何Git-repository-for-Git提交,如果他们有这个提交,他们就有那个大丑陋的哈希ID,而没有其他哈希ID。如果他们有这个哈希ID,它代表的是这个提交,而不是其他提交。但是看一下提交内容的第二行,它说 parent是另一个很大的丑陋哈希。此哈希ID标识Git的Git存储库中的另一个提交;我的这个Git存储库的副本也有这个提交。这个父提交有另一个哈希ID-well,两个,因为它是 merge 提交 - 并且这些提交具有他们的父项的哈希ID,依此类推。 / p>
如果我们将这些绘制为图形,每个提交中的箭头指向其父级,我们就会得到类似的东西 - 好吧,让我们在这里使用一个很小的,三个提交的存储库:
A <-B <-C
Git需要知道 last 哈希ID;这是分支名称的来源:
A <-B <-C <--master
Git使用分支名称找到的 last 哈希ID来查找每个提示提交。该提交有一个父ID,Git用它来查找另一个提交,它有一个父ID,Git再次使用,依此类推。当Git到达像我们的提交A
的提交时,该操作会停止,该提交具有否父ID,因为它是图的结尾。这些提交称为 root commits 。
当我们添加更多提交并将所有这些提交链接起来时,我们会得到一些更复杂的内容,例如:
o--o--o---o--o <-- master
\ /
o--o
我们不需要内部箭头,因为我们知道他们总是倒退:孩子承认了解他们的父母,但父母承诺不了解他们的孩子。
在一个大型存储库中,我们得到了一个非常大的图表。但有时候,取决于我们构建我们的图表的方式 - 特别是如果我们使用git add <remote>
和git fetch
- 我们可以使用多个根获取存储库承诺。例如,在我们的小型三次提交存储库中,我们可能会引入另一个存储库,例如,四次提交:
A--B--C <-- master
D--E--F--G <-- other/master
这些提交是历史记录,但现在有两个断开连接的历史记录!从C
开始,我们回到A
,然后停止。从G
开始,我们回到D
,然后停止。 (请记住,这些易于阅读和理解的单字母代表实际的哈希ID,它们似乎是随机的。)
如果你要求Git合并这些,那么Git所做的就是暂时构成一个虚假的伪装提交,其中包含 no 文件,并将其用作共同的祖先:
*--A--B--C <-- master
\
D--E--F--G <-- other/master
现在,历史记在一起,假冒祖先暂时假装成为合并目的。 Git现在可以将提交*
的空树与提交C
中的源树进行区分;提交C
中的所有文件都是新添加的。 Git还可以在提交G
中针对源树区分空树,再次,新添加的所有文件。
如果这些不相关的历史记录是大多数包含相同文件的提交,则结果是一组巨大的&#34;添加/添加冲突&#34;,因为两个提示提交主要是相同的文件。您可以选择执行此操作,并手动解决所有冲突,然后提交。 Git删除了假的临时root提交(实际上它甚至从未将它放入 - the empty tree is present in all Git repositories,因此它只是直接使用它),你得到:
A--B--C----H <-- master
/
D--E--F--G <-- other/master
现在提交H
通过加入其他不相交的子图来关联这两个历史。
研究和比较本地和远程repos,两者都与本地和远程之间每次提交时作者信息不同的奇怪例外相同。不确定为什么或正确的解决方案是什么。
如果树全部相同,则表示有人专门运行git filter-branch
以修改作者信息。在应用一些文件管理器之后,filter-branch
对复制提交的做法是新提交。如果您选择在某些或所有提交中重写作者姓名的过滤器,则新副本是不同的提交 - 它们具有不同的author
行 - 因此它们具有不同的哈希值。如果这改变了存储库中的根提交,那么即使没有其他提交更改,所有其他复制的提交都必须记录它们的新(不同)父哈希。
例如,在我们的小型三提交存储库中,复制A
但更改作者会产生一个新的哈希,我们可以调用A'
:
A--B--C <-- master
A'
当我们下次复制B时,保持所有内容相同(即使是作者),我们仍然必须将A'
的ID放入副本中,以便复制将指回A'
:
A'-B'
复制C同样会强制改变,如果没有别的话,就会改为父线,给我们:
A--B--C <-- master
A'-B'-C' [just built]
filter-branch
做的最后一件事是将所有标签移动到指向新副本:
A--B--C <-- refs/original/refs/heads/master (to be deleted)
A'-B'-C' <-- master
删除refs/original/
剩余部分以忘记原始提交后,您将留下一个存储库,其中所有提交具有不同的作者,因此具有不同的哈希ID,因此是不同的提交。< / p>
同样,提交是历史。他们的哈希ID是Git关心的。复制存储库(通过克隆)并使用其哈希ID复制提交。通过git filter-branch
或类似方式将存储库复制到 new (不同)提交,最终得到一个新的,不同的存储库,具有不同甚至可能完全不相关的历史记录。 (如果两个存储库保持其根提交不变,则历史记录将相关。)
那些拥有旧存储库的人通常必须放弃他们的存储库以支持新的存储库,或者决定完全忽略新的存储库。如果您知道并接受后果,请仅使用git filter-branch
。