Skip to main content
Submodules allow you to keep a Git repository as a subdirectory of another Git repository. This lets you clone another repository into your project while keeping the commits separate.

What Are Submodules?

A submodule is a repository embedded inside another repository (the superproject). The submodule has its own history, while the superproject tracks which specific commit of the submodule should be checked out.
superproject/
├── .git/
│   └── modules/
│       └── libs/utils/    # Submodule's Git directory
├── .gitmodules            # Submodule configuration
├── src/
└── libs/
    └── utils/             # Submodule working directory
        └── .git           # Link to actual Git directory

When to Use Submodules

Good Use Cases

  1. External dependencies - Pin specific versions of libraries
  2. Shared code - Reuse code across multiple projects
  3. Large binary assets - Keep them in a separate, shallow-cloned repository
  4. Monorepo splitting - Separate access controls for different components

When NOT to Use Submodules

  • Simple dependencies (use package managers instead)
  • Frequently changing code you control (keep in the main repo)
  • When team members aren’t familiar with submodules

Basic Submodule Operations

Adding a Submodule

git submodule add <repository-url> <path>
Example:
git submodule add https://github.com/user/lib.git libs/external-lib
This:
  1. Clones the repository to libs/external-lib
  2. Creates/updates .gitmodules file
  3. Stages the changes
The .gitmodules file:
[submodule "libs/external-lib"]
    path = libs/external-lib
    url = https://github.com/user/lib.git

Cloning a Repository with Submodules

1
Clone the superproject
2
git clone https://github.com/user/project.git
3
Submodule directories exist but are empty.
4
Initialize and update submodules
5
git submodule init
git submodule update
6
Or do it in one step during clone
7
git clone --recurse-submodules https://github.com/user/project.git

Updating Submodules

# Update all submodules to latest commit from their tracked branches
git submodule update --remote

# Update specific submodule
git submodule update --remote libs/external-lib

Viewing Submodule Status

# Show submodule status
git submodule status

# Output:
# a1b2c3d libs/external-lib (v1.2.3)
# -f4e5d6c libs/another (heads/main)
Prefix meanings:
  • No prefix: Submodule is checked out at the correct commit
  • -: Submodule is not initialized
  • +: Submodule is checked out at a different commit than recorded
  • U: Submodule has merge conflicts

Working with Submodules

Making Changes in a Submodule

2
cd libs/external-lib
3
Make changes and commit
4
git checkout -b feature-branch
# Edit files
git commit -am "Add new feature"
git push origin feature-branch
5
Update the superproject
6
cd ../..
git add libs/external-lib
git commit -m "Update external-lib to include new feature"

Updating to a Specific Submodule Version

# Navigate to submodule
cd libs/external-lib

# Check out specific version
git checkout v1.2.3

# Update superproject
cd ../..
git add libs/external-lib
git commit -m "Update external-lib to v1.2.3"

Pulling Changes with Submodules

# Pull changes in superproject
git pull

# Update submodules to match
git submodule update --init --recursive

# Or do both at once
git pull --recurse-submodules

Advanced Submodule Features

Configuring Submodule Behavior

# Set default branch for submodule to track
git config -f .gitmodules submodule.libs/external-lib.branch main

# Set update strategy (merge, rebase, or checkout)
git config -f .gitmodules submodule.libs/external-lib.update merge

# Ignore submodule changes in git status
git config submodule.libs/external-lib.ignore all

Active Submodules

Control which submodules are active:
# Activate only specific submodules
git config submodule.libs/external-lib.active true

# Use pathspec to activate matching submodules
git config submodule.active 'libs/*'

Foreach: Run Commands on All Submodules

# Run command in each submodule
git submodule foreach 'git fetch'

# More complex example
git submodule foreach 'git checkout main && git pull'

# Include superproject
git submodule foreach --recursive 'echo "$name: $path"'

Submodule Workflows

Third-Party Library Workflow

# Add the library
git submodule add https://github.com/third-party/lib.git vendor/lib
git commit -m "Add third-party library"

