列出Git存储库中的子模块

我有一个Git存储库,其中有几个子模块。我如何列出git submodule init运行后所有子模块的名称?

git submodule foreach命令可以回显子模块的名称,但这只有在它们被签出后才有效,而在init步骤之后没有发生。在签出之前,链中还有更多的步骤需要执行,我不希望将子模块的名称硬连接到脚本中。

那么是否存在一个Git命令来获取所有当前已注册但尚未签出的子模块的名称?

293755 次浏览

你可以使用与git submodule init使用自身相同的机制,即查看.gitmodules。该文件枚举每个子模块路径及其引用的URL。

例如,从存储库的根目录,cat .gitmodules将把内容打印到屏幕上(假设你有cat)。

因为.gitmodule文件有Git配置格式,你可以使用Git配置来解析这些文件:

git config --file .gitmodules --name-only --get-regexp path

将显示所有子模块条目,并与

git config --file .gitmodules --get-regexp path | awk '{ print $2 }'

您只能得到子模块路径本身。

我使用这个:

git config --list|egrep ^submodule

为了只返回已注册的子模块的名称,你可以使用这个命令:

grep path .gitmodules | sed 's/.*= //'

把它想象成不存在的git submodule --list

我用的是这个:

git submodule status | cut -d' ' -f3-4

输出(路径+版本):

tools/deploy_utils (0.2.4)

如果想显示嵌套子模块,可以使用git submodule statusgit submodule status --recursive

从Git文档中:

显示子模块的状态。的SHA-1 的子模块的当前签出提交 子模块路径和git的输出为SHA-1描述。每一个 如果子模块未初始化,SHA-1将以-作为前缀,+ 如果当前检出的子模块提交不匹配SHA-1 在包含该子模块的存储库的索引中找到U

我注意到在回答这个问题时提供的命令为我提供了我正在寻找的信息:

在.gitmodule中没有找到非子模块路径的子模块映射

git ls-files --stage | grep 160000

使用:

$ git submodule

它将列出指定Git存储库中的所有子模块。

你可以使用:

git submodule | awk '{ print $2 }'

这招对我很管用:

git ls-files --stage | grep ^160000

它是基于这篇伟大的文章:了解Git子模块

它必须读为grep ^160000

只是劳驾,夫人……

git config --list | grep \^submodule | cut -f 2 -d .
Vendor/BaseModel
Vendor/ObjectMatcher
Vendor/OrderedDictionary
Vendor/_ObjC
Vendor/XCodeHelpers

👍🏼

git config允许指定一个配置文件 和.gitmodules 配置文件

因此,在"使用cut命令使用空格作为分隔符"的帮助下:

git config --file=.gitmodules --get-regexp ^^submodule.*\.path$ | cut -d " " -f 2

它将只列出路径,每个声明的子模块一个路径。

正如天奴指出的在评论中:

  • 这对于包含空间的子模块无效。
  • 子模块路径可能包含换行,如

    git submodule add https://github.com/hilbix/bashy.git "sub module"
    git mv 'sub module' $'sub\nmodule'
    

As a more robust alternative, Tino proposes:

git config -z --file .gitmodules --get-regexp '\.path$' | \
sed -nz 's/^[^\n]*\n//p' | \
tr '\0' '\n'

对于有换行符的路径(它们可以用git mv创建),不要使用| tr '\0' '\n',而使用类似... | while IFS='' read -d '' path; do ...的东西用于bash的进一步处理 这需要一个现代bash,它能理解read -d ''(不要忘记-d and ''之间的空格)

下面的命令将列出子模块:

git submodule--helper list

输出如下所示:

<mode> <sha1> <stage> <location>

注意:需要Git 2.7.0或以上版本。

如果你不介意只对初始化的子模块进行操作,你可以使用git submodule foreach来避免文本解析。

git submodule foreach --quiet 'echo $name'

按名称列出所有子模块:

git submodule --quiet foreach --recursive 'echo $name'

如果没有任何.gitmodules文件,但在.git/modules/中存在子模块配置:

find .git/modules/ -name config -exec grep url {} \;

在我的Git [1]版本中,每个Git子模块都有namepath。它们不一定是相同的[2]。在不先签出子模块(git update --init)的情况下,以一种可靠的方式获得两者,这是一种棘手的shell魔法。

