git-subtree没有保留历史记录所以我无法推送子树更改,我怎样才能解决这个问题/将来避免这个问题?

时间:2011-04-22 21:38:54

标签: git git-subtree

我一直在使用git-subtree扩展(https://github.com/apenwarr/git-subtree)来管理主项目中的子项目。当我尝试从主项目中拆分对子项目所做的更改时,它正在完成我想要的事情。

e.g。早些时候我已经完成了

git subtree add -P Some/Sub/Dir --squash git@gitserver:lib.git master

将库代码引入我们主项目中的Some / Sub / Dir。这里的一切都很棒,所以我把我的更改推到了我们的中央主要项目裸git repo。然后我决定在Some / Sub / Dir中对我的本地版本的lib进行更改,提交它,然后将其拆分以将其推回到lib.git repo

git subtree split -P Some/Sub/Dir -b some_branch

一切都按预期工作。不再需要回购的本地副本我删除了它。

从我们的中央仓库克隆了一个新的repo副本后,我对Some / Sub / Dir中的lib进行了一些更改,并决定将这些更改拆分出来并将它们推回到lib.git存储库。我尝试使用与以前相同的子树分割命令,但这次我最终得到以下输出:

1/      3 (0)
2/      3 (1)
3/      3 (1)
fatal: bad object d76a03f0ec7e20724bcfa253e6a03683211a7bb1

d76a03f0ec7e20724bcfa253e6a03683211a7bb1来自我添加子树时:

commit 43b3eb7d69d5eb64241eddb12e5bd74fd0215083
Author: Ian Bond <ibond@onezero.com>
Date:   Fri Apr 22 15:06:50 2011 -0400

    Squashed 'Subtree/librepoLib/' content from commit d76a03f

    git-subtree-dir: Subtree/librepoLib
    git-subtree-split: d76a03f0ec7e20724bcfa253e6a03683211a7bb1

实际上是指lib.git repo中的提交。


我能够拼凑起来(我是一个git noob,所以我可能错了,忽略了一些东西,或者在这里使用了不正确的术语),是'git subtree add --squash'会带来从远程lib.git repo到当前仓库的整个历史记录,将其压缩到单独的提交中,然后将该提交添加到工作分支中。 lib.git提交历史记录保留在当前的repo中,但是它们是悬空提交,因为除了通过压缩提交的文本之外它们实际上没有被引用。只要那些悬空提交仍然存在,git-subtree就可以使用它们来执行拆分,但是由于推或拉不包含悬空对象(或者如果我运行gc并完全修剪悬空对象),那些悬空提交将丢失并且git-subtree不再具有执行拆分的必要信息。

我添加了a script,它将完全重现我一直存在的问题。


我的问题是:

1)我可以做些什么来处理我现在有子树的现有情况,我想要合并回原始仓库,但不再有任何将它们链接在一起的历史记录。我目前的想法是做类似的事情:

git subtree split -P Some/Sub/Dir 43b3eb7^.. --ignore-joins -b splitBranch

将“git子树添加”后的所有历史记录拆分并将其合并回原始仓库(幸好自添加以来没有任何变化)。这是最好的方式吗?关于如何执行合并的任何建议?

2)我能做些什么来让git-subtree按预期工作?我相信如果我省略'git subtree add'上的--squash参数,那么一切都会起作用,但这会导致一堆无关的历史注入我的仓库。有没有办法保持所需的提交(最好不保留库的整个历史记录)?

2 个答案:

答案 0 :(得分:14)

git subtree split的目的是在子树的原始历史记录之上创建一些新的提交(表示最初在子树的本地目录中进行的“本地”更改)。由于它直接涉及子树的原始历史记录(作为第一个重写的本地提交的父提交,触及子树),如果没有子树的原始历史本身存在,则无法进行拆分操作。

考虑一下git subtree split生成的历史将对您做什么。您可能希望将其推送到存储库,您可以将其合并到“上游”历史记录的其余部分。为了使合并操作有意义,拆分历史记录需要基于原始历史本身 1

