본문으로 건너뛰기 Git Submodules in Practice | Add· Update

Git Submodules in Practice | Add· Update

Git Submodules in Practice | Add· Update

이 글의 핵심

Git submodules let you embed another Git repository inside yours, pinned to a specific commit. This guide covers add/update/delete workflows, CI setup, common pitfalls (empty folders, detached HEAD), and when monorepos are a better choice.

What Is a Git Submodule?

A Git submodule is a Git repository embedded inside another Git repository. The outer repo (called the superproject) records the exact commit SHA of the inner repo. This gives you:

  • Source code sharing without publishing to a package registry
  • Strict version pinning — you control exactly which commit of the dependency you use
  • Separate commit history and access control for each repo

The cost: operations that feel automatic in a single repo (clone, pull, CI) require extra steps.

superproject/
├── .gitmodules           ← maps submodule paths to their URLs
├── src/
├── vendor/
│   └── shared-lib/       ← this is a gitlink, not a real directory in superproject
└── ...

Adding a Submodule

# Add a submodule at vendor/shared-lib
git submodule add https://github.com/org/shared-lib.git vendor/shared-lib

# This creates/updates .gitmodules and stages the gitlink
git commit -m "chore: add shared-lib submodule"

The resulting .gitmodules file:

[submodule "vendor/shared-lib"]
    path = vendor/shared-lib
    url = https://github.com/org/shared-lib.git
    branch = main   # optional: track a branch

The superproject now contains a gitlink — an entry in the tree that points to a specific SHA in the child repo. It is not a regular file or directory from Git’s perspective.


Cloning a Repo with Submodules

Regular git clone does not fetch submodules:

# All-in-one: clone and initialize all submodules
git clone --recurse-submodules https://github.com/org/main-app.git

# If you already cloned without --recurse-submodules:
git submodule update --init --recursive

After git pull that updates a submodule pointer:

git pull
git submodule update --init --recursive   # update to the new SHA

Put this in your README. Every developer who doesn’t know about submodules will hit the “empty folder” problem exactly once, then need this command.


Updating a Submodule to Latest

The superproject does not automatically follow the latest commit in the child repo. You bump it deliberately:

# Enter the submodule
cd vendor/shared-lib

# Get the latest commits
git fetch origin
git checkout main
git pull

# Go back and record the new SHA
cd ../..
git add vendor/shared-lib
git status
# modified: vendor/shared-lib (new commits)
git commit -m "chore: bump shared-lib to latest main"
git push

Your teammates then:

git pull
git submodule update --init --recursive

Update All Submodules at Once

# Fetch and merge latest for all submodules tracking a branch
git submodule update --remote --merge

# Then review, add, and commit
git add .
git commit -m "chore: bump all submodules"

Removing a Submodule

Git has no single git submodule remove command. The steps are:

# 1. Remove from .gitmodules
git config -f .gitmodules --remove-section submodule.vendor/shared-lib

# 2. Stage the .gitmodules change
git add .gitmodules

# 3. Remove the submodule from the index and working tree
git rm -f vendor/shared-lib

# 4. Clean up the .git/modules cache
rm -rf .git/modules/vendor/shared-lib

# 5. Commit
git commit -m "chore: remove shared-lib submodule"

CI Configuration

Every CI run needs to initialize submodules. Two approaches:

GitHub Actions

# .github/workflows/ci.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: 'recursive'    # initializes all submodules
          token: ${{ secrets.GH_PAT }}  # required for private submodules

Shell Script

# If you use a manual checkout step
git checkout $COMMIT_SHA
git submodule update --init --recursive --depth 1  # --depth 1 for faster CI

# Or in a Makefile:
setup:
    git submodule update --init --recursive

Caching Submodules in CI

- name: Cache submodule data
  uses: actions/cache@v4
  with:
    path: vendor/
    key: submodules-${{ hashFiles('.gitmodules') }}-${{ github.sha }}
    restore-keys: |
      submodules-${{ hashFiles('.gitmodules') }}-

