Ch. 16 · Git & CI/CD

Git Submodules vs Subtrees

Embed another repository with submodules or subtrees, and choose based on how often it changes and who owns it.

~2 min readadvancedupdated Oct 5, 2026

Sharing code between repositories needs a mechanism to include one inside another. A submodule records a pointer to a specific commit in the other repo; a subtree copies the other repo’s content into yours. They differ in where the history lives and how changes flow.

Before you start

You should be comfortable with cloning and commits. This article compares the two approaches.

Step-by-step walkthrough

Step 1: Submodules keep a pointer

git submodule add records the URL and a specific commit. The parent repo stores a pointer, not the files, so a clone must run git submodule update --init to populate it. The submodule has its own history and is updated by moving the pointer.

Step 2: Subtrees copy content into the parent

git subtree add merges the other repo’s history into a subdirectory of yours, so the files are present in a normal clone with no extra steps. Updates pull or push between the two with git subtree pull and git subtree push.

Step 3: Choose by ownership and change rate

Submodules suit a dependency you do not modify, whose history should stay separate, at the cost of clone and update friction. Subtrees suit shared code you sometimes modify, with a simpler clone, at the cost of duplicated history and more complex merges.

Worked scenario

A fresh clone needs an extra step for a submodule.

git clone --recurse-submodules <url>
# or, after a plain clone:
git submodule update --init --recursive
Terminal

Walk through the example

The clone brings the parent repo and, with the flag, the submodule content at the recorded commit. A plain clone leaves the submodule directory empty, which is a common surprise. A subtree would already be present, because its files live in the parent history.

Common mistake

Cloning without initializing submodules and finding empty directories, or updating a submodule’s files without committing the moved pointer in the parent, so the change is lost.

Verify the behavior

Clone a repo with a submodule and confirm the directory is empty without init and populated with it. Change the submodule commit and confirm the parent shows a pointer change. For a subtree, confirm the files appear in a plain clone.

Interview exercise

Why is a submodule’s directory empty after a plain clone?

Answer and reasoning

Because the parent repository stores only a pointer to a commit in the submodule’s remote, not the files themselves. Cloning the parent does not clone the submodule, so you must run git submodule update --init (or clone with --recurse-submodules) to fetch its content at the recorded commit. A subtree embeds the files, so it needs no such step.

Continue learning

Compare vendoring in Ignore tracked files and large files in LFS. Read the Git submodules documentation and try the Git interview questions.

More in Git & CI/CD

read ✓Git & CI/CD · easy

Git Detached HEAD

What a detached HEAD is, when it happens, and how to keep commits made in that state.

~2 min readread →
read ✓Git & CI/CD · mid

Git Hooks and Local Automation

Automate checks with client and server hooks, share them through a framework, and enforce the same rules in CI where they cannot be bypassed.

~2 min readread →
read ✓Git & CI/CD · hard

Git LFS for Large Files

Track large binaries with Git LFS, keep pointer files in the repository, and plan for LFS storage and access.

~2 min readread →
esc