从.gitmodules中还原git子模块

101

我有一个文件夹,它是一个git仓库。它包含一些文件和.gitmodules文件。现在,当我运行git init,然后运行git submodule init时,后面的命令输出为空。如何让git看到在.gitmodules文件中定义的子模块,而不必再次手动运行git submodule add命令?

更新: 这是我的.gitmodules文件:

[submodule "vim-pathogen"]
    path = vim-pathogen
    url = git://github.com/tpope/vim-pathogen.git
[submodule "bundle/python-mode"]
    path = bundle/python-mode
    url = git://github.com/klen/python-mode.git
[submodule "bundle/vim-fugitive"]
    path = bundle/vim-fugitive
    url = git://github.com/tpope/vim-fugitive.git
[submodule "bundle/ctrlp.vim"]
    path = bundle/ctrlp.vim
    url = git://github.com/kien/ctrlp.vim.git
[submodule "bundle/vim-tomorrow-theme"]
    path = bundle/vim-tomorrow-theme
    url = git://github.com/chriskempson/vim-tomorrow-theme.git

这里是该目录的清单:

drwxr-xr-x  4 evgeniuz 100 4096 июня  29 12:06 .
drwx------ 60 evgeniuz 100 4096 июня  29 11:43 ..
drwxr-xr-x  2 evgeniuz 100 4096 июня  29 10:03 autoload
drwxr-xr-x  7 evgeniuz 100 4096 июня  29 12:13 .git
-rw-r--r--  1 evgeniuz 100  542 июня  29 11:45 .gitmodules
-rw-r--r--  1 evgeniuz 100  243 июня  29 11:18 .vimrc

所以,肯定是在顶层目录。git目录没有改变,只执行了 git init 命令。


子模块是否已经存在,也就是说,如果您切换到任何子模块目录,是否有文件存在,并且 git rev-parse --show-toplevel 给出的是子模块而不是“超级模块”目录? - Mark Longair
1
不,子模块目录不存在。想象一下这个文件夹是完全空的,只有一个名为 .gitmodules 的文件。 - evgeniuz
啊,我明白问题出在哪里了 - 我已经更新了我的答案。 - Mark Longair
8个回答

130

git submodule init 只会考虑已经在索引中(即已经“暂存”)的子模块进行初始化。我会编写一个简短的脚本,解析 .gitmodules文件,并对每个 urlpath 对运行以下命令:

git submodule add <url> <path>
例如,您可以使用以下脚本:
#!/bin/sh

set -e

git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
    while read path_key local_path
    do
        url_key=$(echo $path_key | sed 's/\.path/.url/')
        url=$(git config -f .gitmodules --get "$url_key")
        git submodule add $url $local_path
    done

这基于git-submodule.sh脚本本身对.gitmodules文件的解析。


5
你是不是指的是 git submodule update --init 命令?所有这些命令都会默默失败。看起来 Git 没有看到仅在 .gitmodules 文件中定义的模块。 - evgeniuz
在执行完git submodule init之后,你能否从git config --list | grep submodule命令中看到任何输出?(正如文档所说,git submodule init应该“初始化子模块,即将.gitmodules中找到的每个子模块名称和URL注册到.git/config中”。) - Mark Longair
1
“git config --list” 只显示标准值,没有提到子模块。 - evgeniuz
不幸的是,如果子模块包含空格,例如在 git submodule add URL 'path with spaces' 之后,此解决方案将失败。也许可以尝试类似于 git config -f .gitmodules -z --get-regexp '^submodule\..*\.path$' | sed -z 's/\n.*$//' | tr '\0' '\n' | while read keys; .. 的东西。 - Tino
1
如果某些子模块已经在索引中,脚本将退出。为了避免这种情况:git submodule add $url $path || true - schwart
显示剩余5条评论

16

在 @Mark Longair 的回答基础上进行扩展,我编写了一个 Bash 脚本来自动化以下步骤中的第二步和第三步:

  1. 克隆“样板”仓库以开始新项目
  2. 删除 .git 文件夹并重新初始化为新仓库
  3. 重新初始化子模块,在删除文件夹之前提示输入

#!/bin/bash

set -e
rm -rf .git
git init

git config -f .gitmodules --get-regexp '^submodule\..*\.path$' > tempfile

while read -u 3 path_key path
do
    url_key=$(echo $path_key | sed 's/\.path/.url/')
    url=$(git config -f .gitmodules --get "$url_key")

    read -p "Are you sure you want to delete $path and re-initialize as a new submodule? " yn
    case $yn in
        [Yy]* ) rm -rf $path; git submodule add $url $path; echo "$path has been initialized";;
        [Nn]* ) exit;;
        * ) echo "Please answer yes or no.";;
    esac

done 3<tempfile

rm tempfile
注意:子模块将被检出到它们的主分支的最新提交,而不是与样板存储库相同的提交,因此你需要手动进行。

将git config的输出导入读取循环会导致输入提示出现问题,所以将其输出到临时文件中。欢迎对我的第一个bash脚本提出任何改进意见 :)


非常感谢Mark、https://dev59.com/WnVC5IYBdhLWcg3wpS3f#226724bash: nested interactive read within a loop that's also using read,以及tnettenba @ chat.freenode.net帮助我得出这个解决方案!


10

在@Mark Longair的优秀答案的基础上进行扩展,以添加子模块并尊重分支和仓库名称。