Include the submodule directory in the cache key based on .gitmodules — this invalidates the cache when the submodule URL or path changes.


Common Pitfalls

Detached HEAD in the Submodule

When you enter a submodule after git submodule update, you’re in detached HEAD — you’re not on any branch:

cd vendor/shared-lib
git status
# HEAD detached at a1b2c3d

If you make changes here without creating a branch first, those commits may be lost when you update again:

# Before making changes in a submodule:
git checkout -b my-feature   # create a branch
# ... make changes, commit ...
git push origin my-feature   # push to the child repo's remote

Permission Errors in CI

Private submodules require the CI runner to have access to the child repo:

# Option 1: SSH deploy key on the child repo (read-only)
# Add the public key to the child repo's Deploy Keys

# Option 2: GitHub token with repo scope
# Use secrets.GH_PAT in the checkout action

# Option 3: Machine user with access to all repos
# Create a bot account, add it as collaborator on child repos

The .git/modules Cache

After git rm -f vendor/shared-lib, the .git/modules/vendor/shared-lib directory still exists. If you re-add the same submodule path later (even at a different URL), Git uses the cached data and may fail. Always clean it:

rm -rf .git/modules/vendor/shared-lib

Comparing Multi-Repo Strategies

ApproachBest whenDownsides
Git submoduleStrict version pinning, private source-level sharing, no registryMore complex clone/CI, detached HEAD confusion
Git subtreeWant to merge history into one repo, rare upstream syncsHeavier upstream sync workflow
npm/pip/cargo packagePublished library, semantic versioning, public or private registryNeeds publish step for every change
MonorepoTeams change multiple packages together, unified CI pipelineRepo size grows, permission design more complex

When submodules make sense:

  • Shared protobuf/schema files used by services in different languages
  • Internal C++ library shared between firmware and desktop apps
  • Test data or fixtures that are maintained as their own project

When to prefer packages: if you’re sharing JavaScript/TypeScript utilities between Node.js services, publish to a private npm registry (GitHub Packages, Verdaccio) instead — standard dependency tools work better than submodules for this use case.


Workflow Checklist

Add to your README.md:

## Setup

Clone with submodules:
```bash
git clone --recurse-submodules https://github.com/org/main-app.git

If you already cloned:

git submodule update --init --recursive

After every git pull:

git submodule update --init --recursive

---

## Key Takeaways

- **Superproject records a SHA**, not "latest" — submodule updates are deliberate commits in the parent
- **Always clone with `--recurse-submodules`** or run `git submodule update --init --recursive` after cloning
- **CI**: use `submodules: recursive` in actions/checkout or add the update step explicitly; use `--depth 1` for faster shallow fetches
- **Detached HEAD**: always create a branch before committing inside a submodule
- **Remove**: four manual steps — remove from `.gitmodules`, `git rm`, remove `.git/modules` cache, commit
- **Private repos**: need a deploy key or machine user token in CI
- **Consider packages** when the shared code has semantic versioning and a publish workflow fits your process


---

## Frequently Asked Questions (FAQ)

### Q. When would I use this in practice?

**A.** Git submodules: how to pull sub-repos, init/update/delete, CI caching, common pitfalls, and when to prefer a monorepo.

### Q. What should I read before this?

**A.** Follow the **previous article** or **related articles** links at the bottom of each post to learn in sequence.

### Q. Where can I study this more deeply?

**A.** Check [cppreference](https://en.cppreference.com/) and the relevant library’s official documentation. The reference links at the end of the article are also worth using.

---

## Related Articles (Internal Links)

Other articles related to this topic.

- [Git push pull 차이 | 원격 저장소·GitHub 협업·Pull Request 완벽 가이드](/blog/git-series-03-remote-collaboration/)
- [Git 머지 충돌 해결 실전 사례 | 대규모 리팩토링 브랜치 병합기](/blog/git-case-study-01-merge-conflict-resolution/)

---

## Keywords Covered in This Article (Related Search Terms)

This article covers **Git**, **Submodule**, **Multi-repo**, **Dependency management**, **CI**, **Collaboration**.