Submodules
Git submodules allow you to include and manage external repositories within your own repository. This is particularly useful when you want to incorporate third-party libraries or other dependencies while maintaining control over their specific versions.
Adding a Submodule
To add a submodule to your repository, use the git submodule add
command.
# Syntax
git submodule add <repository-url> [<path>]
# Example
git submodule add https://github.com/example/library.git lib/library
This command clones the submodule repository into the specified path and adds a reference to it in your .gitmodules
file. By default, submodules will add the subproject into a directory named the same as the repository. You can add a different path at the end of the command if you want it to go elsewhere. You can also specify a branch to checkout with the -b
flag.
git submodule add -b master git@github.com:emeneo/moodle-enrol_apply.git enrol/apply
.gitmodules
file is a configuration file that stores the mapping between the project’s URL and the local subdirectory you’ve pulled it into.
[submodule "enrol/apply"]
path = enrol/apply
url = git@github.com:emeneo/moodle-enrol_apply.git
branch = master
Initializing and Updating Submodules
After cloning a repository that contains submodules, you need to initialize and update them.
# Initialize submodules
git submodule init
# Update submodules
git submodule update
git submodule sync; git submodule update --init --recursive
The init
command initializes the submodules recorded in the .gitmodules
file, while the update
command fetches and checks out the correct commit specified by the superproject.
Cloning a Repository with Submodules
When cloning a repository that includes submodules, use the --recurse-submodules
flag to automatically initialize and update submodules. When you clone such a project, by default you get the directories that contain submodules, but none of the files within them yet. You must run two commands: git submodule init to initialize your local configuration file, and git submodule update to fetch all the data from that project and check out the appropriate commit listed in your superproject. If you pass --recurse-submodules
to the git clone command, it will automatically initialize and update each submodule in the repository, including nested submodules if any of the submodules in the repository have submodules themselves.
# Clone repository and initialize submodules
git clone --recurse-submodules <repository-url>
# Example
git clone --recurse-submodules https://github.com/example/main-project.git
git submodule update --init --recursive
.gitmodules
file in one of the commits you pull. This can happen for example if the submodule project changes its hosting platform. In that case, it is possible for git pull --recurse-submodules, or git submodule update, to fail if the superproject references a submodule commit that is not found in the submodule remote locally configured in your repository. In order to remedy this situation, the git submodule sync command is required:
# copy the new URL to your local config
$ git submodule sync --recursive
# update the submodule from the new URL
$ git submodule update --init --recursive
Updating Submodules to the Latest Commit
If you want to update your submodules to their latest commit, use:
# Update all submodules to the latest commit
git submodule update --remote
# Example
git submodule update --remote lib/library
This command updates the submodule to the latest commit on the default branch.
Removing a Submodule
To remove a submodule, you need to follow several steps:
git rm <path-to-submodule>
git commit
Remove the submodule directory from the index
The .git/config
file keeps track of all submodules, their paths, and URLs. Here’s an example entry.
As the docs note however, the .git dir of the submodule is kept around (in the modules/ directory of the main project's .git dir), "to make it possible to checkout past commits without requiring fetching from another repository". If you nonetheless want to remove this info, manually delete the submodule's directory in .git/modules/, and remove the submodule's entry in the file .git/config. These steps can be automated using the commands
rm -rf .git/modules/<path-to-submodule>
git config --remove-section submodule.<path-to-submodule>.
After performing these steps, commit the changes to complete the removal of the submodule.
Managing Nested Submodules
Submodules can themselves contain submodules. To initialize and update nested submodules, you need to pass the --recursive
flag:
# Initialize nested submodules
git submodule update --init --recursive
# Update nested submodules to the latest commit
git submodule update --remote --recursive
Submodule Configuration in .gitmodules
The .gitmodules
file keeps track of all submodules, their paths, and URLs. Here’s an example entry:
[submodule "lib/library"]
path = lib/library
url = https://github.com/example/library.git
Submodule Foreach
The git submodule foreach
command allows you to run a command on each submodule in your project. It can be used to execute arbitrary commands on each submodule.
For example, let’s say we want to start a new feature or do a bugfix and we have work going on in several submodules. We can easily stash all the work in all our submodules.
git submodule foreach 'git stash'
git submodule foreach 'git checkout -b new-branch'
One really useful thing you can do is produce a nice unified diff of what is changed in your main project and all your subprojects as well.
git diff; git submodule foreach 'git diff'
Managing Dirty Submodules
Reset and Clean All Submodules
git submodule foreach --recursive 'git reset --hard && git clean -fd'
- Discards all changes (both staged and unstaged).
- Removes all untracked files and directories.
When you want to completely reset submodules to the state defined in the main repository, with no need to save changes.
Caution: This permanently deletes untracked changes. Use git clean -fdn
first to preview.
Stash Changes in All Submodules
git submodule foreach --recursive 'git stash'
- Temporarily saves changes in submodules' working directories (for tracked files only).
-
Changes can be restored later using
git stash pop
. -
When you want to save current progress in submodules without committing.
- Useful if you're unsure whether changes are needed later.
Stash Changes, Including Untracked Files
git submodule foreach --recursive 'git stash -u'
-
Temporarily saves changes and untracked files in submodules.
-
When there are untracked files that also need to be saved along with tracked changes.
Update Submodules to Match the Main Repository
git submodule update --init --recursive --force
- Syncs submodules to the exact commit specified in the main repository.
-
Reinitializes submodules if they aren't set up yet.
-
If submodules are out of sync or have uncommitted changes you don't care about.
- Good for getting a clean state without worrying about intermediate changes.
Combine Stash and Clean
git submodule foreach --recursive 'git stash && git clean -fd'
-
Stashes tracked changes and removes untracked files in submodules.
-
If you want to save changes (tracked files) while cleaning untracked files.
Manually Commit or Discard Changes
git submodule foreach --recursive 'git add . && git commit -m "Save changes in submodule"'
git submodule foreach --recursive 'git checkout -- .'
-
Either commits changes for long-term preservation or discards them selectively.
-
Commit: When changes in submodules are worth preserving.
- Discard: When you only want to reset staged and unstaged changes without affecting untracked files.
When to Use Each Approach
Scenario | Best Approach |
---|---|
Completely reset submodules | git reset --hard && git clean -fd |
Temporarily save changes | git stash |
Temporarily save changes, including untracked files | git stash -u |
Sync submodules to main repository state | git submodule update --init --recursive --force |
Save progress in submodules (commit changes) | git add . && git commit -m "Save changes in submodule" |
Save changes but clean untracked files | git stash && git clean -fd |
The --recurse-submodules
flag
Command | Example Usage |
---|---|
git clone |
git clone --recurse-submodules <repository-url> |
git pull |
git pull --recurse-submodules |
git fetch |
git fetch --recurse-submodules |
git checkout |
git checkout --recurse-submodules <branch-name> |
git submodule update |
git submodule update --recurse-submodules |
git push |
git push --recurse-submodules=check |
git reset |
git reset --recurse-submodules <commit> |
Using the --recurse-submodules
flag ensures that operations affecting the main repository also appropriately handle submodules, keeping everything in sync and properly updated. You can tell Git (>=2.14) to always use the --recurse-submodules
flag by setting the configuration option submodule.recurse: git config submodule.recurse true
. This will also make Git recurse into submodules for every command that has a --recurse-submodules option (except git clone).
git clone
When cloning a repository, this flag ensures that all submodules are also cloned and initialized.
git clone --recurse-submodules <repository-url>
git clone --recurse-submodules https://github.com/example/repo.git
git pull
or git fetch
Pulls or Fetches changes from the remote repository and updates submodules as well.
git pull --recurse-submodules
git fetch --recurse-submodules
git checkout
Checks out a branch or commit and updates the submodules to the correct commit.
git checkout --recurse-submodules <branch-name>
git checkout --recurse-submodules main
git submodule update
Updates the submodules to the commit specified in the superproject. This flag is used to ensure that nested submodules are also updated.
git submodule update --recurse-submodules
git push
When pushing to a remote repository, this flag ensures that submodule commits are also pushed.
git push --recurse-submodules=check
check
option checks that all submodule commits that the superproject references are present on the remote repository before pushing. The check
option will make push simply fail if any of the committed submodule changes haven’t been pushed. If you want the “check” behavior to happen for all pushes, you can make this behavior the default by doing git config push.recurseSubmodules check
.
The on-demand
option ensures that the submodule commits are pushed even if the superproject commit is not present on the remote repository. Git will go into each submodule and push to the remotes to make sure they’re externally available. You can make this behavior the default by doing git config push.recurseSubmodules on-demand
.
git reset
Resets the current HEAD to a specified state and updates submodules accordingly.
git reset --recurse-submodules <commit>
git reset --recurse-submodules HEAD~1
git config
and submodules
git diff --submodule
git diff --submodule
shows the changes in the submodules. You can see that the submodule was updated and get a list of commits that were added to it. If you don’t want to type --submodule
every time you run git diff
, you can set it as the default format by setting the diff.submodule config value to “log”.
git config --global diff.submodule log
Modify .gitmodules
If you want to have a submodule track that repository’s “stable” branch, you can set it in either your .gitmodules file (so everyone else also tracks it), or just in your local .git/config file. Let’s set it in the .gitmodules file:
git config -f .gitmodules submodule.<name-submodule>.branch stable
If you leave off the -f .gitmodules it will only make the change for you, but it probably makes more sense to track that information with the repository so everyone else does as well.
Status of submodules
git submodule status
git config status.submodulesummary 1
git log
We can actually see the log of commits that we’re about to commit to in our submodule. Once committed, you can see this information after the fact as well when you run git log -p.
git log -p --submodule
Useful aliases
git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
git config alias.spush 'push --recurse-submodules=on-demand'
git config alias.supdate 'submodule update --remote --merge'