Submodules & Subtrees
Quick Summary
Submodules and subtrees let you include one Git repository inside another. Submodules keep the nested repo as a separate entity (linked by reference). Subtrees merge the nested repo's code directly into your project. Both solve the same problem — sharing code across repositories — but with very different trade-offs.
Submodules
A submodule is a pointer to a specific commit in another repository.
Adding a Submodule
git submodule add git@github.com:donnyaw/shared-lib.git libs/shared
git commit -m "Add shared-lib submodule"
This creates:
libs/shared/— directory with the submodule content.gitmodules— configuration file mapping paths to URLs
Cloning a Repo with Submodules
# Clone and initialize submodules in one step
git clone --recurse-submodules git@github.com:donnyaw/project.git
# Or after a regular clone
git submodule init
git submodule update
Updating Submodules
# Pull latest from submodule's remote
cd libs/shared
git pull origin main
cd ../..
# Commit the updated submodule reference
git add libs/shared
git commit -m "Update shared-lib to latest"
Removing a Submodule
git submodule deinit libs/shared
git rm libs/shared
rm -rf .git/modules/libs/shared
git commit -m "Remove shared-lib submodule"
Subtrees
A subtree copies the external repo's content directly into your project tree.
Adding a Subtree
git subtree add --prefix=libs/shared git@github.com:donnyaw/shared-lib.git main --squash
git commit -m "Add shared-lib as subtree"
Updating a Subtree
git subtree pull --prefix=libs/shared git@github.com:donnyaw/shared-lib.git main --squash
Pushing Changes Back
git subtree push --prefix=libs/shared git@github.com:donnyaw/shared-lib.git main
Comparison
| Aspect | Submodules | Subtrees |
|---|---|---|
| Storage | Pointer (reference) | Full copy in tree |
| Clone behavior | Requires --recurse-submodules | Just works (normal clone) |
| Complexity | Higher (extra commands) | Lower (standard Git) |
| Offline work | Need to init/update | Always available |
| Contributing back | cd into submodule, commit/push | git subtree push |
| History | Separate (linked repo) | Merged into main repo |
| Best for | Large, independent repos | Small, tightly-coupled shared code |
When to Use Each
| Scenario | Recommendation |
|---|---|
| Shared library used by many projects | Submodule |
| Vendor/third-party code you rarely update | Subtree |
| You want contributors to "just clone and build" | Subtree |
| You need independent version control for the nested repo | Submodule |
| Small utility that changes infrequently | Subtree |
Best Practices
- Prefer subtrees for simplicity — they don't require extra setup from contributors
- Use submodules for large, independent projects — keeps repo sizes manageable
- Always use
--squashwith subtrees — avoids cluttering your history with the external repo's full history - Document submodule setup in your README — many developers forget
--recurse-submodules
What's Next
- Hooks & Automation — Run scripts automatically on Git events
- Troubleshooting — Fix common Git problems