Submodules & Subtrees
Sometimes a project needs to include another Git repository inside it — a shared library, a set of configs, or a vendored dependency. Git provides two approaches for this: submodules and subtrees.
Git Submodules
A submodule is a reference to a specific commit in another repository. The parent repo does not store the submodule's code directly — it stores a pointer.
Adding a Submodule
git submodule add https://github.com/example/library.git libs/library
This creates:
- A
libs/library/directory with the cloned repo - A
.gitmodulesfile recording the URL and path - An entry in the index pointing to a specific commit
Cloning a Repo with Submodules
When someone clones your repo, submodule directories will be empty. They need:
git submodule update --init --recursive
Or clone with submodules in one step:
git clone --recurse-submodules https://github.com/example/project.git
Updating a Submodule
cd libs/library
git pull origin main
cd ../..
git add libs/library
git commit -m "Update library submodule"
Common Pitfalls
- Forgetting to init — freshly cloned repos have empty submodule directories
- Detached HEAD — submodules check out a specific commit, not a branch; you are always in detached HEAD state inside a submodule
- Out-of-sync pointers — if you update a submodule but forget to commit the parent repo, collaborators will not see the update
- CI/CD — build systems must be configured to init submodules
Git Subtree
A subtree merges another repository's code directly into your repo. There is no pointer — the files are actually part of your project.
Adding a Subtree
git subtree add --prefix=libs/library https://github.com/example/library.git main --squash
The --squash flag condenses the library's history into a single commit.
Updating a Subtree
git subtree pull --prefix=libs/library https://github.com/example/library.git main --squash
Pushing Changes Back
If you modify the subtree code and want to push upstream:
git subtree push --prefix=libs/library https://github.com/example/library.git main
Submodule vs Subtree
| Aspect | Submodule | Subtree |
|---|---|---|
| Storage | Pointer to external commit | Files merged into repo |
| Clone experience | Requires --recurse-submodules | Works out of the box |
| Collaborator setup | Must run submodule update --init | Nothing extra needed |
| History | Separate repo history | Merged into project history |
| Updating upstream | Pull inside submodule, commit parent | git subtree pull |
| Complexity | Higher — extra commands, easy mistakes | Lower — standard Git operations |
When to Use Each
Use submodules when:
- The dependency is large and you do not want it in your history
- You need to pin an exact version of the dependency
- The submodule is actively developed and has its own release cycle
Use subtrees when:
- Simplicity is more important than separation
- Collaborators should not need to learn extra commands
- You want a self-contained repo that works without internet access
Key Points
- Submodules store a pointer to an external repo; subtrees merge the code directly
- Submodules require extra setup steps (
--init,--recurse-submodules) - Subtrees are simpler for collaborators but mix histories
- Both approaches solve the problem of embedding one repo inside another