Git Submodules Workflow

Create or Add a Submodule

Option A — Add an existing remote repo

cd parent-repo
git submodule add https://github.com/username/child-repo.git path/to/child-repo
git submodule update --init --recursive

Option B — Add a local repo as a submodule (no remote)

git submodule add ../child-repo path/to/child-repo
git submodule update --init --recursive

Working Inside the Submodule

cd path/to/child-repo
git status
git add .
git commit -m "Your descriptive message"

If a remote exists:

git push origin main

Update the Parent Repository Pointer

Once you’ve committed inside the submodule:

cd ../..                # back to parent repo
git status              # shows "modified: path/to/child-repo"
git add path/to/child-repo
git commit -m "Update submodule to latest commit"
git push origin main

Clone or Update Repos with Submodules

When cloning a parent that includes submodules:

git clone --recurse-submodules https://github.com/username/parent-repo.git

If you’ve already cloned without initializing:

git submodule update --init --recursive

To pull new commits from all submodules:

git submodule update --remote --merge

Maintenance Commands

ActionCommand
Re-sync submodule URLs after changing remotesgit submodule sync
Remove a submodule cleanlygit submodule deinit -f path/to/submodule && rm -rf .git/modules/path/to/submodule && git rm -f path/to/submodule
Check current submodule commitsgit submodule status
Update all submodules recursivelygit submodule update --remote --recursive

Directory and Config Structure

parent-repo/
├── .git/
├── .gitmodules
├── path/
   └── child-repo/
       ├── .git/  # submodule’s repo files
       └── ...

Example .gitmodules file:

[submodule "path/to/child-repo"]
    path = path/to/child-repo
    url = https://github.com/username/child-repo.git

Summary

┌─────────────┐
│  Developer  │
└──────┬──────┘

       │ Edits & commits
       ├──────────────────────────┐
       │                          │
       │                          │ Commits updated pointer
       ▼                          ▼
┌─────────────────┐        ┌───────────────┐
│ Submodule Repo  │        │  Parent Repo  │
│                 │        │               │
│ • Own commits   │        │ • Contains    │
│ • Own history   │◄───────┤   pointer     │
│                 │        │ • Tracks      │
└────────┬────────┘        │   commit hash │
         │                 │ • .gitmodules │
         │ Push (optional) │   + index     │
         │                 └──────┬────────┘
         ▼                        │
┌──────────────────┐              │ Push
│ Submodule Remote │              │
└──────────────────┘              ▼
                           ┌───────────────┐
                           │ Parent Remote │
                           └───────────────┘
  1. You commit changes inside the submodule.
  2. The submodule HEAD changes (new commit).
  3. The parent repo detects this and records the new commit hash.
  4. You commit that change in the parent.
  5. Each repo can be pushed independently.

Quick Reference

TaskLocationCommand
Add submoduleParentgit submodule add <url> path/to/child
Commit changesSubmodulegit add . && git commit -m "msg"
Push submoduleSubmodulegit push origin main
Update pointerParentgit add path/to/child && git commit -m "msg"
Push parentParentgit push origin main
Clone allgit clone --recurse-submodules <url>
Update allgit submodule update --remote --recursive