How to update submodules in Git
Managing submodules in Git projects requires regular updates to keep external dependencies in sync with their remote repositories.
With over 25 years of experience in software development and as the creator of CoreUI, a widely used open-source UI library, I’ve managed countless projects with complex submodule structures.
The most reliable approach is to use the git submodule update command with appropriate flags to fetch and checkout the correct commits.
This method ensures your submodules stay synchronized with their parent repository while maintaining version control integrity.
Use git submodule update --remote to update all submodules to their latest commits from the remote repository.
git submodule update --remote
This command updates all submodules in your repository to the latest commit on their tracked branch. Git fetches the newest changes from the remote repository and checks out the appropriate commit in each submodule. The --remote flag tells Git to update to the latest commit rather than the commit recorded in the parent repository.
Updating After Cloning a Repository
git clone https://github.com/user/project.git
cd project
git submodule init
git submodule update
When you clone a repository that contains submodules, the submodule directories are created but empty. The git submodule init command initializes the submodule configuration based on the .gitmodules file. The git submodule update command then clones the submodule repositories and checks out the commit specified in the parent repository. This two-step process ensures all submodules are properly initialized and populated with the correct version.
Updating to Latest Commit on Tracked Branch
git submodule update --remote --merge
This command updates submodules to the latest commit on their tracked branch and merges any local changes. The --merge flag preserves any uncommitted work in the submodule by merging the remote changes with your local changes. If you have made modifications to the submodule that you want to keep, this approach prevents losing your work while bringing in the latest updates from the remote.
Updating Specific Submodules
git submodule update --remote path/to/submodule
When you only need to update a single submodule, specify its path as an argument. This is useful when working on a large project with many submodules but only needing to update one or two of them. Git will fetch the latest changes only for the specified submodule, saving time and bandwidth compared to updating all submodules.
Recursive Submodule Updates
git submodule update --init --recursive
Some submodules contain their own submodules, creating a nested structure. The --recursive flag ensures all levels of submodules are updated. The --init flag initializes any submodules that have not been initialized yet. This combination is essential when working with complex projects that have deeply nested dependencies, ensuring the entire dependency tree is properly synchronized.
Pulling Parent and Submodule Changes
git pull --recurse-submodules
This command pulls changes for both the parent repository and all its submodules in a single operation. Git first updates the parent repository, then automatically updates the submodules to match the commits recorded in the parent. This is the most convenient way to keep everything in sync when pulling the latest changes from a remote repository.
Updating Submodules with Rebase
git submodule update --remote --rebase
Instead of merging, you can rebase your local submodule changes on top of the remote changes. The --rebase flag replays your local commits on top of the updated remote branch, creating a cleaner commit history. This approach is preferred when you want to maintain a linear history in your submodules and avoid merge commits.
Checking Submodule Status
git submodule status
Before updating, it is helpful to check the current status of all submodules. This command shows the commit hash, path, and branch information for each submodule. A minus sign before the hash indicates the submodule is not initialized, a plus sign indicates uncommitted changes, and no prefix means the submodule is clean and matches the parent repository’s recorded commit.
Cloning with Submodules in One Step
git clone --recurse-submodules https://github.com/user/project.git
Instead of cloning and then initializing submodules separately, you can clone the repository and all its submodules in a single command. The --recurse-submodules flag tells Git to automatically initialize and update all submodules after cloning the parent repository. This is the fastest way to get a complete copy of a project with all its dependencies in one operation.
Configuring Submodule Update Behavior
git config submodule.recurse true
git config --global submodule.recurse true
Setting the submodule.recurse configuration option makes Git automatically update submodules when you pull, checkout, or reset. The first command sets this behavior for the current repository only, while the second sets it globally for all repositories on your system. With this configuration, you no longer need to remember the --recurse-submodules flag, as Git handles submodule updates automatically.
Updating Submodules to a Specific Branch
git config -f .gitmodules submodule.path/to/submodule.branch develop
git submodule update --remote
By default, submodules track the default branch of their remote repository. You can configure a specific branch for each submodule by modifying the .gitmodules file. The first command sets the tracked branch to develop for the specified submodule. After configuration, git submodule update --remote updates the submodule to the latest commit on the configured branch instead of the default branch.
Handling Conflicts During Submodule Updates
git submodule update --remote --merge
# If conflicts occur:
cd path/to/submodule
git status
# Resolve conflicts
git add .
git commit -m 'Resolved merge conflicts'
cd ../..
git add path/to/submodule
git commit -m 'Updated submodule with conflict resolution'
When updating submodules with local changes, conflicts may occur if the remote changes overlap with your modifications. The --merge flag attempts to merge automatically, but if conflicts arise, you must resolve them manually within the submodule directory. After resolving conflicts and committing in the submodule, return to the parent repository and commit the updated submodule reference. This ensures your conflict resolution is preserved in the project history.
Syncing Submodule URLs
git submodule sync --recursive
git submodule update --init --recursive
If the remote URL of a submodule changes, you need to sync the new URL before updating. The git submodule sync command updates the submodule URLs in your local configuration to match the .gitmodules file. The --recursive flag ensures nested submodules are also synchronized. After syncing, run git submodule update to fetch from the new URL and update the submodule content.
Viewing Submodule Changes
git diff --submodule
git log --submodule
To see what changed in submodules, use git diff with the --submodule flag. This shows the commit range that changed for each submodule, providing more context than just the commit hashes. The git log --submodule command displays the commit history including submodule updates, showing which commits modified submodule references. These commands help you understand what updates were made to dependencies over time.
Forcing Submodule Update
git submodule update --init --force --recursive
Sometimes submodules get into an inconsistent state due to interrupted operations or manual modifications. The --force flag discards any local changes in submodules and forces them to match the commits recorded in the parent repository. This is useful for resetting submodules to a known good state, but be aware that it destroys any uncommitted work in the submodules. Always check the status and stash or commit important changes before forcing an update.
Updating with Custom Git Commands
git submodule foreach 'git fetch origin && git checkout main && git pull origin main'
The git submodule foreach command executes a Git command in each submodule directory. This is powerful for running custom update logic that goes beyond the standard update commands. In this example, each submodule fetches from origin, checks out the main branch, and pulls the latest changes. You can use any shell command with foreach, making it flexible for complex update scenarios or batch operations across all submodules.
Stashing Changes Before Update
git submodule foreach 'git stash'
git submodule update --remote
git submodule foreach 'git stash pop'
If you have uncommitted changes in submodules that you want to preserve, stash them before updating. The first command stashes all changes in every submodule. After updating with git submodule update --remote, the last command restores the stashed changes. This workflow allows you to update to the latest commits while preserving your local modifications, avoiding the need to commit half-finished work.
Automating Submodule Updates in CI/CD
#!/bin/bash
# Update script for CI/CD pipelines
# Update all submodules to latest
git submodule update --init --recursive --remote
# Check if any submodules changed
if git diff --quiet HEAD; then
echo "No submodule updates"
exit 0
fi
# Commit updated submodules
git config user.name "CI Bot"
git config user.email "[email protected]"
git add .
git commit -m "chore: update submodules to latest versions"
git push origin main
This script automates submodule updates in CI/CD pipelines, keeping dependencies automatically synchronized. It updates all submodules, checks if any changes occurred, and commits the updates if needed. The git diff --quiet command returns zero only if there are no changes, allowing the script to skip unnecessary commits. This automation ensures submodules stay current without manual intervention, useful for continuous integration workflows.
Troubleshooting Common Update Issues
# Issue: Submodule update fails with "reference is not a tree"
git submodule sync
git submodule update --init --recursive
# Issue: Submodule shows modified but no actual changes
cd path/to/submodule
git status
git diff HEAD
git checkout -- .
cd ../..
# Issue: Submodule stuck in detached HEAD state
cd path/to/submodule
git checkout main
cd ../..
git add path/to/submodule
# Issue: Cannot update due to local changes
git submodule foreach 'git checkout -- .'
git submodule update --remote
Common submodule issues have standard solutions. When the reference error occurs, syncing URLs usually fixes it. Modified submodules with no visible changes often have line ending differences that can be reset. Detached HEAD state is normal for submodules but can be fixed by checking out a branch. Local changes preventing updates can be discarded with git checkout -- . if they are not needed. These troubleshooting steps resolve most submodule update problems quickly.
Comparing Submodule Versions
# Show submodule commit difference between branches
git diff branch1 branch2 --submodule
# Show detailed log of submodule changes
git log -p --submodule
# List all submodule commits between two versions
git diff branch1..branch2 -- .gitmodules
git submodule foreach 'git log --oneline branch1..branch2'
When working with branches or reviewing history, comparing submodule versions helps understand what changed. The first command shows which submodule commits differ between branches. The -p flag with --submodule provides detailed commit logs for each submodule change. The last example lists all commits in each submodule between two branches, useful for change log generation or dependency audits.
Working with Shallow Submodules
# Clone with shallow submodules for faster initialization
git clone --recurse-submodules --shallow-submodules https://github.com/user/project.git
# Update existing submodules to shallow copies
git submodule update --init --depth 1
# Convert shallow submodule to full history
cd path/to/submodule
git fetch --unshallow
cd ../..
Shallow submodules contain only recent history, significantly reducing clone and update times for large repositories. The --shallow-submodules flag during clone creates submodules with minimal history. For existing repositories, using --depth 1 with update creates a shallow copy. This approach is ideal for CI/CD pipelines or development environments where full history is unnecessary. If you later need the complete history, the fetch --unshallow command converts a shallow submodule to include all commits.
Managing Multiple Submodule Remotes
# Add alternate remote for a submodule
cd path/to/submodule
git remote add fork https://github.com/yourname/submodule-fork.git
git fetch fork
cd ../..
# Update specific submodule from alternate remote
cd path/to/submodule
git checkout fork/feature-branch
cd ../..
git add path/to/submodule
git commit -m 'Use forked submodule with custom changes'
# Switch back to original remote
cd path/to/submodule
git checkout main
git pull origin main
cd ../..
git add path/to/submodule
git commit -m 'Switch back to original submodule'
When contributing to submodule repositories or testing changes from forks, adding multiple remotes provides flexibility. You can fetch from the fork, test changes, and switch between different versions easily. This workflow is common when your project depends on a third-party library that you have forked for customization. The parent repository tracks which remote and branch each submodule uses, allowing different team members to work with different submodule sources if needed.
Best Practice Note
When updating submodules, always commit the updated submodule references in the parent repository to keep the project history consistent. After running git submodule update --remote, use git add and git commit to record the new submodule commits. This ensures other team members get the same submodule versions when they clone or pull the repository. For comprehensive guidance on submodule management, check out how to work with submodules in Git and how to add a submodule in Git. This is the same approach we use in CoreUI projects to maintain consistent dependency versions across all environments.



