How to Add and Manage Git Submodules
In this tutorial, you'll learn about How to Add and Manage Git Submodules. We cover key concepts, practical examples, and best practices.
The Problem
You need to include another Git repository (a shared library, theme, or utility) inside your project while keeping their histories separate. Manually copying files means you lose upstream updates and can't track changes back to the source. Git submodules let you embed one repository inside another as a reference to a specific commit, preserving both histories independently.
Quick Fix
1. Add a submodule
git submodule add https://github.com/example/shared-lib.git libs/shared
This clones the repo into libs/shared and creates a .gitmodules file.
2. Clone a repository with submodules
# Clone and initialize in one step
git clone --recurse-submodules https://github.com/user/project.git
# Or clone first, then initialize
git clone https://github.com/user/project.git
cd project
git submodule update --init --recursive
3. Update submodules to latest commit
# Pull latest for all submodules
git submodule update --remote --merge
# Update a specific submodule
cd libs/shared
git pull origin main
cd ..
git add libs/shared
git commit -m "Update shared-lib to latest"
4. Check submodule status
git submodule status
Expected output:
abc123def456 libs/shared (heads/main)
789ghi012jkl libs/theme (v2.1.0)
A - prefix means not initialized, + means modified but not committed to parent.
5. Remove a submodule completely
git submodule deinit -f libs/shared
rm -rf .git/modules/libs/shared
git rm -f libs/shared
git commit -m "Remove shared-lib submodule"
6. Work on a submodule
cd libs/shared
git checkout main
# Make changes, commit
git add .
git commit -m "Fix bug in shared lib"
git push origin main
cd ../..
# Update parent to reference the new commit
git add libs/shared
git commit -m "Update shared-lib with bugfix"
7. Pin submodule to a specific tag
cd libs/shared
git checkout v2.1.0
cd ../..
git add libs/shared
git commit -m "Pin shared-lib to v2.1.0"
Common Causes
| Problem | Symptom | Fix |
|---|---|---|
| Submodule not cloned | Empty directory | git submodule update --init --recursive |
| Detached HEAD in submodule | git status shows detached HEAD |
git checkout main or a tag |
| Outdated submodule | Submodule points to old commit | git submodule update --remote --merge |
| Changed submodule not tracked | Parent repo doesn't show changes | git add the submodule path |
| .gitmodules not committed | Other users can't initialize | Commit .gitmodules file |
Practice with a Test Repository
cd /tmp
mkdir git-practice && cd git-practice
git init --initial-branch=main
# Initialized empty Git repository in /tmp/git-practice/.git/
echo "test" > file.txt && git add . && git commit -m "init"
# [main (root-commit) abc1234] init
Before running destructive commands on your real repository, practice on a throwaway test repository. This builds confidence and prevents costly mistakes. The reflog is your safety net, but practice makes it less needed.
Prevention
- Pin submodules to a stable tag instead of
mainfor production - Document submodule commands in your project's CONTRIBUTING.md
- Use
git submodule statusin CI to verify consistent versions across builds
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro