Free — no signup required

Git Submodules: A Repository Inside a Repository

2 min read

The Use Case

Your organization maintains a shared authentication library used by five different services. Each service is its own Git repository. You want each service to depend on a specific, pinned version of the auth library — not just "whatever is latest."

Git Submodules solve this by embedding a reference to another repository inside your repository. The key word is reference: Git does not copy the files. It stores a single pointer — the SHA-1 hash of a specific commit in the external repository.

The main repository's .gitmodules file records the mapping — which local path the submodule lives at, and which remote URL it comes from — and the repository's tree stores a special "gitlink" entry for that directory containing only the commit SHA, not the files themselves.

The Detached HEAD Problem

When you clone a repository that contains submodules, the submodule directory is empty by default. It has to be explicitly initialized and populated — a separate step from the clone itself, recursively if submodules are nested inside submodules.

Once populated, the submodule is checked out at the specific commit the pointer references — not at the tip of any branch. This puts the submodule in a Detached HEAD state. If you cd into the submodule directory, make changes, and commit without first creating a branch, those commits are attached to no branch and are at risk of being garbage-collected by Git. The safe workflow is to create a branch inside the submodule before committing anything there, then — once the fix is committed — step back up to the parent repository and commit the updated pointer separately, so the parent records exactly which submodule commit it now depends on.

The Update Trap

When a teammate updates the submodule pointer and pushes the main repository, pulling that change fetches the new pointer — but does not update the files in the submodule directory. The local submodule still has the old commit checked out. This is the most common source of "it works on my machine" bugs with submodules. The fix is the same initialize-and-populate step from cloning, run again after every pull — which is exactly why many teams automate it as a post-merge Git hook rather than relying on everyone remembering to run it by hand.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free