# Occasionally update to new version
cd vendor/lib
git fetch
git checkout v2.0.0
cd ../..
git add vendor/lib
git commit -m "Update library to v2.0.0"

Shared Code Workflow

# Enable recursive operations by default
git config --global submodule.recurse true

# Clone with submodules
git clone --recurse-submodules <url>

# Regular work - most commands now recurse automatically
git pull
git checkout <branch>
git status

Development Across Repositories

# Work on submodule and superproject simultaneously
cd libs/shared
git checkout -b feature-branch
# Make changes
git commit -am "Add feature to shared library"
git push origin feature-branch

cd ../..
git add libs/shared
git commit -m "Update to shared library feature branch"

Removing Submodules

1
Deinitialize the submodule
2
git submodule deinit libs/external-lib
3
This empties the working directory but keeps the Git directory.
4
Remove from the working tree and .gitmodules
5
git rm libs/external-lib
6
Commit the changes
7
git commit -m "Remove external-lib submodule"
8
Optionally, remove the Git directory
9
rm -rf .git/modules/libs/external-lib

Common Issues and Solutions

Detached HEAD in Submodule

Problem: Submodules are often in detached HEAD state. Solution: Check out a branch before making changes:
cd libs/external-lib
git checkout main
# Make changes

Submodule Changes Not Showing

Problem: Modified submodules don’t appear in git status. Solution:
# Show submodule changes
git status --submodule

# Or configure it permanently
git config --global status.submoduleSummary true

Accidentally Committed Submodule Directory

Problem: Added submodule directory as regular files instead of a submodule. Solution:
# Remove from index but keep files
git rm -r --cached libs/external-lib

# Add as submodule
git submodule add <url> libs/external-lib

Submodule URL Changed

Problem: Submodule repository moved. Solution:
# Update URL in .gitmodules
git config -f .gitmodules submodule.libs/external-lib.url <new-url>

# Sync to .git/config
git submodule sync

# Update
git submodule update --remote

Alternatives to Submodules

Git Subtree

Pros:
  • Simpler for contributors (no special commands)
  • History is integrated into the superproject
Cons:
  • Harder to push changes back upstream
  • Larger repository size
# Add a subtree
git subtree add --prefix libs/external-lib https://github.com/user/lib.git main --squash

Package Managers

For language-specific dependencies:
  • npm (JavaScript)
  • pip (Python)
  • gem (Ruby)
  • cargo (Rust)

Monorepo Tools

For large-scale projects:
  • Google’s Repo tool
  • Meta’s Sapling
  • Microsoft’s Rush

Best Practices

  1. Pin to specific commits or tags - Don’t track branches in production
  2. Document submodule workflow - Add README section explaining submodule usage
  3. Use shallow clones for large repositories:
    git submodule update --init --depth 1
    
  4. Configure global recurse setting:
    git config --global submodule.recurse true
    
  5. Use relative URLs when possible:
    [submodule "lib"]
        url = ../lib.git  # Relative to superproject URL
    
  6. Regular updates - Update submodules regularly to avoid drift
  7. Test after updating - Always test the superproject after updating submodules

Configuration Reference

# Global config (~/.gitconfig)
[submodule]
    # Recurse into submodules by default
    recurse = true

[status]
    # Show submodule changes in git status
    submoduleSummary = true

[diff]
    # Show submodule changes in git diff
    submodule = log

# Repository config (.git/config or .gitmodules)
[submodule "libs/external-lib"]
    url = https://github.com/user/lib.git
    branch = main
    update = merge
    active = true
    ignore = dirty  # or: all, untracked, none

Submodule Commands Reference

# Add submodule
git submodule add <url> <path>

# Initialize submodules
git submodule init

# Update submodules
git submodule update
git submodule update --remote
git submodule update --init --recursive

# Status
git submodule status
git submodule summary

# Execute command in all submodules
git submodule foreach '<command>'

# Sync URLs from .gitmodules to .git/config
git submodule sync

# Deinitialize submodule
git submodule deinit <path>

# Remove submodule
git rm <path>

Build docs developers (and LLMs) love