List submodules in a Git repository
GitGit SubmodulesGit Problem Overview
I have a Git repository that has several submodules in it. How do I list the names of all the submodules after git submodule init
has been run?
The git submodule foreach
command could echo the names of the submodule, but that only works once they have been checked out which has not happened after the init step. There are more steps in the chain that need to happen before they can be checked out, and I don't want to have to hard-wire names of submodules into the script.
So is there a Git command to get the names of all currently registered, but not yet checked out submodules?
Git Solutions
Solution 1 - Git
You could use the same mechanism as git submodule init
uses itself, namely, look at .gitmodules
. This files enumerates each submodule path and the URL it refers to.
For example, from root of repository, cat .gitmodules
will print contents to the screen (assuming you have cat
).
Because .gitmodule files have the Git configuration format, you can use git config to parse those files:
git config --file .gitmodules --name-only --get-regexp path
Would show you all submodule entries, and with
git config --file .gitmodules --get-regexp path | awk '{ print $2 }'
you would only get the submodule path itself.
Solution 2 - Git
You can use git submodule status
or optionally git submodule status --recursive
if you want to show nested submodules.
From the Git documentation:
> Show the status of the submodules. This will print the SHA-1 of the > currently checked out commit for each submodule, along with the > submodule path and the output of git describe for the SHA-1. Each > SHA-1 will be prefixed with - if the submodule is not initialized, + > if the currently checked out submodule commit does not match the SHA-1 > found in the index of the containing repository and U if the submodule > has merge conflicts.
Solution 3 - Git
The following command will list the submodules:
git submodule--helper list
The output is something like this:
<mode> <sha1> <stage> <location>
Note: It requires Git 2.7.0 or above.
Solution 4 - Git
To return just the names of the registered submodules, you can use this command:
grep path .gitmodules | sed 's/.*= //'
Think of it as git submodule --list
which doesn't exist.
Solution 5 - Git
Use:
$ git submodule
It will list all the submodules in the specified Git repository.
Solution 6 - Git
I use this:
git config --list|egrep ^submodule
Solution 7 - Git
I noticed that the command provided in an answer to this question gave me the information I was looking for:
No submodule mapping found in .gitmodule for a path that's not a submodule
git ls-files --stage | grep 160000
Solution 8 - Git
If you don't mind operating only on initialized submodules, you can use git submodule foreach
to avoid text parsing.
git submodule foreach --quiet 'echo $name'
Solution 9 - Git
You can use:
git submodule | awk '{ print $2 }'
Solution 10 - Git
This worked for me:
git ls-files --stage | grep ^160000
It is based on this great article: Understanding Git Submodules
It must read grep ^160000
.
Solution 11 - Git
To list all submodules by name:
git submodule --quiet foreach --recursive 'echo $name'
Solution 12 - Git
I use this one:
git submodule status | cut -d' ' -f3-4
Output (path + version):
tools/deploy_utils (0.2.4)
Solution 13 - Git
> Just the submodule paths please, ma'am...
git config --list | grep \^submodule | cut -f 2 -d .
> Vendor/BaseModel > Vendor/ObjectMatcher > Vendor/OrderedDictionary > Vendor/_ObjC > Vendor/XCodeHelpers
ļļ¼
Solution 14 - Git
git config
allows to specify a config file.
And .gitmodules
is a config file.
So, with the help of "use space as a delimiter with cut command":
git config --file=.gitmodules --get-regexp ^^submodule.*\.path$ | cut -d " " -f 2
That will list only the paths, one per declared submodule.
As Tino points out in the comments:
> - This fails for submodules with spaces in it.
- submodule paths may contain newlines, as in
> 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'
> For paths with newlines in them (they can be created with git mv
), leave away the | tr '\0' '\n'
and use something like ... | while IFS='' read -d '' path; do ...
for further processing with bash.
This needs a modern bash which understands read -d ''
(do not forget the space between -d and ''
).
Solution 15 - Git
Display ALL info about each submodule using built-in git functions:
git submodule foreach -q git config -l
Or just URL-s:
git submodule foreach -q git config remote.origin.url
Stolen from here.
Solution 16 - Git
In my version of Git [1], every Git submodule has a name
and a path
. They don't necessarily have to be the same [2]. Getting both in a reliable way, without checking out the submodules first (git update --init
), is a tricky bit of shell wizardry.
names
Get a list of submodule I didn't find a way how to achieve this using git config
or any other git
command. Therefore we are back to regex on .gitmodules
(super ugly). But it seems to be somewhat safe since git
limits the possible code space allowed for submodule names
. In addition, since you probably want to use this list for further shell processing, the solution below separate entries with 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"
And in your script:
#!/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"
)
Note: read -rd ''
requires bash
and won't work with sh
.
paths
Get a list of submodule In my approach I try not to process the output from git config --get-regexp
with awk
, tr
, sed
, ... but instead pass it a zero byte separated back to git config --get
. This is to avoid problems with newlines, spaces and other special characters (e.g. Unicode) in the submodule paths
. In addition, since you probably want to use this list for further shell processing, the solution below separate entries with NULL
-bytes (\0
).
$ git config --null --file .gitmodules --name-only --get-regexp '\.path$' \
| xargs -0 -n1 git config --null --file .gitmodules --get
For example, in a Bash script you could then:
#!/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
)
Note: read -rd ''
requires bash
and won't work with sh
.
Footnotes
[1] Git version
$ git --version
git version 2.22.0
name
and path
[2] Submodule with diverging Set up test repository:
$ 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 = ./
Move submodule to make name
and path
diverge:
$ 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
Testing
Set up test repository:
$ 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
Get list of submodule $ 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
Get list of submodule $ 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
Solution 17 - Git
If there isn't any .gitmodules
file, but a submodules configuration exists in .git/modules/
:
find .git/modules/ -name config -exec grep url {} \;
Solution 18 - Git
Here is another way to parse Git submodule names from .gitmodules without the need for sed or fancy IFS settings. :-)
#!/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-)
Solution 19 - Git
To get path
grep url .gitmodules | sed 's/.*= //'
To get names as in repos
grep path .gitmodules | sed 's/.*= //'