A submodule is a git repository nested inside another — pinned to a specific commit, versioned by a pointer rather than a copy. It is git's official answer to "this project depends on that repository at exactly this version", and it is also the most commonly misused feature in git. The difference is knowing what the pointer actually records.
Adding and cloning #
$ git submodule add https://github.com/aospix/shared-ui libs/shared-ui
$ git commit -m "add shared-ui at 9a8b7c"
The parent repo now stores two things: the submodule's URL, and the exact commit hash it should be at. The submodule's own directory is a full clone with its own HEAD, branches, and remotes — changes inside it are commits in that repository, then a pointer bump in the parent.
The clone trap: a plain git clone of the parent fetches the submodule as an empty directory. The flags that make it whole:
$ git clone --recurse-submodules https://github.com/aospix/site.git
# or, for an existing clone:
$ git submodule update --init --recursive
Updating: two pointers, one direction #
The workflow is always "advance inside, then bump outside":
$ cd libs/shared-ui
$ git fetch && git switch main && git pull
$ cd ../..
$ git diff --submodule # shows old -> new hash
$ git add libs/shared-ui && git commit -m "shared-ui: bump to include toc fix"
| Task | Command |
|---|---|
| Fetch latest into all submodules | git submodule update --remote |
| See what changed | git diff --submodule |
| Status of all nested repos | git submodule status |
| Remove one | git rm libs/shared-ui (+ cleanup) |
update --remote moves to the submodule's default branch tip — convenient for development, exact-version pins for releases come from committing the hash deliberately.
When to use them, honestly #
Submodules fit when the dependency is versioned like code and you want the pin explicit: a shared component library consumed by several products, vendored firmware, a docs theme. They fit badly when the answer is "always latest" (that is a package manager job) or "loosely related" (that is two repositories).
| Alternative | Better when |
|---|---|
| Package manager (npm, cargo) | The dependency has releases and a registry |
| git subtree | You want the code in-history, no pointer dance |
| Copy the files | Small, stable, rarely-updated snippets |
The pointers travel with ordinary push and pull; pinning releases uses tags inside the submodule. The Git series continues.
