Git Submoduleで複数リポジトリを効率的に管理する実装ガイド

本記事では、Git Submoduleの基本から実践的な運用方法まで、すぐにチームで活用できる手法をご紹介します。複数のリポジトリを統合管理する際のコマンド実行例、よくあるハマりポイント、そして避けるべき場面まで、実務で必要な知識を網羅しています。

Git Submoduleとは何か

Git Submoduleは、あるGitリポジトリの中に別のGitリポジトリを組み込む機能です。マイクロサービスアーキテクチャ、共有ライブラリの管理、複数プロジェクト間でのコード共有といったシーンで活躍します。

例えば、メインプロジェクトが複数の独立した開発チームによるサブモジュールに依存している場合、各チームが独立してリポジトリを管理しながら、メインプロジェクトではそれらを一つの状態として固定できます。これが次世代の依存管理を実現します。

Submoduleの追加と初期化

新しいSubmoduleを追加する

メインリポジトリに外部リポジトリをSubmoduleとして追加するには、以下のコマンドを実行します。

# 外部リポジトリをSubmoduleとして追加
git submodule add https://github.com/example/shared-lib.git libs/shared-lib

# 特定のブランチを指定する場合
git submodule add -b main https://github.com/example/shared-lib.git libs/shared-lib

このコマンドを実行すると、.gitmodulesファイルが生成され、Submoduleの設定が記録されます。同時に指定したディレクトリに外部リポジトリがクローンされます。

# .gitmodulesファイルの内容例
[submodule "libs/shared-lib"]
	path = libs/shared-lib
	url = https://github.com/example/shared-lib.git
	branch = main

既存リポジトリでSubmoduleを初期化する

Submoduleを含むリポジトリをクローンしたばかりの場合、Submoduleディレクトリは空の状態です。以下のコマンドで初期化と同期を行います。

# Submoduleの初期化と同時にクローン(推奨)
git clone --recurse-submodules https://github.com/example/main-project.git

# または既存リポジトリで後から実行する場合
git submodule update --init --recursive

--recurse-submodulesフラグを使用することで、クローン時にSubmoduleも自動的に取得できます。運用チームの手作業を削減できるため、大規模プロジェクトでは必須の習慣です。

Submoduleの日常的な操作と更新

Submoduleを最新のコミットに更新する

Submoduleが指す外部リポジトリの最新コミットを取得するには以下を実行します。

# 特定のSubmoduleのみ更新
git submodule update --remote libs/shared-lib

# すべてのSubmoduleを更新
git submodule update --remote --recursive

# 特定のブランチの最新を取得(.gitmodulesで指定したブランチ)
git submodule update --remote --merge

重要なのは、--remoteフラグです。これがないと、.gitmodulesに記録された特定のコミットを参照したままになります。最新の変更を反映させるには必ず--remoteを指定してください。

Submodule内での変更をコミットする

Submoduleディレクトリ内で発生した変更は、まずそのSubmodule自体のリポジトリにコミットする必要があります。その後、メインリポジトリの参照ポイントを更新します。

# Submoduleディレクトリに移動
cd libs/shared-lib

# Submodule側で通常のGit操作
git add src/utils.js
git commit -m "Fix utility function"
git push origin main

# メインリポジトリに戻る
cd ../..

# メインリポジトリで参照を更新
git add libs/shared-lib
git commit -m "Update shared-lib to latest version"
git push origin main

Submoduleの変更はメインリポジトリのgit statusで「modified」と表示されます。これを明示的にコミットすることで、どのタイミングでどのバージョンを使用しているかが記録されます。

Submoduleの削除と置き換え

Submoduleを完全に削除する

Submoduleは単なるディレクトリ削除では完全に除去できません。以下の手順に従ってください。

# 1. .gitmodulesファイルからエントリを削除
# テキストエディタで該当セクションを削除

# 2. .git/configからもエントリを削除
git config --file .git/config --remove-section submodule.libs/shared-lib

# 3. ステージングエリアからも削除
git rm --cached libs/shared-lib

# 4. 物理的なディレクトリを削除
rm -rf libs/shared-lib

# 5. 変更をコミット
git add .gitmodules .git/config
git commit -m "Remove shared-lib submodule"

この手順をスキップすると、リポジトリにゴミが残り、後続の操作で予期しないエラーが発生します。特にチームで共有するリポジトリの場合は、全メンバーが同じ手順を実行する必要があります。

