- Developers who already know basic commit and branch actions
- Readers who want to understand command boundaries and risk
Command Reference
git-submodule Tutorial
Manage external repository pointers inside a parent repository, with emphasis on committing submodule pointer updates in the superproject.
- A basic mental model of worktree, index, and commits
- Comfort reading `git status` and a small commit graph
- 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
- move submodule to intended commit
- return to superproject and inspect changes
- 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
If the superproject pointer is not committed, teammates will not get the intended submodule revision.
Good follow-up reads
submodule update flowcross repository integration workflowgit-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
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
--initinitializes any uninitialized submodules--recursivehandles nested submodules (submodules within submodules)- Use
--remoteto 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
--depthto 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:
- You update the submodule's content locally (cd into it, pull, make changes)
- But forget to
git addthe updated pointer in the parent repo - 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 --recursiveto 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
.gitmodulesand the internal.git/config, and all team members need to sync the change.