Submodules & Subtrees
This chapter closes the loop on something teased across the entire series — Course 1, Chapter 9's broken-gitlink incident, and Course 2, Chapter 10's full diagnosis. Both showed what happens when a folder ends up tracked as a gitlink by accident. This chapter covers the two real, supported ways to do this deliberately: submodules, set up correctly this time, and subtrees, an entirely different approach that sidesteps the gitlink mechanism altogether.
Submodules, Done Properly
Chapter 1 explained the mechanism: a submodule is a gitlink (tree entry, mode 160000) pointing at a commit hash in a separate repository, plus a .gitmodules file recording the URL and path. Adding one correctly:
Cloning into 'libs/ui'...
$ git status
new file: .gitmodules
new file: libs/ui
$ git commit -m "Add shared-ui-components as a submodule"
[submodule "libs/ui"]
path = libs/ui
url = https://github.com/someone/shared-ui-components.git
The part everyone gets wrong: cloning a repo WITH submodules
A plain git clone creates the gitlink-tracked folder but leaves it empty — the submodule's actual content needs a separate, explicit step.
$ git clone --recurse-submodules https://github.com/you/project.git
$ # ...or, after a plain clone, populate them after the fact
$ git submodule update --init --recursive
git submodule update --init, not a diagnostic investigation. Checking for a valid .gitmodules entry (Course 2, Chapter 10's key distinguishing signal) tells you immediately which situation you're in.
Updating a submodule to a newer commit
$ git pull origin main
$ cd ../..
$ # The parent repo sees this as a change — the gitlink now points at a newer commit
$ git add libs/ui
$ git commit -m "Update ui submodule to latest"
Subtrees — A Different Approach Entirely
git subtree takes the opposite philosophy: instead of a pointer to another repository, it actually copies the other project's files directly into your repo, as ordinary tracked content — no gitlink, no .gitmodules, no separate clone step for anyone using the repo.
After this, libs/ui contains ordinary tracked files — anyone cloning your repo gets them automatically, with zero extra steps, exactly like every other file in the project.
Submodules vs Subtrees
Cloning: requires an extra step (--recurse-submodules or submodule update).
Best for: when you want to track an exact upstream version and occasionally pull in updates from the original project cleanly.
Cloning: works exactly like any other clone — nothing extra required.
Best for: simpler day-to-day use, especially when contributors shouldn't need to learn submodule-specific commands at all.
Choosing Between Them
| Situation | Use |
|---|---|
| Need to track an exact upstream version, pull updates occasionally | Submodule |
| Want zero extra steps for anyone cloning the repo | Subtree |
| Team unfamiliar with submodule workflow, wants simplicity | Subtree |
| Need to occasionally contribute changes back upstream | Submodule — keeps a clean separate history to push from |
| Just need a folder with normal tracked content, no real "other project" | Neither — this is the broken-gitlink trap; just track it normally |
Command Reference
| Command | What it does |
|---|---|
| git submodule add <url> <path> | Adds a properly configured submodule, creating/updating .gitmodules |
| git clone --recurse-submodules <url> | Clones a repo and populates its submodules in one step |
| git submodule update --init --recursive | Populates submodules after a plain clone left them empty |
| git subtree add --prefix=<path> <url> <branch> --squash | Copies another repo's files directly into your repo as ordinary content |
Chapter 4 Quick Reference
- A proper submodule always has a matching .gitmodules entry — the exact thing the broken incident lacked
- Plain git clone leaves submodules empty — use --recurse-submodules or submodule update --init afterward; this is normal, not broken
- git subtree copies another project's files directly into your repo — no gitlink, no extra clone steps for anyone
- Submodule — best when tracking an exact upstream version and occasionally contributing back
- Subtree — best for simplicity, zero extra steps for contributors
- Most projects need neither — a folder of files that's just part of your project should be tracked normally
- Next chapter: advanced GitHub Actions — matrix builds, secrets, caching, and deployment pipelines