我有一个包含许多子模块的项目。但是,事后看来,其中一些子模块不应该是子模块,因为它们并非意味着或永远不会在另一个项目中使用,并且我偶尔会在它们之间传递代码。这个项目是子模块中的实验的两倍,所以我对此有些疯狂。
我想知道是否有一种方法可以将子模块转换为常规目录,保持更改历史记录,但重写主项目的历史记录,以便将它们视为常规目录。
我已经看到了有关子树合并的内容,但是我希望找到一种重写提交的方法,以便文件路径以子模块的前缀。
答案 0 :(得分:1)
通常这是一个难题。在某些情况下,或者应用子模块内容的简并方式使之变得更容易。一种折衷方案(可能不够,也可能不够好)是简单地将两个提交历史记录合并到一个存储库中,然后使用git filter-branch
或仅使用自动化git replace
进行一些稍有改动的转换(尽管如此使用或滥用git replace
可能会导致性能问题。
这是基本情况,即在考虑推广问题之前,您需要了解的一些心理工具。每个存储库都包含一个提交图:DAG个提交,在图中找到了各种入口点,并通过分支名称保留了这些入口点。在使用子模块的每个提交中,超级项目的提交都具有对子模块的提交之一的引用。这些引用作为“ gitlink ”类型的条目位于“树”对象中。关于保留提交,Git实际上不会对其进行检查,因为假定它们是在某些 other 存储库(子模块)中标识提交的。
您可以轻松地使用git fetch
将整个子模块的图提取到超级项目存储库中,从而将子模块的分支名称更改为超级项目中的不同名称。 (git fetch
的默认设置是生成远程跟踪名称,但有些偷偷摸摸,您可以轻松地使用其他名称空间。对于我建议的解决方案,无论如何远程跟踪名称都是可以的。)但是,仅仅是您有两个断开连接的DAG。超级项目提交仍然只有带有gitlink条目的树,这些条目引用另一个DAG中的提交。这些gitlink条目将不保留提交reachable,因此您必须保留两组名称。除了将所有提交都包含在一个存储库数据库中之外,这根本没有任何改善(并且可能会变得更糟,因为现在很难使用它。)
这是 general 问题:Git存储的是这些提交。我们没有单独的项目可以称为“历史”; Git存储库中的历史记录是(是?)存储库中的提交。如果我们绘制提交,我们可以从视觉上看到问题。让我们在超级项目中将其简化为从A
到E
的五个提交。大写字母代表实际的哈希ID(对人类没有用):
A--B--C <-- master
\
D--E <-- dev
现在,由于它是子项目,所以我们使用小写字母在子项目中放置六个提交:
a--b--c--d <-- master
\
e--f <-- issue213
一些超级项目提交(也许是全部),但为简单起见,我们只说C
和E
,在其中包含了对某些子项目提交的引用,因此,如果我们将所有子模块的提交使用名称sub/*
来记住分支提示,从而提交到超级项目中,我们得到以下信息:
A--B--C <-- master
\ :
D÷-E <-- dev
: :
: :
: :
: :
a--b--c--d <-- sub/master
\
e--f <-- sub/issue213
假设我们现在以某种方式用树的实际提交替换提交C
(其gitlink到b
)和E
(其gitlink到d
) ,直接引用树对象以提交b
和e
。我们将这些提交称为C'
和E'
。从技术上讲,这在Git中是可能的-我们只对想要的树进行新提交C'
和E'
,分别使用b
和d
中的树,然后进行更改名称master
和dev
来引用提交C'
和E'
。如果我们删除sub/*
名称,我们将拥有:
A--B--C' <-- master
\
D--E' <-- dev
如果现在使用git checkout master
,我们将获得一个很好的工作树,其中充满了原始C
plus 子模块中的内容,该子模块是从其提交中获得的正如我们从图中看到的那样,原始b
使用的C
。
类似地,如果现在git checkout dev
,我们将得到一个很好的工作树,其中充满了原始E
中的内容以及子模块的提交d
中的内容。
这个新的经过修改的存储库中的树包含通过检出C
-and-submodule或E
-和而获得的快照的所有源-子模块。但是 commits 是子模块中的,即d
导致历史回到c
导致历史回到b
回到a
,再加上整个issue213
分支,其中包括f
引回到e
引回到c
……好吧,这些提交消失了!没有任何东西可以代表它们了。
此外,没有可以插入它们的地方。在包含A
至E
(全部为大写)的图形中,哪里a
至f
(全部为小写) fit?唯一的答案是“无处”:他们不能去任何地方。
现在,在特定情况下,我们可以发明一个答案。我们可以在现有提交之间插入 new 提交,以便新的提交在更新子模块文件时将超级项目的文件保留在原位。只要子模块图的拓扑类型“适合”超级项目图的拓扑类型,这就是实用的。 (如果有多个子模块,则需要所有图的并集的完整拓扑排序。)不能保证存在这种情况,并且很容易得出不存在这种情况的情况:
A--B--C <-- master
: :
: :
:
: :
: :
a--b--c <-- sub/master
在这里,超级项目提交A
引用子项目中的 last 提交,而超级项目提交C
引用子项目中的 first 提交。子项目。这些图拓扑是不可组合的。 1 但是,可能是您的拓扑是这种情况,在这种情况下,您可以根据需要插入提交节点,如果您想组成一个新的图来充当适当的超集。我不知道要执行此操作的程序。
1 我不确定“ composable”是否是一个好词,但是我没有时间进行文献检索。我的意思是,合并DAG可能会导致周期,因此我将此类存储库称为“不可组合的”。例如,另请参见Efficient algorithm for merging two DAGs。
您将不得不编写一些代码。这是不平凡的,需要一些图论。它不是特别复杂,但是我绝对不会在这里做。
在上面的示例中,更简单的工作是可以自动化的:它可以遍历所有提交,找到它们的提交,然后将提交C
替换为C'
,将E
替换为E'
。子模块gitlinks,并使用git replace
将具有子模块的树对象替换为使用子模块树的树对象。实际上,这将替换树对象而不是提交对象,因此历史记录确实仍然是以前的样子,但是现在您将拥有大量替换对象。此外,克隆存储库不会克隆替换对象,因此现在该使用git filter-branch
重写所有提交了。
我没有像这样使用git replace
的便捷方法,但是您可能希望通过将git replace --edit
变量设置为一个可以找到并替换gitlink条目。 (编写这样的脚本会有些乏味,但在技术上并不困难。)
由于GIT_EDITOR
尊重替换, 2 并且不需要其他更改,因此您只需运行git filter-branch
即可执行所有提交替换。 (请注意:在专门为替换和筛选分支进行实验的克隆上执行此操作,以便将其弄乱后可以重新开始。)然后,您可以删除所有替换引用({{1} }),因为它们不再需要了,现在只是让Git变慢了。
2 好吧,除非以git filter-branch --tag-name-filter cat -- --branches --tags
运行,否则它会起作用。
答案 1 :(得分:0)
我在处理子模块方面经验不足,但这就是我要做的事情:
也许不是最好的方法,但是如果我想将一个不同的项目引入到我的项目中,同时又要使事情分开,那将是我要做的事情。
**甚至可能吗?我绝对需要获得更多有关子模块之类的动手经验。
答案 2 :(得分:0)
如果只想保留每个子模块的单个分支的历史记录,则使用git subtree
相当容易:
git fetch <path/to/submodule> HEAD
git rm <path/to/submodule>
git commit -m "Prepare to integrate Git submodules' history into repository"
git subtree add --prefix=<path/to/submodule> FETCH_HEAD
这将集成子模块当前已检出修订版的历史记录。确保之前处于清洁状态,例如运行git submodule update
并使用git status
仔细检查。
您将获得两个提交:第一个删除子模块,第二个将先前的历史记录(现在存储在FETCH_HEAD
中)整合到存储库中。没有简单的方法(至少我不知道)通过“原子”提交来做到这一点。为此,您需要弄乱Git的管道命令。
如果需要集成多个子模块的历史记录,建议将所有删除操作放入第一个提交中,并将所有集成操作放入第二个提交中。在这种情况下,您需要通过其他方式记住提取的HEADS。
注意:
尽管git subtree
位于上游Git的./contrib
之内,但自v1.9.1(2014年3月)以来,它似乎(至少)在Debian上可用。