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
| Approach | Best when | Downsides |
|---|---|---|
| Git submodule | Strict version pinning, private source-level sharing, no registry | More complex clone/CI, detached HEAD confusion |
| Git subtree | Want to merge history into one repo, rare upstream syncs | Heavier upstream sync workflow |
| npm/pip/cargo package | Published library, semantic versioning, public or private registry | Needs publish step for every change |
| Monorepo | Teams change multiple packages together, unified CI pipeline | Repo 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
---
## 자주 묻는 질문 (FAQ)
### Q. 이 내용을 실무에서 언제 쓰나요?
**A.** Git submodules: how to pull sub-repos, init/update/delete, CI caching, common pitfalls, and when to prefer a monorepo in… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
### Q. 선행으로 읽으면 좋은 글은?
**A.** 각 글 하단의 **이전 글** 또는 **관련 글** 링크를 따라가면 순서대로 배울 수 있습니다. [C++ 시리즈 목차](/blog/cpp-series-index/)에서 전체 흐름을 확인할 수 있습니다.
### Q. 더 깊이 공부하려면?
**A.** [cppreference](https://en.cppreference.com/)와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
---
## 같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [Git push pull 차이 | 원격 저장소·GitHub 협업·Pull Request 완벽 가이드](/blog/git-series-03-remote-collaboration/)
- [Git 머지 충돌 해결 실전 사례 | 대규모 리팩토링 브랜치 병합기](/blog/git-case-study-01-merge-conflict-resolution/)
---
## 이 글에서 다루는 키워드 (관련 검색어)
**Git**, **Submodule**, **Multi-repo**, **Dependency management**, **CI**, **Collaboration** 등으로 검색하시면 이 글이 도움이 됩니다.