Gitサブモジュール入門
外部リポジトリをサブモジュールとして取り込む方法、更新・削除の手順、よくある落とし穴について演習形式で学びます。
このチュートリアルでは、git submodule を使って外部リポジトリをプロジェクトに取り込む方法を演習形式で学びます。サブモジュールは便利ですが独特のワークフローがあるため、基本から順に理解していきましょう。
前提: Git の基本操作(clone・commit・push)と、ブランチ・リモートリポジトリの概念を理解していることを前提とします。
サブモジュールとは
サブモジュールとは、あるGitリポジトリの中に別のGitリポジトリを「特定のコミット」として埋め込む仕組みです。
メインリポジトリ/
├── .gitmodules ← サブモジュールの定義ファイル
├── src/
│ └── main.c
└── libs/
└── external-lib/ ← サブモジュール(別リポジトリ)
├── .git/ (または .git ファイルへの参照)
└── lib.h
利用場面の例:
- 社内で共有するライブラリを複数プロジェクトで使いたい
- OSS の特定バージョンをプロジェクトに固定して使いたい
- モノレポ以外の手段でコードを共有したい
演習 1:サブモジュールを追加する
外部ライブラリのリポジトリをサブモジュールとして追加してください。
問題: https://github.com/example/shared-lib を libs/shared-lib ディレクトリにサブモジュールとして追加するコマンドを書いてください。
答えを見る
git submodule add https://github.com/example/shared-lib libs/shared-lib実行後、以下の変更が行われます:
# 自動的に作成・変更されるファイル
.gitmodules # サブモジュールの設定(新規作成)
libs/shared-lib/ # サブモジュールのチェックアウト先
# ステータスを確認
git status
# On branch main
# Changes to be committed:
# new file: .gitmodules
# new file: libs/shared-lib.gitmodules の内容:
[submodule "libs/shared-lib"]
path = libs/shared-lib
url = https://github.com/example/shared-lib重要: サブモジュールは「特定のコミット」を指します。追加後はコミットしてサブモジュールの参照を記録しましょう。
git commit -m "libs/shared-lib をサブモジュールとして追加"演習 2:サブモジュールを含むリポジトリをクローンする
同僚からリポジトリを共有してもらいました。サブモジュールも含めてクローンしてください。
問題: サブモジュールを含むリポジトリをクローンする際、サブモジュールも同時に初期化・取得するオプションを付けたコマンドを書いてください。
答えを見る
git clone --recurse-submodules https://github.com/example/main-repo.gitよくある落とし穴:
--recurse-submodulesを付けずにgit cloneした場合、サブモジュールのディレクトリは空のままになります。
# クローン後にサブモジュールが空だった場合の対処
git submodule init # .gitmodules の設定を .git/config に登録
git submodule update # サブモジュールのコードを取得2コマンドをまとめて実行:
git submodule update --init --recursive--recursive を付けると、入れ子のサブモジュール(サブモジュールの中のサブモジュール)も再帰的に初期化します。
演習 3:サブモジュールを最新コミットに更新する
依存ライブラリが更新されました。サブモジュールを最新のコミットに追従させてください。
問題: サブモジュールをリモートの最新コミットに更新するコマンドを書いてください。
答えを見る
git submodule update --remote--remote を付けることで、サブモジュールのリモートリポジトリから最新のコミットを取得します。
特定のブランチを追従させたい場合は .gitmodules に設定を追記します:
# .gitmodules にブランチを指定
git config -f .gitmodules submodule.libs/shared-lib.branch mainその後:
git submodule update --remote --merge確認:
git submodule status
# +abc1234 libs/shared-lib (v2.0.0-3-gabc1234) ← + は変更があることを示すサブモジュールの更新をメインリポジトリにコミットするのを忘れずに:
git add libs/shared-lib
git commit -m "libs/shared-lib を最新コミットに更新"演習 4:サブモジュール内で変更をコミットしてプッシュする
サブモジュールのコードに直接修正が必要になりました。サブモジュール内で変更してプッシュしてください。
問題: 以下の手順が「なぜ2段階のコミットが必要か」を説明し、正しい手順を書いてください。
答えを見る
2段階が必要な理由:
サブモジュールはそれ自体が独立したGitリポジトリです。変更はサブモジュール側とメインリポジトリ側の両方にコミット・プッシュが必要です。
メインリポジトリ ← ② ここにも「どのコミットを指すか」をコミット
└── libs/shared-lib/ ← ① まずここでコミット・プッシュ正しい手順:
# ① サブモジュールのディレクトリに移動
cd libs/shared-lib
# ② デタッチドHEADを解消するためブランチに切り替え
git switch main
# ③ 変更してコミット
echo "修正内容" >> README.md
git add README.md
git commit -m "README のタイポを修正"
# ④ サブモジュールのリモートにプッシュ
git push origin main
# ⑤ メインリポジトリに戻る
cd ../..
# ⑥ メインリポジトリでサブモジュールの新しい参照をコミット
git add libs/shared-lib
git status
# Changes to be committed:
# modified: libs/shared-lib ← コミットIDが変わったことを記録
git commit -m "libs/shared-lib の参照を更新"
git push落とし穴(デタッチドHEAD): サブモジュールをクローン・更新した直後は「デタッチドHEAD」状態になっています。この状態でコミットしても、ブランチに記録されないため変更が失われるリスクがあります。必ず
git switch <ブランチ名>でブランチに切り替えてからコミットしましょう。
# デタッチドHEADの確認
cd libs/shared-lib
git branch
# * (HEAD detached at abc1234) ← これがデタッチドHEAD演習 5:サブモジュールを安全に削除する
不要になったサブモジュールを削除してください。単純にディレクトリを rm -rf するだけでは不完全です。
問題: サブモジュール libs/shared-lib を完全に削除する手順を書いてください。
答えを見る
サブモジュールの削除は4箇所のクリーンアップが必要です:
# ① git submodule deinit でサブモジュールの登録を解除
git submodule deinit libs/shared-lib
# ② git rm でサブモジュールのディレクトリと .gitmodules の記述を削除
git rm libs/shared-lib
# ③ .git/modules 内のキャッシュを削除
rm -rf .git/modules/libs/shared-lib
# ④ コミット
git commit -m "libs/shared-lib サブモジュールを削除"各ステップで何が削除されるか:
| コマンド | 削除される内容 |
|---|---|
git submodule deinit | .git/config のサブモジュール設定 |
git rm | libs/shared-lib/ ディレクトリ・.gitmodules の記述 |
rm -rf .git/modules/... | .git/modules/ 内のキャッシュ |
注意:
rm -rfだけでディレクトリを消しても.gitmodulesと.git/configと.git/modulesに設定が残ってしまいます。必ず上記の手順で削除してください。
サブモジュールの落とし穴まとめ
| 落とし穴 | 原因 | 対処法 |
|---|---|---|
| クローン後にサブモジュールが空 | --recurse-submodules なしでクローン | git submodule update --init --recursive |
| デタッチドHEADでコミットが失われる | サブモジュールはデフォルトでデタッチドHEAD | git switch <ブランチ名> でブランチに切り替え |
| サブモジュールの変更がメインに反映されない | サブモジュール側のpushを忘れた | メインのコミット前にサブモジュール側でpush |
| 削除後も設定が残る | rm -rf のみで削除 | git submodule deinit + git rm + .git/modules 削除 |
代替アプローチとの比較
サブモジュールが唯一の選択肢ではありません。用途に応じて使い分けましょう。
| 手段 | 向いているケース |
|---|---|
git submodule | Gitリポジトリを特定コミットに固定して取り込みたい |
git subtree | サブモジュールより操作が簡単で、単一リポジトリとして管理したい |
| npm/yarn workspaces | JavaScript/TypeScriptプロジェクトでのモノレポ管理 |
| package.json の dependencies | バージョン管理されたパッケージとして配布されているライブラリ |
まとめ
Gitサブモジュールの基本フロー:
追加: git submodule add <url> <path>
クローン: git clone --recurse-submodules <url>
更新: git submodule update --remote
削除: git submodule deinit <path>
git rm <path>
rm -rf .git/modules/<path>
サブモジュールは強力ですが、チームメンバー全員がデタッチドHEADの仕組みを理解していないと混乱しやすいです。用途によっては git subtree や npm workspaces などの代替手段も検討してみましょう。