Command Reference

git-submodule Tutorial

Manage external repository pointers inside a parent repository, with emphasis on committing submodule pointer updates in the superproject.

Who This Is For
  • Developers who already know basic commit and branch actions
  • Readers who want to understand command boundaries and risk
Prerequisites
  • A basic mental model of worktree, index, and commits
  • Comfort reading `git status` and a small commit graph
Common Risks
  • Using local cleanup commands on already shared history
  • Continuing to rewrite before confirming a recovery path

The short version

A submodule is a pointer from a parent repo to a specific commit in another repo.

Core commands

git submodule update --init --recursive
git submodule status
git submodule update --remote

Most important collaboration rule

After updating a submodule, commit the pointer change in the parent repository.

Typical update flow

  1. move submodule to intended commit
  2. return to superproject and inspect changes
  3. commit updated submodule pointer in superproject
git add path/to/submodule
git commit -m "chore: bump submodule foo to <sha>"

Frequent risk points

  • forgetting init/recursive update after clone
  • local uncommitted changes inside submodule during update
  • mismatch between recorded pointer and expected team version
Updating submodule content is not enough

If the superproject pointer is not committed, teammates will not get the intended submodule revision.

Good follow-up reads

  1. submodule update flow
  2. cross repository integration workflow
  3. git-bundle

What problem this command solves in a workflow

git submodule manages nested repositories as dependencies within a parent (superproject) repository. It solves the problem of "how do I incorporate and track external repositories at specific versions within my project?"

Typical use cases

  • Include a shared library or dependency as a submodule so that the parent repository pins it to a specific commit.
  • Initialize and update submodules after cloning with git submodule update --init --recursive, ensuring all nested repos are checked out at the correct versions.
  • When a submodule receives upstream updates, update the parent repo's recorded submodule pointer to the new commit.

Diagram view

Submodule dependency managementSubmodules link external repositories at specific commits. The parent repo tracks the submodule's commit hash, not its content.
Input
Submodule URLTarget path in parent repoSpecific commit reference
Output
.gitmodules config entryNested repository checkoutCommit pointer in parent
The parent repo stores only the submodule's commit hash, not its file content — submodule content must be fetched separately.

The submodule lifecycle: add → update → sync → remove

Adding a submodule

git submodule add <url> <path>
git commit -m "Add submodule at <path>"

This creates an entry in .gitmodules, clones the repository into the specified path, and stages both for commit.

Updating submodules

After cloning a repo with submodules, or when the parent repo's submodule pointer changes:

git submodule update --init --recursive
  • --init initializes any uninitialized submodules
  • --recursive handles nested submodules (submodules within submodules)
  • Use --remote to fetch the latest upstream changes instead of the pinned commit

Syncing submodule URLs

When a submodule's URL changes (e.g., migrating from HTTPS to SSH):

git submodule sync --recursive
git submodule update --init --recursive

sync updates the internal .git/config entries to match .gitmodules.

Removing a submodule

There is no single git submodule rm command. To properly remove one:

git submodule deinit <path>
git rm <path>
git commit -m "Remove submodule <path>"

This removes the submodule's entry from .gitmodules, cleans up .git/config, and removes the working directory.

Nested submodules

Submodules can contain their own submodules. The --recursive flag handles these:

git submodule update --init --recursive

Nested submodules add complexity:

  • Each level requires its own fetch and checkout
  • CI pipelines may need significant time to initialize deep hierarchies
  • Use --depth to shallow-fetch if you don't need full history

Understanding the .gitmodules file

.gitmodules lives at the root of your repository and tracks each submodule:

[submodule "libs/shared-utils"]
    path = libs/shared-utils
    url = https://github.com/example/shared-utils.git
    branch = main

Key fields:

  • path: where the submodule is checked out in the parent repo
  • url: the remote repository URL
  • branch: (optional) the default branch to track with --remote

Commit this file — it's the source of truth for submodule configuration.

Common pitfall: submodule pointer vs content mismatch

A submodule in Git is represented as a special entry in the parent repo's tree that stores only a commit SHA. This leads to a frequent problem:

  1. You update the submodule's content locally (cd into it, pull, make changes)
  2. But forget to git add the updated pointer in the parent repo
  3. The parent still points to the old commit

Symptom: git status in the parent shows modified: <path> (new commits, modified content).

Fix:

cd <submodule-path>
git commit -am "Update submodule content"
cd ..
git add <submodule-path>
git commit -m "Update submodule pointer"

Always verify with git diff --submodule to see exactly what changed.

Special cases and boundaries

  • Submodule updates are not automatically "absorbed" by the parent repo; you still need to commit the updated submodule pointer in the superproject.
  • After cloning a repository with submodules, run git submodule update --init --recursive to fetch and check out all nested repositories.
  • Submodules can be confusing for collaborators who are unfamiliar with them — consider documenting the workflow in your project README.
  • Changing a submodule's URL requires updating both .gitmodules and the internal .git/config, and all team members need to sync the change.