<!--order=12-->
<h1>Git: Submodules</h1>
<p>Submodules let you embed one Git repository inside another as a subdirectory. The outer repo records a reference to a specific commit in the inner repo — not a branch, a specific commit. This lets you pin a dependency at a known-good version while keeping it as a separate, independently-versioned project. Submodules have a reputation for being confusing; this covers the full workflow.</p>

<h2>Commands Covered</h2>
<p><code>git submodule</code></p>

<h2>Adding a Submodule</h2>
<pre>
# Add a submodule at a default path (uses repo name as directory)
$ git submodule add https://github.com/user/library.git

# Add a submodule at a specific path
$ git submodule add https://github.com/user/library.git vendor/library

# This creates/modifies two things:
# 1. .gitmodules file (tracked in repo — records URL and path)
# 2. vendor/library/ directory (the actual submodule content)

# The directory is recorded in the parent repo as a special "gitlink" entry.
# Commit both:
$ git add .gitmodules vendor/library
$ git commit -m "Add library as submodule"
</pre>

<h2>Cloning a Repo with Submodules</h2>
<pre>
# Clone without initializing submodules (submodule dirs are empty)
$ git clone https://github.com/user/project.git

# Initialize and populate all submodules after cloning
$ git submodule init
$ git submodule update

# Or do it all in one step:
$ git clone --recurse-submodules https://github.com/user/project.git

# If you forgot --recurse-submodules, catch up after cloning:
$ git submodule update --init
# Or recursively (for submodules within submodules):
$ git submodule update --init --recursive
</pre>

<h2>Updating Submodules</h2>
<pre>
# Update all submodules to the commit recorded in the parent repo
$ git submodule update

# Update and initialize any new submodules at the same time
$ git submodule update --init

# Update a submodule to the latest commit on its tracked branch
# (Changes what commit the parent records — this is an intentional update)
$ cd vendor/library
$ git fetch
$ git checkout main
$ git pull
$ cd ../..
$ git add vendor/library
$ git commit -m "Update library to latest main"

# Alternatively, use submodule update --remote:
$ git submodule update --remote vendor/library
# This fetches and checks out the latest of the submodule's tracking branch
# Then commit the new reference:
$ git add vendor/library
$ git commit -m "Update library submodule"

# Update all submodules to their remote tracking branch:
$ git submodule update --remote
</pre>

<h2>Working Inside a Submodule</h2>
<pre>
# Submodules start in "detached HEAD" state — not on any branch.
# To make changes, first switch to a branch inside the submodule:
$ cd vendor/library
$ git switch main   # or the branch you want to work on
# ... make changes ...
$ git commit -m "Fix in library"
$ git push origin main
$ cd ../..
$ git add vendor/library    # record the new commit in the parent
$ git commit -m "Update library submodule to include bug fix"
$ git push
</pre>

<h2>git submodule status and foreach</h2>
<pre>
# Show status of all submodules
$ git submodule status
# abc1234 vendor/library (heads/main)
# - prefix = not initialized
# + prefix = checked out commit differs from recorded commit
# U prefix = merge conflict

# Run a command in every submodule
$ git submodule foreach 'git fetch'
$ git submodule foreach 'git status'
$ git submodule foreach --recursive 'git log --oneline -5'

# Pull latest in every submodule
$ git submodule foreach 'git pull origin main'
</pre>

<h2>Removing a Submodule</h2>
<pre>
# Removing a submodule requires several steps:
# 1. Remove from .gitmodules
$ git submodule deinit -f vendor/library

# 2. Remove from .git/config
$ git rm -f vendor/library

# 3. Remove the .git/modules cache
$ rm -rf .git/modules/vendor/library

# 4. Commit
$ git commit -m "Remove library submodule"

# Shortcut (Git 2.41+):
$ git rm vendor/library
$ git commit -m "Remove library submodule"
</pre>

<h2>Common Submodule Problems</h2>
<table>
  <thead><tr><th>Problem</th><th>Cause</th><th>Fix</th></tr></thead>
  <tbody>
    <tr><td>Submodule directory is empty</td><td>Cloned without <code>--recurse-submodules</code></td><td><code>git submodule update --init</code></td></tr>
    <tr><td>Submodule shows "modified" in parent</td><td>Submodule is on a different commit than recorded</td><td><code>git submodule update</code> to restore, or commit the new reference</td></tr>
    <tr><td>Detached HEAD in submodule</td><td>Normal — submodules start detached</td><td><code>git switch main</code> inside the submodule before making changes</td></tr>
    <tr><td>Forgot to push submodule changes</td><td>Parent records a commit the remote doesn't have</td><td>Push inside the submodule first, then push the parent</td></tr>
    <tr><td>Submodule URL changed</td><td>Original repo moved or was renamed</td><td>Edit <code>.gitmodules</code>, run <code>git submodule sync</code></td></tr>
  </tbody>
</table>

<pre>
# Sync updated URLs from .gitmodules into .git/config:
$ git submodule sync
$ git submodule sync --recursive

# Push parent only if all submodule commits are already pushed:
$ git push --recurse-submodules=check
# Fails if any referenced submodule commit isn't on a remote

# Push submodules first automatically:
$ git push --recurse-submodules=on-demand
</pre>

<h2>Alternatives to Submodules</h2>
<p>Submodules work well for vendoring specific commits of external projects. For other use cases, alternatives may be more appropriate:</p>
<table>
  <thead><tr><th>Alternative</th><th>Best for</th></tr></thead>
  <tbody>
    <tr><td>Package managers (npm, pip, composer, cargo)</td><td>Language-ecosystem dependencies — much easier to use</td></tr>
    <tr><td><code>git subtree</code></td><td>Embedding repos where contributors don't need to know it's separate</td></tr>
    <tr><td>Monorepo with workspaces (pnpm, Cargo workspaces)</td><td>Tightly-coupled projects owned by the same team</td></tr>
  </tbody>
</table>

<h2>References</h2>
<ul>
  <li><a href="https://git-scm.com/docs/git-submodule" target="_blank" rel="noopener">git-submodule documentation ↗</a></li>
  <li><a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" target="_blank" rel="noopener">Pro Git Book — Submodules ↗</a></li>
</ul>