获取子模块names的列表

我没有找到一种方法来实现这个使用git config或任何其他git命令。因此,我们回到了.gitmodules上的regex(超级丑陋)。但它似乎有点安全,因为git限制了子模块names所允许的可能的代码空间。此外,由于您可能希望使用此列表进行进一步的shell处理,因此下面的解决方案将包含NULL-bytes (\0)的条目分开。

$ sed -nre \
's/^\[submodule \"(.*)\"]$/\1\x0/p' \
"$(git rev-parse --show-toplevel)/.gitmodules" \
| tr -d '\n' \
| xargs -0 -n1 printf "%b\0"

在你的剧本里:

#!/usr/bin/env bash


while IFS= read -rd '' submodule_name; do
echo submodule name: "${submodule_name}"
done < <(
sed -nre \
's/^\[submodule \"(.*)\"]$/\1\x0/p' \
"$(git rev-parse --show-toplevel)/.gitmodules" \
| tr -d '\n' \
| xargs -0 -n1 printf "%b\0"
)

请注意: read -rd ''需要bash,不能与sh一起工作。

获取子模块paths的列表

在我的方法中,我尝试处理来自git config --get-regexp的输出,使用awktrsed,…而是将一个零字节传递回git config --get。这是为了避免在子模块paths中出现换行符、空格和其他特殊字符(例如Unicode)的问题。此外,由于您可能希望使用此列表进行进一步的shell处理,因此下面的解决方案将包含NULL-bytes (\0)的条目分开。

$ git config --null --file .gitmodules --name-only --get-regexp '\.path$' \
| xargs -0 -n1 git config --null --file .gitmodules --get

例如,在Bash脚本中,你可以:

#!/usr/bin/env bash


while IFS= read -rd '' submodule_path; do
echo submodule path: "${submodule_path}"
done < <(
git config --null --file .gitmodules --name-only --get-regexp '\.path$' \
| xargs -0 -n1 git config --null --file .gitmodules --get
)

请注意: read -rd ''需要bash,不能与sh一起工作。


脚注

[1] Git版本

$ git --version
git version 2.22.0

[2]派生出namepath的子模块

设置测试存储库:

$ git init test-name-path
$ cd test-name-path/
$ git checkout -b master
$ git commit --allow-empty -m 'test'
$ git submodule add ./ submodule-name
Cloning into '/tmp/test-name-path/submodule-name'...
done.
$ ls
submodule-name


$ cat .gitmodules
[submodule "submodule-name"]
path = submodule-name
url = ./

移动子模块使namepath发散:

$ git mv submodule-name/ submodule-path


$ ls
submodule-path


$ cat .gitmodules
[submodule "submodule-name"]
path = submodule-path
url = ./


$ git config --file .gitmodules --get-regexp '\.path$'
submodule.submodule-name.path submodule-path

测试

设置测试存储库:

$ git init test
$ cd test/
$ git checkout -b master
$ git commit --allow-empty -m 'test'
$
$ git submodule add ./ simplename
Cloning into '/tmp/test/simplename'...
done.
$
$ git submodule add ./ 'name with spaces'
Cloning into '/tmp/test/name with spaces'...
done.
$
$ git submodule add ./ 'future-name-with-newlines'
Cloning into '/tmp/test/future-name-with-newlines'...
done.
$ git mv future-name-with-newlines/ 'name
> with
> newlines'
$
$ git submodule add ./ 'name-with-unicode-💩'
Cloning into '/tmp/test/name-with-unicode-💩'...
done.
$
$ git submodule add ./ sub/folder/submodule
Cloning into '/tmp/test/sub/folder/submodule'...
done.
$
$ git submodule add ./ name.with.dots
Cloning into '/tmp/test/name.with.dots'...
done.
$
$ git submodule add ./ 'name"with"double"quotes'
Cloning into '/tmp/test/name"with"double"quotes'...
done.
$
$ git submodule add ./ "name'with'single'quotes"
Cloning into '/tmp/test/name'with'single'quotes''...
done.
$ git submodule add ./ 'name]with[brackets'
Cloning into '/tmp/test/name]with[brackets'...
done.
$ git submodule add ./ 'name-with-.path'
Cloning into '/tmp/test/name-with-.path'...
done.

.gitmodules:

[submodule "simplename"]
path = simplename
url = ./
[submodule "name with spaces"]
path = name with spaces
url = ./
[submodule "future-name-with-newlines"]
path = name\nwith\nnewlines
url = ./
[submodule "name-with-unicode-💩"]
path = name-with-unicode-💩
url = ./
[submodule "sub/folder/submodule"]
path = sub/folder/submodule
url = ./
[submodule "name.with.dots"]
path = name.with.dots
url = ./
[submodule "name\"with\"double\"quotes"]
path = name\"with\"double\"quotes
url = ./
[submodule "name'with'single'quotes"]
path = name'with'single'quotes
url = ./
[submodule "name]with[brackets"]
path = name]with[brackets
url = ./
[submodule "name-with-.path"]
path = name-with-.path
url = ./

获取子模块names的列表

$ sed -nre \
's/^\[submodule \"(.*)\"]$/\1\x0/p' \
"$(git rev-parse --show-toplevel)/.gitmodules" \
| tr -d '\n' \
| xargs -0 -n1 printf "%b\0" \
| xargs -0 -n1 echo submodule name:
submodule name: simplename
submodule name: name with spaces
submodule name: future-name-with-newlines
submodule name: name-with-unicode-💩
submodule name: sub/folder/submodule
submodule name: name.with.dots
submodule name: name"with"double"quotes
submodule name: name'with'single'quotes
submodule name: name]with[brackets
submodule name: name-with-.path

获取子模块paths的列表

$ git config --null --file .gitmodules --name-only --get-regexp '\.path$' \
| xargs -0 -n1 git config --null --file .gitmodules --get \
| xargs -0 -n1 echo submodule path:
submodule path: simplename
submodule path: name with spaces
submodule path: name
with
newlines
submodule path: name-with-unicode-💩
submodule path: sub/folder/submodule
submodule path: name.with.dots
submodule path: name"with"double"quotes
submodule path: name'with'single'quotes
submodule path: name]with[brackets
submodule path: name-with-.path

下面是另一种从. Git modules解析Git子模块名称的方法,不需要sed或花哨的IFS设置。: -)

#!/bin/env bash


function stripStartAndEndQuotes {
temp="${1%\"}"
temp="${temp#\"}"
echo "$temp"
}


function getSubmoduleNames {
line=$1
len=${#line} # Get line length
stripStartAndEndQuotes "${line::len-1}" # Remove last character
}


while read line; do
getSubmoduleNames "$line"
done < <(cat .gitmodules | grep "\[submodule.*\]" | cut -d ' ' -f 2-)

获取路径

grep url .gitmodules | sed 's/.*= //'

在回购中得到名字

grep path .gitmodules | sed 's/.*= //'

使用内置git函数显示每个子模块的所有信息:

git submodule foreach -q git config -l

或者只是url:

git submodule foreach -q git config remote.origin.url

偷自在这里

只是一个简单的子模块列表:

git submodule--helper list | cut -d$'\t' -f 2

一个好的替代方法如评论 by 天奴所示:

git config --null --file .gitmodules --get-regexp "\.path\$" | \
sed -nz "s/^[^\\n]*\n//p" | \
xargs --null rm -v;

但是BusyBox上的sed或xargs无法使用--null/-z选项。然后,这是我能想到的最好的替代方案(xargs不会在空格上中断):

grep "^\s*path\s*=" .gitmodules | \
sed -e "s/.*=\s*\"*//" -e "s/\"*\s*\$//" | \
xargs -n 1 -I mark rm -v "mark";

但它有一个缺点,因为xargs被调用时没有--null,而我们正在使用-I mark,它正在吞噬前导空白。要修复它,不要使用xargs并将名称写入文件:

grep "^\s*path\s*=" .gitmodules | \
sed -e "s/.*=\s*\"*//" -e "s/\"*\s*\$//" > tempfilefordanmxargsmotherbroked.txt;
while IFS= read -r line; do
echo ".$line."
done <"tempfilefordanmxargsmotherbroked.txt"
rm "tempfilefordanmxargsmotherbroked.txt"
  • 更正:BusyBox上的xargs没有删除前导空白,但完整版本是。