将包含子模块的子目录拆分成一个独立的git仓库

6
作为之前已经提出的问题detach-subdirectory 的一个子集,并考虑到尽管有很多关于拆分和合并git存储库的过程的问题,但我找不到一个涉及在存在子模块时进行拆分的主题。
因此,在接下来的情况下:
.git/
.gitmodules
folder/
    data/
    content/
        other_data/
        submoduleA/
        submoduleB/

我希望获得以下结构的两个代码库:
.git/
data/

并且

.git/
.gitmodules
content/
    other_data/
    submoduleA/
    submoduleB/

第一个情况不是问题,可以使用detach-subdirectory中描述的方法轻松解决。

然而第二个情况就不太一样了。子模块的存在以及.gitmodules包含folder/content/submoduleAfolder/content/submoduleB的完整路径会导致部分历史记录不一致,因为.gitmodules引用了不存在的目录结构(一旦使用filter-branch后)。

所以我想知道是否有一种方法可以在不造成不一致历史的情况下完成此操作。

3个回答

6

我有和Unode一样的问题,以下是我用的解决方法:

git clone git@github.com:kdeldycke/kev-code.git
cd kev-code
git filter-branch --tree-filter "test -f ./.gitmodules && mv ./.gitmodules ./cool-cavemen/gitmodules || echo 'No .gitmodules file found'" -- --all
git filter-branch --force --prune-empty --subdirectory-filter cool-cavemen --tag-name-filter cat -- --all init..HEAD
git filter-branch --force --tree-filter "test -f ./gitmodules && mv ./gitmodules ./.gitmodules || echo 'No gitmodules file found'" -- --all
git filter-branch --force --tree-filter "test -f ./.gitmodules && sed -i 's/cool-cavemen\///g' ./.gitmodules || echo 'No .gitmodules file found'" -- --all
git remote rm origin
rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune
git remote add origin git@github.com:kdeldycke/cool-cavemen.git
git push -u origin master --force --tags

如您所见,诀窍是临时重命名.gitmodules文件,并使用sed重写其内容。您可以在我的博客中获取所有细节和此过程的上下文


在所有filter-branch命令中添加 --tag-name-filter cat 选项以在过滤后保留标签。 - kolyuchiy
感谢您记录这个。我必须调整两件事才能使其正常工作。首先,我认为您假设init..HEAD范围内有一个标记为“init”的初始提交。其次,我不得不在sed命令中添加-e,即:sed -i -e 's/cool-cavemen\///g' ./.gitmodules - Von

2
我怀疑(未经测试)第二个git filter-branch命令将有机会修改新存储库的每个提交的.gitmodules内容。
但实际上,早在2009年就已经讨论了git submodule split命令

建议用法:

git submodule split [--url submodule_repo_url] submodule_dir \
    [alternate_dir...]

用新创建的子模块替换submodule_dir,同时保留submodule_dir的所有历史记录。此命令还将重写当前存储库历史记录中的每个提交,以包括sumodule_dir的正确修订版本和适当的.gitmodules条目。
然而,我在latest what's cooking中没有看到它。 建议补丁中的脚本可以给您一些关于更新.gitmodules文件所需的树状重写的思路。

使用第二个git filter-branch命令,我能够使用sed命令重写.gitmodules文件,但是实际的子模块文件夹仍然没有改变(无论是使用index-filter还是tree-filter)。只有subdirectory-filter能够改变它们,但是这样会删除.gitmodules文件。看起来git submodule split命令正好符合我的意图,但是阅读线程后,我觉得它存在一些问题,所以我不敢使用它。 - unode
@Unode:我明白。我认为这个特定的补丁现在没有在积极开发中。 - VonC

0
进一步解释凯文的回答:假设从未存在过除 cool/cavemen 之外的子模块 - 如果文件夹被分离 (否则需要更复杂的 .gitmodules 编辑来删除那些额外的部分),可以使用一个 index-filter 在一步中 更快速地 完成此操作:
$ git filter-branch --subdirectory-filter cool/cavemen --index-filter $'
hash=$(git rev-parse --verify $GIT_COMMIT:.gitmodules 2>/dev/null) &&
 git update-index --add --cacheinfo 100644 $(git cat-file -p $hash |
 sed \'s/cool\\/cavemen\\///g\' | git hash-object -w --stdin) .gitmodules ||
true' --tag-name-filter cat --prune-empty -- --all

作为附加好处,如果在每个修订版本或分支中都不存在cool/cavemen,那么只会查看包含cool/cavemen的那些修订版本或分支。
如果是这种情况,您可能想要运行以下命令以删除未更改的引用:
$ git for-each-ref --format='%(refname)' | 
 grep -vF "$(git for-each-ref --format='%(refname)' refs/original |
 sed 's/refs\/original\///g')" | xargs -n 1 git update-ref -d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接