Git Submodules
Submodules allow you to include one Git repository inside another as a subdirectory, while keeping their histories separate.
Adding Submodules
# Add a submodule
git submodule add https://github.com/user/lib.git libs/lib
# Add at specific branch
git submodule add -b main https://github.com/user/lib.git libs/lib
# Initialize after clone
git clone --recurse-submodules https://github.com/user/repo.git
# Or initialize separately
git clone https://github.com/user/repo.git
git submodule init
git submodule update
Working with Submodules
# Update all submodules
git submodule update --remote
# Update specific submodule
git submodule update --remote libs/lib
# Fetch and merge submodule changes
cd libs/lib
git fetch
git merge origin/main
cd ../..
git add libs/lib
git commit -m "Update lib submodule"
# Pull with submodules
git pull --recurse-submodules
Submodule Configuration
# .gitmodules file
[submodule "libs/lib"]
path = libs/lib
url = https://github.com/user/lib.git
branch = main
# Configure to always update
git config --global submodule.recurse true
# Set submodule to track branch
git config -f .gitmodules submodule.libs/lib.branch main
Removing Submodules
# Remove submodule
git submodule deinit libs/lib
git rm libs/lib
rm -rf .git/modules/libs/lib
git commit -m "Remove lib submodule"
Submodule Status
# Check submodule status
git submodule status
# Show submodule summary
git submodule summary
# Show submodule diff
git diff --submodule
Git Subtrees
Subtrees merge another repository into a subdirectory of your project, keeping all history in one repository.
Adding Subtrees
# Add a subtree
git subtree add --prefix=libs/lib https://github.com/user/lib.git main --squash
# Without squash (keeps full history)
git subtree add --prefix=libs/lib https://github.com/user/lib.git main
Updating Subtrees
# Pull updates from subtree remote
git subtree pull --prefix=libs/lib https://github.com/user/lib.git main --squash
# Add remote for convenience
git remote add lib https://github.com/user/lib.git
# Then pull
git subtree pull --prefix=libs/lib lib main --squash
Contributing Back to Subtree
# Push changes back to subtree repository
git subtree push --prefix=libs/lib lib main
# Or create a branch
git subtree split --prefix=libs/lib -b lib-changes
git push lib lib-changes:main
Subtree Merge Strategy
# Alternative: Use merge strategy
git remote add lib https://github.com/user/lib.git
git fetch lib
git merge -s subtree --allow-unrelated-histories lib/main
Submodules vs Subtrees
| Submodules | Subtrees |
|---|---|
| Separate repositories | Single repository |
| Requires extra commands | Works with standard Git |
| Points to specific commit | Merges full history |
| Smaller repo size | Larger repo size |
| More explicit dependency | Simpler for contributors |
When to Use Which
Use Submodules when:
- Dependency is actively developed separately
- You need to track specific versions
- Repo size is a concern
- Team is comfortable with submodule workflow
Use Subtrees when:
- You want simpler workflow
- Contributors don't need to know about dependencies
- You're vendoring a library
- You rarely need to update the dependency
Practical Example: Monorepo with Subtrees
# Create monorepo structure
mkdir monorepo && cd monorepo
git init
# Add existing projects as subtrees
git subtree add --prefix=packages/frontend ../frontend main
git subtree add --prefix=packages/backend ../backend main
git subtree add --prefix=packages/shared ../shared main
# Structure:
# monorepo/
# ├── packages/
# │ ├── frontend/
# │ ├── backend/
# │ └── shared/
# └── .git/