问题 将Git子模块转换为子树


如何将git子模块(本地文件系统中的文件夹作为远程)转换为git子树,最好保留子模块的提交历史记录?


2488
2018-01-29 12:52


起源



答案:


以下bash脚本基于Alexander Mikhailian的帖子(http://mikhailian.mova.org/node/233)。我稍微修改了它来打电话 subtree add 代替 read-tree。它将从中获取子模块列表 .gitmodule 并提取模块的前缀,名称和URL。然后删除每个子模块并将它们作为子树添加回同一位置。它还将每个子模块的远程添加为远程,因此您可以通过稍后提供其名称而不是其URL来更新子树(即 git subtree pull -P Foo Foo master --squash 代替 git subtree pull -P Foo https://example.com/foo.git master --squash

你可以删除 --squash 如果要将子树的完整历史记录导入存储库,请参数。同 --squash,只会将子树的头部导入到您的存储库中。这应该是大多数人想要的。

有关更多信息,您可能希望阅读atlassian的这篇文章: http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/

#!/bin/bash -x
# extract the list of submodules from .gitmodule
cat .gitmodules |while read i
do
if [[ $i == \[submodule* ]]; then
    echo converting $i

    # extract the module's prefix
    mpath=$(echo $i | cut -d\" -f2)

    # skip two lines
    read i; read i;

    # extract the url of the submodule
    murl=$(echo $i|cut -d\= -f2|xargs)

    # extract the module name
    mname=$(basename $mpath)

    # deinit the module
    git submodule deinit $mpath

    # remove the module from git
    git rm -r --cached $mpath

    # remove the module from the filesystem
    rm -rf $mpath

    # commit the change
    git commit -m "Removed $mpath submodule"

    # add the remote
    git remote add -f $mname $murl

    # add the subtree
    git subtree add --prefix $mpath $mname master --squash

    # fetch the files
    git fetch $murl master
fi
done
git rm .gitmodules

10
2017-07-26 19:39



这里可以找到如何在所有分支上进行这些转换的示例;转换代码略有调整;还有一些额外的代码用于我的镜像目的 - 忽略它或随意使用它。 github.com/eallik/curry-kics2-sync - Erik Allik
而不是回显您可能想要考虑使用的所有命令,例如 #!/bin/bash -x 作为shebang。 - Bjoern Dahlgren
如果子模块url中缺少空间,则子模块行的url将无效,使用murl = $(echo $ i | cut -d \ = -f2 | xargs)将更安全 - Wuvist
我是否正确理解这将在将子模块转换为子树时拉出最新的提交?因此,如果我的子模块引用了旧的提交以获得“兼容性原因”,那么这个脚本会破坏它吗? - Nikita240


答案:


以下bash脚本基于Alexander Mikhailian的帖子(http://mikhailian.mova.org/node/233)。我稍微修改了它来打电话 subtree add 代替 read-tree。它将从中获取子模块列表 .gitmodule 并提取模块的前缀,名称和URL。然后删除每个子模块并将它们作为子树添加回同一位置。它还将每个子模块的远程添加为远程,因此您可以通过稍后提供其名称而不是其URL来更新子树(即 git subtree pull -P Foo Foo master --squash 代替 git subtree pull -P Foo https://example.com/foo.git master --squash

你可以删除 --squash 如果要将子树的完整历史记录导入存储库,请参数。同 --squash,只会将子树的头部导入到您的存储库中。这应该是大多数人想要的。

有关更多信息,您可能希望阅读atlassian的这篇文章: http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/

#!/bin/bash -x
# extract the list of submodules from .gitmodule
cat .gitmodules |while read i
do
if [[ $i == \[submodule* ]]; then
    echo converting $i

    # extract the module's prefix
    mpath=$(echo $i | cut -d\" -f2)

    # skip two lines
    read i; read i;

    # extract the url of the submodule
    murl=$(echo $i|cut -d\= -f2|xargs)

    # extract the module name
    mname=$(basename $mpath)

    # deinit the module
    git submodule deinit $mpath

    # remove the module from git
    git rm -r --cached $mpath

    # remove the module from the filesystem
    rm -rf $mpath

    # commit the change
    git commit -m "Removed $mpath submodule"

    # add the remote
    git remote add -f $mname $murl

    # add the subtree
    git subtree add --prefix $mpath $mname master --squash

    # fetch the files
    git fetch $murl master
fi
done
git rm .gitmodules

10
2017-07-26 19:39



这里可以找到如何在所有分支上进行这些转换的示例;转换代码略有调整;还有一些额外的代码用于我的镜像目的 - 忽略它或随意使用它。 github.com/eallik/curry-kics2-sync - Erik Allik
而不是回显您可能想要考虑使用的所有命令,例如 #!/bin/bash -x 作为shebang。 - Bjoern Dahlgren
如果子模块url中缺少空间,则子模块行的url将无效,使用murl = $(echo $ i | cut -d \ = -f2 | xargs)将更安全 - Wuvist
我是否正确理解这将在将子模块转换为子树时拉出最新的提交?因此,如果我的子模块引用了旧的提交以获得“兼容性原因”,那么这个脚本会破坏它吗? - Nikita240


由@GaspardP修改的Alexander Mikhailian的剧本对我不起作用。

我修改了它,并做了改进。现在,新的子树将指向与旧子模块相同的提交。以前,该脚本只是从目标存储库下载最新的提交,可能会导致兼容性问题。

https://gist.github.com/Nikita240/0c98cea8f53a15e69699cd8bc40657c4

#!/bin/bash -x
# This script will convert all your git submodules into git subtrees.
# This script ensures that your new subtrees point to the same commits as the
# old submodules did, unlike most other scripts that do this.
# THIS SCRIPT MUST BE PLACED OUTSIDE OF YOUR REPOSITORY!!!!!!!!!!
# Otherwise, the script will interfere with the git commits.
# Save the script in your home directory as `~/subtrees.sh`
# `cd` into your repository
# Run `~/subtrees.sh`
# Enjoy!

# extract the list of submodules from .gitmodule
cat .gitmodules |while read i
do
if [[ $i == \[submodule* ]]; then
    echo converting $i

    read i

    # extract the module's prefix
    mpath=$(echo $i | grep -E "(\S+)$" -o)

    echo path: $mpath

    read i

    # extract the url of the submodule
    murl=$(echo $i|cut -d\= -f2|xargs)

    echo url: $murl

    # extract the module name
    mname=$(basename $mpath)

    echo name: $mname

    # extract the referenced commit
    mcommit=$(git submodule status $mpath | grep -E "\S+" -o | head -1)

    echo commit: $mcommit

    # deinit the module
    git submodule deinit $mpath

    # remove the module from git
    git rm -r --cached $mpath

    # remove the module from the filesystem
    rm -rf $mpath

    # commit the change
    git commit -m "Removed $mpath submodule at commit $mcommit"

    # add the remote
    git remote add -f $mname $murl

    # add the subtree
    git subtree add --prefix $mpath $mcommit --squash

    # commit any left over uncommited changes
    git commit -a -m "$mname cleaned up"

    # fetch the files
    git fetch $murl master

    echo
fi
done
git rm .gitmodules
git commit -a -m "Removed .gitmodules"

4
2018-06-07 08:16



+1。我不得不稍微调整一下这个脚本;无论出于何种原因,提交名称 git submodule status 回来了有一个领先的连字符(-) 在里面。我不得不添加一个 cut -d"-" -f2 到该行的末尾将其切掉。 - Jason R