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 #

Add a nested repo, pinned to a commit
$ 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:

Cloning a repo with submodules
$ 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":

Move a submodule forward
$ 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"
TaskCommand
Fetch latest into all submodulesgit submodule update --remote
See what changedgit diff --submodule
Status of all nested reposgit submodule status
Remove onegit 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).

AlternativeBetter when
Package manager (npm, cargo)The dependency has releases and a registry
git subtreeYou want the code in-history, no pointer dance
Copy the filesSmall, stable, rarely-updated snippets

The pointers travel with ordinary push and pull; pinning releases uses tags inside the submodule. The Git series continues.