git
submodules
recursive update
version control
software development

Git update submodules recursively

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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

bash
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

bash
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

bash
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

bash
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
bash
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

bash
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

bash
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

bash
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

bash
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
bash
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

bash
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

bash
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

bash
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

bash
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)

Course illustration
Course illustration

All Rights Reserved.