14 上級 約 30 分

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 rmlibs/shared-lib/ ディレクトリ・.gitmodules の記述
rm -rf .git/modules/....git/modules/ 内のキャッシュ

注意: rm -rf だけでディレクトリを消しても .gitmodules と .git/config と .git/modules に設定が残ってしまいます。必ず上記の手順で削除してください。


サブモジュールの落とし穴まとめ

落とし穴原因対処法
クローン後にサブモジュールが空--recurse-submodules なしでクローンgit submodule update --init --recursive
デタッチドHEADでコミットが失われるサブモジュールはデフォルトでデタッチドHEADgit switch <ブランチ名> でブランチに切り替え
サブモジュールの変更がメインに反映されないサブモジュール側のpushを忘れたメインのコミット前にサブモジュール側でpush
削除後も設定が残るrm -rf のみで削除git submodule deinit + git rm + .git/modules 削除

代替アプローチとの比較

サブモジュールが唯一の選択肢ではありません。用途に応じて使い分けましょう。

手段向いているケース
git submoduleGitリポジトリを特定コミットに固定して取り込みたい
git subtreeサブモジュールより操作が簡単で、単一リポジトリとして管理したい
npm/yarn workspacesJavaScript/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 などの代替手段も検討してみましょう。

関連: git submodule コマンドリファレンス