安排用户拥有子树原始历史记录的最可靠方法可能是在文档中发布子树上游存储库的URL,让他们为它定义一个遥控器(拥有“无关”的遥控器是完全没问题的。在一个存储库中)。 E.g。

  

如果您需要使用Some/Sub/Dir的“上游”(以引入外部更改或推送本地更改),请在使用git subtree之前为库的存储库定义和更新远程:

git remote add lib git@host:the-lib-repository &&
git fetch lib

即使您没有使用--squash,也需要执行类似的操作,因为用户需要知道从哪里获得新的上游提交(以及(最终)推送新的拆分生成的提交)。 / p>

使用--squash在主项目中为您提供“干净”的历史记录,这意味着只有那些需要处理子树“上游”的用户实际上必须在其存储库中拥有其对象。


您似乎对对象模型有了很好的理解。你是正确的git subtree add --squash拉入的历史将变成悬空 2 git subtree split仍然可以使用它直到它被修剪掉。

(参考您的复制脚本)
您只能在repoMainClone中成功拆分,因为本地克隆会自动硬链接(或复制).git/objects/中的所有文件(从而可以访问repoMain的悬空副本(或几乎悬挂来自repoLib 2 )对象,而不是使用通常的“pack协议”传输(这会将传输的对象限制为仅传输的refs所需的对象;即从{省略任何内容) {1}})。您的repoLib实际上等效克隆repoMainPullfile://"$(pwd)"/repoMain repoMainCloneFile网址强制本地克隆使用基于包的转移,而不仅仅是链接/复制所有内容。)


1 实际上,您可以直接合并不相关的历史记录,但是您失去了进行三向合并的能力(因为没有共同的祖先)。这将是一个很大的牺牲。

您提议的file://(其中43b3eb7是由git subtree split -P Some/Sub/Dir 43b3eb7^.. --ignore-joins …产生的综合提交)会生成无关的历史记录(除非git subtree add --squash …表示43b3eb7..,因此需要43b3eb7^ “43b3eb7的第一个父母”和43b3eb7没有父母。我不确定git subtree split是否设计为采用这样的范围。 git subtree split的文档只是说<commit>,但从未真正提及其目的。读取代码显示它默认为HEAD,这可能表明它是一个单一的提交,指定应该处理以进行拆分的历史记录的“提示”。此外,打开调试输出会显示消息incorrect order:,这可能表示使用范围参数将拆分操作置于意外情况(它希望在处理提交之前处理了提交的所有父级)本身,但该范围确保从不处理43b3eb7(它是子树合并提交的父级)。我认为您可以使用--ignore-splits,如果您想要生成“无关”历史记录并尝试以某种方式使用它,请忽略该范围:git subtree split -P Some/Sub/Dir --ignore-joins …

2 它们实际上并不是在git subtree add --squash之后立即悬空,因为它们仍被FETCH_HEAD引用。然而,一旦完成无关的提取,它们将变得真正悬空。

答案 1 :(得分:1)

@Chris Johnsen的答案非常正确,它解释了为什么分裂在克隆中起作用而不是拉动情况。

对于问题中提供的问题并在@Chris Johnsen的答案的脚注2中解释,我可以确认git subtree split -P Some/Sub/Dir -b splitBranch --ignore-joinsgit subtree split -P Some/Sub/Dir -b splitBranch 43b3eb7..在实际上产生了相同的提交和相同的分支,这可以反映出在本地仓库中完成的修改,但不能推送到原始的repoLib仓库,因为它们没有共同的访问者,即使git diff显示d76a03f0ec7e243b3eb7d69d是相同的。

因此,为了在拉动情况下使子树推送工作,必须添加和提取原始的repoLib远程仓库以获得d76a03f0ec7e2 exseited以生成与原始repoLib具有公共访问者的分支。 / p>

原始的重现脚本无法在linux下顺利运行,这是一个新的:http://pastebin.com/3NAQKEz9