30

Is there any way in git to know if you're in a submodule? You can do thinks like git submodule foreach in the parent directory but I can't seem to come up with a generic way to show that you're in a submodule if you're in one, or in any of the child directories inside the submodule.

I guess you could find the repo root with git rev-parse --show-toplevel, and then cd-ing up a level, and finding the root of that repo again and then comparing the list of submodules to the current directory, but that seems so sticky...

Andy Ray
  • 30,372
  • 14
  • 101
  • 138
  • for anyone who finds this, here's the project i used this in http://andrewray.me/bash-prompt-builder/index.html – Andy Ray Apr 04 '12 at 00:04
  • 1
    What about `git rev-parse --git-dir | grep '\.git/modules'`? Or simply `git rev-parse --git-dir` if you just need the git directory. – Quentin Pradet Oct 16 '14 at 09:42
  • 2
    Note: with Git 2.13 (Q2 2017), the actual command to use would be `git rev-parse --show-superproject-working-tree`. See my [edited answer below](http://stackoverflow.com/a/7359782/6309). – VonC Apr 09 '17 at 21:32

5 Answers5

39

(Update April 2017 for Git 2.13, Q2 2017)

There is now an official command to determine if a repo is a submodule of a parent repo:

cd /path/to/potential/submodule/repo
git rev-parse --show-superproject-working-tree

See commit bf0231c (08 Mar 2017) by Stefan Beller (stefanbeller).
(Merged by Junio C Hamano -- gitster -- in commit 3edcc04, 17 Mar 2017)

rev-parse: add --show-superproject-working-tree

In some situations it is useful to know if the given repository is a submodule of another repository.

Add the flag --show-superproject-working-tree to git-rev-parse to make it easy to find out if there is a superproject.
When no superproject exists, the output will be empty.

Jethro Yu suggests in the comments:

get super project path regardless inside/outside of submodule:

git rev-parse --show-superproject-working-tree --show-toplevel | head -1

(Update 2014) As noted by Quentin Pradet, more recent Git submodule repos show a simple .git file instead of a .git folder.
That .git file reference the path of the actual submodule git repo, stored in the parent repo .git/modules subfolder.

jeffrson adds in the comments (2023):

Existing '.git' itself is not sufficient, because that's the same for worktrees.
However, the file should contain the string "modules" or "worktrees", respectively, as part of the target.


(Original answer: Sept. 2011)

The very nature of a submodule is for the git repo acting as submodule has no idea it is used as a submodule by a parent repo.

One dirty trick would be to:

  • change a file
  • go back one level above the current repo
  • try a "git status --ignore-submodules=none"
  • restore the changed file.

If you see the file in the result of the git status, your repo should be a submodule.
If it is only a nested repo, the git status should ignore your nested repo entirely.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Is it still true? At least with git 2.1.2, submodules contain a simple ".git" file saying "gitdir: relative/path/to/parent/.git/modules/path/to/submodule/". – Quentin Pradet Oct 16 '14 at 09:15
  • @QuentinPradet True. I have included your comment in the answer for more visibility. A `.git` *file* should means the repo is a submodule. – VonC Oct 16 '14 at 09:25
  • 2
    get super project path regardless inside/outside of submodule: git rev-parse --show-superproject-working-tree --show-toplevel | head -1 – Jethro Yu Oct 17 '18 at 09:49
  • @JethroYu Thank you. I have included your link in the answer for more visibility. – VonC Oct 17 '18 at 10:29
  • Thank you for pointing at '--show-superproject-working-tree'. BTW, existing '.git' itself is not sufficient, because that's the same for worktrees. However, the file should contain the string "modules" or "worktrees", respectively, as part of the target. – JeffRSon Apr 20 '23 at 06:14
  • @JeffRSon Good point, thank you for this feedback. I have included your comment in the answer for more visibility. – VonC Apr 20 '23 at 09:00
9

Here is a shell function that you can use to detect this:

function is_submodule() 
{       
     (cd "$(git rev-parse --show-toplevel)/.." && 
      git rev-parse --is-inside-work-tree) | grep -q true
}

Edit In response to your proposed script:

Looking good.

  • There is a bug in

    for line in $submodules; do cd "$parent_git/$line"; \
        if [[ `pwd` = $_git_dir ]]; then return 0; fi; \
    done
    

because it won't cd back (so it would only work if the first submodule is a match). My version checks without changing directories; That could be done done by cd-ing in a subshell, but returning the exitcode is getting complicated that way

  • I don't know where you get $_git_dir from - I used basename(1) to get that information (see below).

  • There was also a problem with submodules containing a space in the name. In my version, there is still a problem with newlines in submodule names left, but I don't care enough to fix that. (Note the 'idiomatic' way to avoid having the while read in a subshell without needing new bash-isms like readarray)

  • finally declaring all the vars local fixes potential problems when using this inside other scripts (e.g. when the outer script uses the $path variable...)

  • I renamed _git_dir to top_level (which is less confusing, because GIT_DIR means something else)

Remaining issues:

  • I don't know whether git supports it (I don't think so) but this script could fail if the submodule directory is a symlink (because "$top_level/.." might resolve outside the containing repository)

  • submodule names with newlines will not be recognized properly

  • I also suggest you trap errors (either with 'set -e', 'trap "return 1" ERR' or similar) -- not in my script/exercise for reader

