Tour of Git
Advanced Git / Lesson 5.11

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 .gitmodules file 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

AspectSubmoduleSubtree
StoragePointer to external commitFiles merged into repo
Clone experienceRequires --recurse-submodulesWorks out of the box
Collaborator setupMust run submodule update --initNothing extra needed
HistorySeparate repo historyMerged into project history
Updating upstreamPull inside submodule, commit parentgit subtree pull
ComplexityHigher — extra commands, easy mistakesLower — 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