#!/bin/sh

set -e

git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
    while read path_key path
    do
        name=$(echo $path_key | sed 's/\submodule\.\(.*\)\.path/\1/')
        url_key=$(echo $path_key | sed 's/\.path/.url/')
        branch_key=$(echo $path_key | sed 's/\.path/.branch/')
        url=$(git config -f .gitmodules --get "$url_key")
        branch=$(git config -f .gitmodules --get "$branch_key" || echo "master")
        git submodule add -b $branch --name $name $url $path || continue
    done

1
这个方法很好用,但它也会在.gitmodules文件中复制每个条目,需要事后手动清理。 - Marcus Ottosson

4

这是@mark-longair的脚本的更新版本。该版本还支持分支,处理某些子模块已存在于.git/config中的情况,并在必要时备份与子模块路径相同的现有目录。

git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
    while read path_key path
    do
        url_key=$(echo $path_key | sed 's/\.path/.url/');
        branch_key=$(echo $path_key | sed 's/\.path/.branch/');
        # If the url_key doesn't yet exist then backup up the existing
        # directory if necessary and add the submodule
        if [ ! $(git config --get "$url_key") ]; then
            if [ -d "$path" ] && [ ! $(git config --get "$url_key") ]; then
                mv "$path" "$path""_backup_""$(date +'%Y%m%d%H%M%S')";
            fi;
            url=$(git config -f .gitmodules --get "$url_key");
            # If a branch is specified then use that one, otherwise
            # default to master
            branch=$(git config -f .gitmodules --get "$branch_key");
            if [ ! "$branch" ]; then branch="master"; fi;
            git submodule add -f -b "$branch" "$url" "$path";
        fi;
    done;

# In case the submodule exists in .git/config but the url is out of date

git submodule sync;

# Now actually pull all the modules. I used to use this...
#
# git submodule update --init --remote --force --recursive
# ...but the submodules would check out in detached HEAD state and I 
# didn't like that, so now I do this...

git submodule foreach --recursive 'git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)';

3
我遇到了类似的问题。执行git submodule init时没有任何反应。
当我执行以下命令时: git submodule add <url> <path> 我看到了以下内容: The following path is ignored by one of your .gitignore files: ... 我认为可能是由于.gitignore文件中的路径被忽略导致的。

2

3
实际上我已经找到了一个:npx @iclare/ksync .gitmodules . --nobackup - Petr Plenkov

1

我知道已经有一段时间了,但我想分享这个版本,它只调用git config一次,不需要脚本,并且还处理分支:

git config -f .gitmodules --get-regexp '^submodule\.' | perl -lane'
$conf{$F[0]} = $F[1]}{
@mods = map {s,\.path$,,; $_} grep {/\.path$/} keys(%conf);
sub expand{$i = shift; map {$conf{$i . $_}} qw(.path .url .branch)}
for $i (@mods){
    ($path, $url, $branch) = expand($i);
    print(qq{rm -rf $path});
    print(qq{git submodule add -b $branch $url $path});
}
'

唯一的副作用是命令输出,不执行任何操作,因此您可以在提交之前进行审计。
这适用于在控制台上进行简单的复制和粘贴,但将其放入shell脚本应该很容易。
示例输出:
rm -rf third-party/dht
git submodule add -b post-0.25-transmission https://github.com/transmission/dht third-party/dht
rm -rf third-party/libutp
git submodule add -b post-3.3-transmission https://github.com/transmission/libutp third-party/libutp
rm -rf third-party/libb64
git submodule add -b post-1.2.1-transmission https://github.com/transmission/libb64 third-party/libb64
rm -rf third-party/libnatpmp
git submodule add -b post-20151025-transmission https://github.com/transmission/libnatpmp third-party/libnatpmp
rm -rf third-party/miniupnpc
git submodule add -b post-2.0.20170509-transmission https://github.com/transmission/miniupnpc third-party/miniupnpc
rm -rf third-party/libevent
git submodule add -b post-2.0.22-transmission https://github.com/transmission/libevent third-party/libevent

0
对于 zsh 用户,试用我的函数,它支持 DRY_RUN=1 来查看将要运行的命令,并且只使用 git 解析文件,而不是使用 sed
gsub_file() {(
  set -eu

  cd "$(git rev-parse --show-toplevel)"

  submodule_paths=(
    "${(@f)$(git config --file ./.gitmodules --get-regexp "path" | awk '{ print $2 }')}"
  )
  submodule_urls=(
    "${(@f)$(git config --file ./.gitmodules --get-regexp "url" | awk '{ print $2 }')}"
  )
  submodule_branches=(
    "${(@f)$(git config --file ./.gitmodules --get-regexp "branch" | awk '{ print $2 }')}"
  )

  sh_c() {
    echo + "$*"
    if [ "${DRY_RUN-}" ]; then
      return
    fi
    eval "$@"
  }

  for (( i=1; i <= ${#submodule_paths[@]}; i++ )) do
    p="${submodule_paths[$i]}"
    if [ -d "$p" ]; then
      continue
    fi

    url="${submodule_urls[$i]}"
    unset b
    if [ "${submodule_branches[$i]-}" ]; then
      b="-b ${submodule_branches[$i]}" 
    fi
    sh_c git submodule add "${b-}" "$url" "$p"
  done
)}

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