#!/bin/bash

function is_submodule() {
    local top_level parent_git module_name path
    # Find the root of this git repo, then check if its parent dir is also a repo
    top_level="$(git rev-parse --show-toplevel)"
    module_name="$(basename "$top_level")"
    parent_git="$(cd "$top_level/.." && git rev-parse --show-toplevel 2> /dev/null)"
    if [[ -n $parent_git ]]; then
        # List all the submodule paths for the parent repo
        while read path
        do
            if [[ "$path" != "$module_name" ]]; then continue; fi
            if [[ -d "$top_level/../$path" ]];    then return 0; fi
        done < <(cd $parent_git && git submodule --quiet foreach 'echo $path' 2> /dev/null)
        #return 1
    fi
    return 1
}

Usage

if is_submodule; then
    echo "In a submodule!"
else
    echo "Not in a submodule"
fi
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 2
    Would this return a false positive if you simply have a repo checked out inside a repo? – Andy Ray Sep 09 '11 at 17:57
  • @Andy: yes it would. So you may change the name to is_nested_worktree – sehe Sep 09 '11 at 18:53
  • I modified the function to eliminate false positives, do you have any suggestions? https://gist.github.com/1208078 – Andy Ray Sep 10 '11 at 08:06
  • @Andy: I have proposed an improved script based on yours (see also https://gist.github.com/1208393) – sehe Sep 10 '11 at 15:06
  • I think you're mistaken about the `cd` bug, `cd "$parent_git/$line"` will be a full path, not a relative one. – Andy Ray Sep 10 '11 at 21:07
  • @Andy: aha. Indeed the manpage says so. I still think it is better to avoid unnecessary directory changing (again to avoid unexpected effects on calling scripts). What about `is_submodule || git reset --hard`: it could reset a random other submodule if the current working tree was not a submodule. Hope the rest of it helped. – sehe Sep 10 '11 at 22:03
5

try git rev-parse --git-dir which will return ".git" if and only if called from the project root:

if `git rev-parse --git-dir` == ".git"
    // in root directory
else
    // in submodule directory

unless you set $GIT_DIR which will be the returned value in that case (see rev-parse):

--git-dir

Show $GIT_DIR if defined. Otherwise show the path to the .git directory. The path shown, when relative, is relative to the current working directory.

If $GIT_DIR is not defined and the current directory is not detected to lie in a Git repository or work tree print a message to stderr and exit with nonzero status.

Amir Nissim
  • 3,173
  • 2
  • 24
  • 19
  • Interestingly this doesn't seem to work for nested submodules. – Keith Smiley Dec 30 '15 at 04:50
  • @KeithSmiley I think it works for nested submodules as well. At least in current last version 2.9.3. If you create a submodule via `git submodule add... ` rather than `git clone ...` in the submodule. – olegtaranenko Aug 24 '16 at 00:48
  • 1
    This one seems to be the most effective way; in essence all you need is `is_submodule() { ! [[ "$(git rev-parse --git-dir 2>/dev/null)" =~ \.git$ ]]; }` – ϹοδεMεδιϲ Apr 06 '18 at 12:13
  • 1
    A little update: run the check from the toplevel dir: `alias issub='( cd $(git rev-parse --show-toplevel) && test $(git rev-parse --git-dir) != ".git"; )'` – vr286 Jul 21 '19 at 12:55
  • Unfortunately this will not work in a worktree (which itself actually is a project root) – JeffRSon Apr 20 '23 at 06:26
3

I tried the script suggested above and it didn't work for me. Of course, I may be the only person in the world who has a submodule that's not a direct child of the parent module. The code above assumes that it is.

It would also be nice to then show the submodule path inside the parent - I plan to use this to show a bash shell prompt telling me if I'm in a submodule, and where.

Here's my update:

function is_submodule() {
    local git_dir parent_git module_name path strip
    # Find the root of this git repo, then check if its parent dir is also a repo
    git_dir="$(git rev-parse --show-toplevel)"
    parent_git="$(cd "$git_dir/.." && git rev-parse --show-toplevel 2> /dev/null)"

    if [[ -n $parent_git ]]; then
        strip=$((${#parent_git} + 1))
        module_name=${git_dir:$strip}
        # List all the submodule paths for the parent repo
        while read path
        do
            if [[ "$path" != "$module_name" ]]; then continue; fi
            if [[ -d "$parent_git/$path" ]]; then
                echo $module_name
                return 0;
            fi
        done < <(cd $parent_git && git submodule --quiet foreach 'echo $path' 2> /dev/null)
    fi
    return 1
}

# Usage
submodule=$(is_submodule)
if [[ $? -eq 0 ]]; then
    echo "In a submodule! $submodule"
else
    echo "Not in a submodule"
fi
Thomas Vander Stichele
  • 36,043
  • 14
  • 56
  • 60
1

The selected answer doesn't work for submodules that aren't at the top level. This does and is also simpler:

function is_git_submodule() {
    local module_name 

    module_path="$(git rev-parse --show-toplevel 2> /dev/null)"

    # List all the submodule paths for the parent repo and look for our module's path
    (cd "${module_path}/.." && git submodule --quiet foreach 'echo $toplevel/$path' 2> /dev/null) | \
        grep --quiet --line-regexp --fixed-strings "$module_path"
}
Lexelby
  • 131
  • 1
  • 4