Introduction
To update all Git submodules recursively (including nested submodules), run git submodule update --init --recursive. The --init flag initializes any submodules that have not been cloned yet, and --recursive processes submodules within submodules. This is the most common command for syncing submodules after a git pull or fresh clone. For pulling the latest changes from each submodule's remote, use git submodule update --remote --recursive.
The Essential Commands
Clone with Submodules
1# Clone and initialize all submodules in one step
2git clone --recurse-submodules https://github.com/user/repo.git
3
4# Equivalent to:
5git clone https://github.com/user/repo.git
6cd repo
7git submodule update --init --recursive
Update After Pull
1# After pulling changes to the main repo
2git pull
3
4# Update submodules to the commits recorded in the main repo
5git submodule update --init --recursive
Pull Latest from Submodule Remotes
1# Update all submodules to their latest remote commits
2git submodule update --remote --recursive
3
4# This fetches from each submodule's remote and checks out the latest commit
5# on the tracked branch (default: the branch specified in .gitmodules)
Understanding the Flags
1git submodule update [options]
2
3--init Initialize submodules that haven't been cloned yet
4--recursive Process nested submodules (submodules within submodules)
5--remote Fetch latest from each submodule's remote instead of using
6 the commit recorded in the parent repo
7--merge Merge the remote changes into the submodule's current branch
8--rebase Rebase the submodule's current branch onto the fetched commit
9--force Discard local changes in submodules
1# Common combinations:
2git submodule update --init --recursive # Standard sync
3git submodule update --remote --recursive # Pull latest from all remotes
4git submodule update --remote --merge --recursive # Pull and merge
5git submodule update --init --recursive --force # Force sync (discard local changes)
How Submodules Work
1# The parent repo tracks a specific COMMIT of each submodule
2# .gitmodules defines the submodule URLs
3
4cat .gitmodules
5# [submodule "libs/utils"]
6# path = libs/utils
7# url = https://github.com/user/utils.git
8# branch = main
9
10# The parent repo stores the pinned commit hash
11git ls-tree HEAD libs/utils
12# 160000 commit abc1234... libs/utils
13
14# "160000" is the submodule mode
15# "abc1234" is the exact commit the parent expects
When you run git submodule update, Git checks out commit abc1234 in libs/utils. When you run git submodule update --remote, Git fetches the latest from the remote and checks out the newest commit on the tracked branch.
Updating a Specific Submodule
1# Update only one submodule
2git submodule update --init --recursive -- libs/utils
3
4# Pull latest for one submodule
5git submodule update --remote -- libs/utils
6
7# Or cd into it and pull directly
8cd libs/utils
9git pull origin main
10cd ../..
11
12# Then commit the updated reference in the parent
13git add libs/utils
14git commit -m "Update libs/utils submodule to latest"
Recording Submodule Updates
1# After updating submodules to new remote commits
2git submodule update --remote --recursive
3
4# Check which submodules changed
5git status
6# modified: libs/utils (new commits)
7# modified: libs/parser (new commits)
8
9# Stage and commit the new submodule references
10git add libs/utils libs/parser
11git commit -m "Update submodules to latest versions"
12git push
The parent repo does not store the submodule code — it stores a pointer (commit hash). You must commit this pointer change for others to see the update.
Automating with git pull
1# Configure git pull to automatically update submodules
2git config --global submodule.recurse true
3
4# Now every git pull, checkout, and switch also updates submodules
5git pull # Automatically runs submodule update --recursive
1# Or add a post-merge hook
2# .git/hooks/post-merge
3#!/bin/sh
4git submodule update --init --recursive
Foreach: Run Commands in All Submodules
1# Run a command in every submodule
2git submodule foreach 'git checkout main && git pull'
3
4# Recursive version (includes nested submodules)
5git submodule foreach --recursive 'git checkout main && git pull'
6
7# Check status of all submodules
8git submodule foreach 'echo $name: $(git rev-parse --short HEAD)'
9
10# Clean all submodules
11git submodule foreach --recursive 'git clean -fd'
Troubleshooting
Submodule Directory is Empty
1# Submodule was not initialized
2git submodule update --init --recursive
3
4# If still empty, check if .gitmodules has the correct URL
5cat .gitmodules
6
7# Re-sync URLs
8git submodule sync --recursive
9git submodule update --init --recursive
Detached HEAD in Submodule
1# Submodules are in detached HEAD by default (normal behavior)
2cd libs/utils
3git status
4# HEAD detached at abc1234
5
6# To work on the submodule, create or checkout a branch
7git checkout main
8# Make changes...
9git commit -am "Fix bug"
10git push
11cd ../..
12git add libs/utils
13git commit -m "Update utils submodule"
URL Changed
1# Update submodule URL in .gitmodules
2git config -f .gitmodules submodule.libs/utils.url https://new-url.git
3
4# Sync the URL change
5git submodule sync --recursive
6
7# Then update
8git submodule update --init --recursive
Common Pitfalls
Forgetting --init on first clone: Without --init, submodules that have not been cloned are skipped. Always use --init unless you are sure all submodules are already initialized.
Confusing update with update --remote: git submodule update checks out the commit recorded in the parent repo. git submodule update --remote fetches and checks out the latest from the remote. The former syncs to what the parent expects; the latter updates to the newest code.
Not committing submodule reference changes: After git submodule update --remote, the parent repo has unstaged changes (new commit hashes). If you do not commit these, other developers and CI will still use the old commits.
Detached HEAD confusion: Submodules are always in detached HEAD state after update. This is normal. If you need to make changes in a submodule, checkout a branch first.
Nested submodules without --recursive: If a submodule itself contains submodules, git submodule update --init only processes the first level. Always include --recursive to handle all nesting levels.
Summary
Use git submodule update --init --recursive to sync submodules after clone or pull
Use git submodule update --remote --recursive to pull latest from submodule remotes
Use git clone --recurse-submodules to clone with all submodules in one step
Set git config submodule.recurse true to auto-update submodules on pull/checkout
Always commit submodule reference changes in the parent repo after updates
Use --recursive flag to handle nested submodules (submodules within submodules)