ハマりやすいポイントと解決策

Submoduleの変更が自動でコミットされない問題

Submoduleの参照が変わっても、メインリポジトリに明示的にコミットしないと、他のメンバーがgit pullしても古いバージョンを使用し続けます。

# 問題のある操作フロー
cd libs/shared-lib
git pull origin main
cd ../..
# ここでコミットしないと、変更が伝播しない

# 正しいフロー
cd libs/shared-lib
git pull origin main
cd ../..
git add libs/shared-lib  # 重要:この行を忘れずに
git commit -m "Update to latest shared-lib"
git push

チーム内では、「Submoduleの更新を行ったら必ずメインリポジトリでコミットする」というルールを定めるべきです。

detached HEADの状態に陥る

Submoduleの参照は特定のコミットハッシュを指しているため、Submoduleに入ると自動的に detached HEAD 状態になります。ここでgit checkoutなしに変更を加えるとコミットが失われるリスクがあります。

# 現在の状態を確認
cd libs/shared-lib
git status

# detached HEAD の場合、ブランチに戻る
git checkout main

# 以後は通常通り操作可能
git pull origin main
git add src/index.js
git commit -m "Make changes"
git push origin main

パーミッション不足でクローンに失敗する

Submoduleが認証付きのプライベートリポジトリの場合、SSH鍵の設定が必要です。

# SSH鍵の確認
ssh -T git@github.com

# Submoduleの URL スキームを SSH に変更
git config --file .gitmodules submodule.libs/shared-lib.url git@github.com:example/shared-lib.git
git submodule sync --recursive

Submoduleを使うべき場面と避けるべき場面

✅ Submoduleが活躍する場面

  • 複数チームが並行開発する場合:各チームが独立してコード管理でき、インテグレーション時のバージョン管理が明確になります。
  • 共有ライブラリの管理:複数プロジェクトで使用する共通コンポーネントをSubmoduleとして参照することで、DRY原則を守れます。
  • プロジェクトテンプレートの組み込み:テンプレートリポジトリをSubmoduleとして含め、更新を一元管理します。

❌ Submoduleを避けるべき場面

  • 頻繁に相互参照するコードベース:Submoduleは単方向の依存を想定しています。循環参照が必要な場合は単一リポジトリにまとめるべきです。
  • 小規模チーム・単一リポジトリで十分な場合:npm packageやPoetryなどの言語固有のパッケージマネージャーの方が管理は簡単です。
  • リアルタイムの緊密な連携が必要:Submoduleは「固定版」の利用を想定しており、開発中の頻繁な変更には向きません。

Submoduleと代替手段の比較

Git Submodule以外の依存管理手段として、Git Monorepo(複数プロジェクトを1つのリポジトリで管理)とパッケージマネージャー(npm、pip など)があります。

Monorepoは初心者向けで管理が簡単ですが、リポジトリサイズが増大するデメリットがあります。一方、パッケージマネージャーはバージョン管理が得意ですが、git履歴との同期が必要です。Submoduleは「git管理下で明示的にバージョンを固定したい」という中間的なニーズに最適です。

よくある質問

A: Submoduleディレクトリ内の変更をコミットしただけでは不十分です。メインリポジトリ側でもgit add libs/shared-libを実行し、参照の更新をコミットする必要があります。

A: 以下の手順で URL を更新します。

A: Submoduleの参照も含めてマージされます。マージ後は必ずgit submodule update --recursiveを実行して、ローカルの Submodule を最新の参照に同期させてください。

まとめ

  • Submodule追加git submodule addで外部リポジトリを組み込み、.gitmodulesに記録される
  • 初期化・更新:クローン時は--recurse-submodules、更新時は--remoteフラグを忘れずに
  • コミット手順:Submodule内の変更→そのリポジトリにpush→メインリポジトリで参照をコミット
  • 削除手順:3つのファイル(.gitmodules、.git/config、物理ディレクトリ)の削除が必要
  • 適用場面の見極め:複数チーム・複数リポジトリの明示的なバージョン管理に最適。単一リポジトリで事足りる場合は避ける

Git Submoduleは正しく理解することで、エンタープライズ規模のプロジェクト管理を大幅に効率化できます。チーム内で運用ルールを決め、今回紹介したコ

K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →