Git Submodules & Subtrees: Managing Dependencies Across Repos
In this tutorial, you'll learn about Git Submodules & Subtrees: Managing Dependencies Across Repos. We cover key concepts, practical examples, and best practices.
Git submodules embed a specific commit from another repository as a subdirectory, while subtree copies the external repository's history into your own.
In this tutorial, you'll learn Git submodules and subtrees — two built-in mechanisms for including external repositories inside your project. Both let you track dependencies from other repositories, but they work very differently. By the end, you'll know when to use each and how to manage them effectively.
flowchart TD
A[Need external dependency] --> B{How to include?}
B --> C[Submodule]
B --> D[Subtree]
C --> E[Reference to external repo]
C --> F[Locked to specific commit]
D --> G[Copied into your repo]
D --> H[Full history included]
E --> I[Submodule update fetches]
G --> J[Subtree pull merges]
Git Submodules: Referencing External Repos
Add a submodule to your project:
git submodule add https://github.com/user/lib.git libs/my-lib
git commit -m "Add my-lib as submodule"
Expected output:
Cloning into 'libs/my-lib'...
remote: Enumerating objects: 50, done.
Receiving objects: 100% (50/50), done.
This creates a .gitmodules file tracking the submodule configuration:
[submodule "libs/my-lib"]
path = libs/my-lib
url = https://github.com/user/lib.git
Cloning a Repo with Submodules
When cloning a project that uses submodules:
git clone --recursive https://github.com/user/project.git
Or update submodules after a regular clone:
git clone https://github.com/user/project.git
git submodule update --init --recursive
Updating Submodules
Pull the latest from each submodule's configured branch:
git submodule update --remote
git add .
git commit -m "Update submodules to latest versions"
Git Subtree: Copying External Repos
Add a subtree to include external code:
git subtree add --prefix=libs/my-lib https://github.com/user/lib.git main --squash
Expected output:
git fetch https://github.com/user/lib.git main
Added dir 'libs/my-lib'
Pull updates from the external repo:
git subtree pull --prefix=libs/my-lib https://github.com/user/lib.git main --squash
Submodules vs Subtrees
| Feature | Submodule | Subtree |
|---|---|---|
| Reference type | Pointer to external commit | Copied content |
| History | Stored externally | Included in your repo |
| Clone cost | Separate fetch per submodule | Single clone, larger repo |
| Modify from parent | Read-only by default | Can edit and push back |
| Team friction | Team must learn submodule commands | Transparent to team |
| Atomic dependency updates | Update + commit | Subtree pull + commit |
| CI/CD complexity | Extra checkout step | No extra steps |
Working with Submodule Branches
By default, submodules point to a specific commit. Track a branch instead:
# Configure submodule to track a branch
git config -f .gitmodules submodule.libs/my-lib.branch main
# Update to latest commit on that branch
git submodule update --remote
# The .gitmodules entry now shows:
# [submodule "libs/my-lib"]
# path = libs/my-lib
# url = https://github.com/user/lib.git
# branch = main
Subtree Push Back to Upstream
Unlike submodules, subtrees let you push changes back upstream:
# Make changes in libs/my-lib
cd libs/my-lib
echo "new feature" >}} feature.py
cd ../..
# Split the changes back to the external repo
git subtree push --prefix=libs/my-lib https://github.com/user/lib.git main
Common Errors
| Error | Cause | Fix |
|---|---|---|
fatal: not a git repository in submodule |
Submodule not initialized | Run git submodule update --init |
No URL configured for submodule |
Missing from .gitmodules |
Check .gitmodules file |
detached HEAD in submodule |
Submodule points to commit, not branch | git submodule update --remote |
Pathspec 'libs/my-lib' is in submodule |
Git commands can't cross submodule boundary | cd into submodule then run commands |
| Subtree prefix already exists | Directory already in use | Remove directory or choose different prefix |
fatal: refusing to merge unrelated histories |
Subtree fetch issues | Use git subtree pull --squash |
| Submodule shows as modified | Expected behavior | Commit the submodule change in parent |
| Slow clone with many submodules | Each submodule cloned separately | Use --depth 1 on submodules |
Practice Questions
Challenge
Create a repository that depends on a public library via submodule. Add the submodule, make a change to how it's used in the parent, update the submodule to a newer version, and commit. Then remove the submodule and re-add the same dependency using subtree. Compare the resulting repository sizes.
Real-World Task
You maintain a shared authentication library in one repository and an application in another. Use Git submodules to include the auth library in the app repo. Set up a CI/CD pipeline that runs submodule initialization during the build. Configure Dependabot-style automated updates using a cron job that runs git submodule update --remote and creates a PR. This mirrors how DodaTech manages shared libraries across DodaZIP and Durga Antivirus Pro.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro