Submodules management in Git

Git’s submodule tool allows you to create a child repository as a submodule inside a repository. This article shows you how to manipulate a submodule.

Add a submodule from a remote repository

To create a new sub repository whose content has not existed in the repository:

# Add a submodule named a-submodule from an remote repository
# This command will create a .gitmodules which contains the mapping relation of
# the submodule and its remote repository.
# It also stages the changes.
$ git submodule add

# Use status to check what the above command stages
$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   .gitmodules
        new file:   a-submodule

# You need to commit staged changes: .gitmodules and a-submodule
$ git commit -m 'add a submodule'
[master 9d7d3b3] add: add a submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 a-submodule

As you see, Git treats the new submodule as a file with special mode (1600000) in a commit.

For a repository with submodules inside it, it is called the main repository.

Note: In new git versions, the submodule’ s .git data is stored the main repository’s .git directory while they stay in the submodule’s directory in the old versions.

Add a submodule from an subdirectory

You can also switch a subdirectory to a submodule. To add a submodule from a subdirectory that has been tracked, you need to remove it first:

# First remove the subdirectory named csvlib from index
$ git rm --cached csvlib

Then add a subdirectory as a submodule:

# Init csvlib as a repo
$ cd csvlib
$ git init
$ git add .
$ git commit -m 'initial commit'

# Add csvlib repo as a submodule
$ git submodule add ./csvlib/
Adding existing repo at 'csvlib' to the index

# Check status
$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        new file:   csvlib
        deleted:    csvlib/

# Commit the changes
$ git commit -m 'switch subdirectory csvlib as a submodule'
[master 9d7d3b3] add: add submodule git-submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 a-submodule

Note: If you switch to a branch in which the subdirectory not being as a submodule,you may get an error for the subdirectory will be overwritten. At this time, an option -f is needed to checkout. Afterwards if you get back to the branch treating it as a submodule, you need to execute git checkout . in the submodule’s directory to get the content back. See more on git submodules.

Clone a repository with submodules

If you clone a repository with submodules inside it as you do usually like below, you will get empty directories for submodules.

$ git clone <repository url>

To fetch the files in the submodules, you need to run two more commands:

# Initialize local configuration file
$ git submodule init

# Fetch files of submodules
$ git submodule update
Cloning into '<submodule name>'...

If there are nested submodules, add --recursive option to these two commands. You can also combine the two commands into one:

# Initialize and update submodules (with nested ones) with one command
$ git submodule update --init --recursive

Or you can add an --recurse-submodules option to the git clone command to automatically initialize and fetch each submodule recursively.

# Clone a repository with submodules
$ git clone --recurse-submodules <repository url>

Commit changes for a submodule

To commit changes in a submodule, first commit in its own repository and then commit for the main repository:

# Commit changes in the submodule directory
$ git add 
$ git commit -m 'feat: add some functionly'
# Push changes to its remote repository
$ git push

# Switch to the main repository directory
$ cd ..
# Check changes from a submodule
$ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  (commit or discard the untracked or modified content in submodules)

        modified:   a-submodule (modified content)

# Commit the submodule's changes to the top repository
$ git add a-submodule
$ git commit -m 'update changes of submodule a-submodule'
# Push changes if you want
$ git push

Pull updates for a submodule

To pull updates from remote for a submodule, an easy way is to run:

# Let Git to go into the submodule's directory and get updates for a-submodule
# --merge, merge local work
$ git submodule update --remote --merge a-submodule

Or you can do it manually:

# Switch to the submodule's directory
$ cd a-submodule

# Pull and merge updates form upstream branch
$ git pull
$ git merge origin/master

Manipulate multiple submodules at a time

Git has a submodule foreach command for you to manipulate multiple submodules. For example, stashing changes for all your submodules:

# Stash changes for all submodules
$ git submodule foreach 'git stash'

List all the submodules in a repository

Git does not provide a command something like git submodule list to list all the submodules. Since all the submodules in a repository are recorded in the .gitmodules file. Check that file to get all the submodules. You can do that with a command:

# Output the content of .gitmodules
$ cat .gitmodules

Or you can do this with help with git submodule status which outputs some information for each submodule:

$ git submodule status
 e8aecd...ebd a-submodule-name (heads/main)

Note if you only care about changes of each submodule, use git status instead.

Remove a submodule from a repository

To remove a submodule named a-submodule from a repository:

# Frist remove the from index
$ git rm --cached a-submodule

# If you want to keep submodule's files in working tree.
# Rename it for the next command will delete it from working tree
$ mv a-submodule a-submodule-renamed

# Unregister the submodule
# This command removes it both from .git/config and working tree
$ git submodule deinit a-submodule


Leave a Reply