はじめに
プロジェクト開発において、「外部ライブラリを特定のバージョンで固定して管理したい」「複数のプロジェクトで共有するコンポーネントを一元管理したい」「サードパーティのリポジトリを自社プロジェクトに組み込みたい」という要件に直面することがあります。
Gitには、外部リポジトリをプロジェクトに組み込むための2つの主要なアプローチがあります。それがgit submoduleとgit subtreeです。どちらも外部リポジトリを管理する機能ですが、その仕組みと適した使用シーンは大きく異なります。
本記事では、サブモジュールとサブツリーの違いから、git submodule add、git submodule update、git submodule syncの使い方、git subtreeの基本操作、そして共有ライブラリの管理パターンまでを実践的に解説します。
この記事を読み終えると、以下のことができるようになります。
- git submoduleとgit subtreeの違いを理解できる
- 外部リポジトリを組み込む適切な方法を選択できる
git submodule addでサブモジュールを追加できるgit submodule updateでサブモジュールを更新できるgit submodule syncでURL変更に対応できるgit subtree addでサブツリーを追加できる- 共有ライブラリの管理パターンを実装できる
実行環境と前提条件
本記事の内容は、以下の環境で動作確認を行っています。
| 項目 | 要件 |
|---|---|
| Git | 2.40以上 |
| OS | Windows 10/11、macOS 12以上、Ubuntu 22.04以上 |
| ターミナル | コマンドプロンプト、PowerShell、Terminal.app、bash等 |
| エディタ | VS Code推奨 |
前提条件として、以下の知識があることを想定しています。
- コマンドライン操作の基礎知識(
cd、ls/dir、mkdir等) - テキストエディタの基本操作
- Gitの基本コマンド(
git init、git add、git commit、git remote)の理解 - リモートリポジトリの概念(
git clone、git push、git pull)の理解
Gitのバージョンは以下のコマンドで確認できます。
|
|
git submoduleとgit subtreeの違い
外部リポジトリ管理の2つのアプローチ
git submoduleとgit subtreeは、どちらも外部リポジトリをプロジェクトに組み込む機能ですが、その仕組みは根本的に異なります。
| 項目 | git submodule | git subtree |
|---|---|---|
| データの格納方法 | 参照(ポインタ)のみを保存 | ファイル自体をコピーして保存 |
| リポジトリ構成 | 親と子で分離 | 単一リポジトリに統合 |
| メタデータ | .gitmodulesファイルが必要 |
追加のメタデータなし |
| クローン時 | 明示的な初期化が必要 | 追加操作不要 |
| 履歴の保持 | 外部リポジトリの履歴を分離 | 親リポジトリの履歴に統合 |
| 学習コスト | やや高い | 比較的低い |
| 上流への貢献 | 容易 | やや複雑 |
git submodule(サブモジュール)の特徴
git submoduleは、外部リポジトリへの「参照」を親リポジトリに記録する方式です。サブモジュールとして追加された外部リポジトリは、独立したGitリポジトリとして存在し続けます。
親リポジトリ(main-project)
├── .git/
├── .gitmodules <- サブモジュールの設定ファイル
├── src/
│ └── main.js
└── libs/
└── shared-lib/ <- サブモジュール(特定コミットへの参照)
├── .git/ <- 独立したGitリポジトリ
└── index.js
サブモジュールのメリットは以下の通りです。
- 外部リポジトリのバージョンを厳密に固定できる
- 外部リポジトリへの変更を上流に簡単にプッシュできる
- 親リポジトリのサイズが小さく保たれる
- 外部リポジトリの独立性が保たれる
サブモジュールのデメリットは以下の通りです。
- クローン後に追加の初期化コマンドが必要
- ブランチ切り替え時に状態の不整合が発生しやすい
- チームメンバー全員がsubmoduleの操作を理解する必要がある
- 更新手順がやや複雑
git subtree(サブツリー)の特徴
git subtreeは、外部リポジトリのファイルを丸ごとコピーして親リポジトリに「取り込む」方式です。取り込んだファイルは親リポジトリの一部となります。
親リポジトリ(main-project)
├── .git/ <- 全ての履歴を含む
├── src/
│ └── main.js
└── libs/
└── shared-lib/ <- 外部リポジトリのファイルがコピーされている
└── index.js <- 通常のファイルとして管理
サブツリーのメリットは以下の通りです。
- 追加のメタデータファイルが不要
- クローン時に特別な操作が不要
- チームメンバーがsubtreeを知らなくても利用可能
- ブランチ切り替え時の問題が発生しにくい
サブツリーのデメリットは以下の通りです。
- 親リポジトリのサイズが大きくなる
- 上流への変更のプッシュがやや複雑
- 取り込んだコードの出所が分かりにくくなる可能性がある
- 履歴が親リポジトリに混在する
使い分けの指針
以下の基準で選択することを推奨します。
git submoduleを選ぶべきケース
- 外部ライブラリの特定バージョンを厳密に固定したい
- 外部リポジトリに頻繁に変更を加え、上流にプッシュする予定がある
- チームメンバー全員がGitに習熟している
- 親リポジトリのサイズを小さく保ちたい
- 複数のプロジェクトで同じサブモジュールを共有している
git subtreeを選ぶべきケース
- チームメンバーにsubmoduleの学習コストを負わせたくない
- 外部リポジトリを取り込んだ後、ほとんど変更しない
- クローン後の追加操作を最小限にしたい
- CI/CDパイプラインをシンプルに保ちたい
- 外部リポジトリのフォークを自社で独自に発展させる予定がある
git submoduleの基本操作
サブモジュールを追加する(git submodule add)
外部リポジトリをサブモジュールとして追加するには、git submodule addコマンドを使用します。
|
|
実際に共有ライブラリをサブモジュールとして追加してみましょう。
|
|
このコマンドを実行すると、以下の処理が行われます。
|
|
サブモジュールの追加により、2つのファイルがステージングされます。
|
|
.gitmodulesファイルには、サブモジュールの設定情報が記録されます。
|
|
この変更をコミットして、サブモジュールの追加を確定します。
|
|
特定のブランチを追跡する
サブモジュールがデフォルトブランチ以外のブランチを追跡するように設定できます。
|
|
出力例は以下の通りです。
|
|
サブモジュールを含むリポジトリをクローンする
サブモジュールを含むリポジトリをクローンする場合、通常のクローンではサブモジュールのディレクトリは空のままです。
|
|
サブモジュールを取得するには、以下のいずれかの方法を使用します。
方法1: クローン時に–recurse-submodulesオプションを使用
|
|
方法2: クローン後に初期化と更新を実行
|
|
方法3: 初期化と更新を1コマンドで実行
|
|
方法4: ネストしたサブモジュールも含めて再帰的に初期化
|
|
サブモジュールを更新する(git submodule update)
サブモジュールの更新には複数のシナリオがあります。
シナリオ1: 親リポジトリで記録されているコミットにサブモジュールを合わせる
他のチームメンバーがサブモジュールの参照を更新した場合、git pull後にサブモジュールを更新する必要があります。
|
|
--initオプションを付けると、新しく追加されたサブモジュールも同時に初期化できます。
|
|
シナリオ2: サブモジュールを上流の最新版に更新する
サブモジュールのリモートリポジトリにある最新コミットを取得するには、--remoteオプションを使用します。
|
|
このコマンドを実行すると、サブモジュールが最新のコミットをチェックアウトし、親リポジトリに変更が記録されます。
|
|
この変更をコミットして、新しいサブモジュールのバージョンを記録します。
|
|
シナリオ3: 更新時にマージまたはリベースを行う
サブモジュール内で作業している場合、更新時にローカルの変更を保持するためにマージまたはリベースオプションを使用できます。
|
|
サブモジュールのURL変更に対応する(git submodule sync)
外部リポジトリのホスティング先が変更された場合、.gitmodulesのURLを更新した後、ローカル設定を同期する必要があります。
|
|
git submodule syncは、.gitmodulesに記載されたURLを、ローカルの.git/configに反映させるコマンドです。
|
|
サブモジュールを削除する
サブモジュールを完全に削除するには、複数のステップが必要です。
|
|
サブモジュールで便利な設定
サブモジュール操作を効率化するための便利な設定を紹介します。
|
|
git submodule foreachで一括操作
複数のサブモジュールに対して同じ操作を実行するには、foreachコマンドが便利です。
|
|
git subtreeの基本操作
サブツリーを追加する(git subtree add)
外部リポジトリをサブツリーとして追加するには、git subtree addコマンドを使用します。
|
|
実際に共有ライブラリをサブツリーとして追加してみましょう。
|
|
--squashオプションを使用すると、外部リポジトリの履歴が1つのコミットにまとめられます。
|
|
コミット履歴を確認すると、squashマージコミットが作成されていることがわかります。
|
|
リモートを登録してコマンドを短縮する
頻繁にサブツリー操作を行う場合は、リモートを登録しておくとコマンドが短縮できます。
|
|
サブツリーを更新する(git subtree pull)
外部リポジトリの最新の変更を取り込むには、git subtree pullを使用します。
|
|
リモートを登録済みの場合は以下のようになります。
|
|
サブツリーの変更を上流にプッシュする(git subtree push)
サブツリー内のファイルに加えた変更を、元の外部リポジトリにプッシュすることもできます。
まず、プッシュ先となるリモートを設定します(フォークした自分のリポジトリなど)。
|
|
サブツリーの変更をプッシュします。
|
|
このコマンドは、libs/shared-libディレクトリ内の変更を抽出し、指定したリモートのブランチにプッシュします。
サブツリーを分割する(git subtree split)
既存のディレクトリをサブツリーとして切り出し、独立したブランチを作成できます。
|
|
この機能は、モノリポジトリから一部のコードを別リポジトリに切り出す場合に便利です。
|
|
サブツリーを削除する
サブツリーは通常のディレクトリとして管理されているため、削除は単純にディレクトリを削除するだけです。
|
|
共有ライブラリの管理パターン
パターン1: 社内共通ライブラリをsubmoduleで管理
複数のプロジェクトで共有する社内ライブラリを、submoduleで一元管理するパターンです。
company-projects/
├── project-a/
│ ├── src/
│ └── libs/
│ └── common-utils/ <- submodule
├── project-b/
│ ├── src/
│ └── libs/
│ └── common-utils/ <- 同じsubmodule
└── common-utils/ <- 共通ライブラリ本体
セットアップ手順
|
|
共通ライブラリを更新した場合のワークフロー
|
|
パターン2: フォークした外部ライブラリをsubtreeで管理
外部のOSSライブラリをフォークして、自社で独自のカスタマイズを加えながら管理するパターンです。
|
|
パターン3: モノリポジトリでのパッケージ分離
大規模なモノリポジトリで、特定のパッケージを将来的に分離する可能性がある場合の管理パターンです。
monorepo/
├── apps/
│ ├── web-app/
│ └── mobile-app/
└── packages/
├── ui-components/ <- 将来分離予定
└── api-client/ <- 将来分離予定
パッケージを分離する手順
|
|
よくあるトラブルと対処法
submoduleがdetached HEAD状態になる
git submodule updateを実行すると、サブモジュールはdetached HEAD状態になります。これは正常な動作ですが、サブモジュール内で作業する場合は注意が必要です。
|
|
サブモジュール内で作業する場合は、明示的にブランチをチェックアウトしてください。
|
|
submodule updateで「not a git repository」エラー
サブモジュールの初期化が完了していない場合に発生します。
|
|
subtree pullでコンフリクトが発生する
サブツリー内のファイルをローカルで変更した後、上流の変更を取り込むとコンフリクトが発生することがあります。
|
|
ブランチ切り替え時にsubmoduleの状態がおかしくなる
Git 2.13以降では、--recurse-submodulesオプションでサブモジュールも同時に切り替えられます。
|
|
デフォルトでこの動作を有効にするには、以下の設定を行います。
|
|
submoduleとsubtreeの選択チェックリスト
プロジェクトに外部リポジトリを組み込む際の選択基準をチェックリストにまとめました。
git submoduleを選択する場合のチェックリスト
- 外部リポジトリを独立して管理し続けたい
- 特定のコミットやタグに固定する必要がある
- 上流に頻繁にコントリビュートする予定がある
- チームメンバーがGitに習熟している
- CI/CDでの追加設定が許容できる
git subtreeを選択する場合のチェックリスト
- 外部リポジトリを取り込んだ後、あまり更新しない
- チームメンバーへの学習コストを最小限にしたい
- クローンや操作をシンプルに保ちたい
- 外部コードをフォークして独自に発展させる予定がある
- 単一リポジトリで全てを管理したい
まとめ
本記事では、git submoduleとgit subtreeの違いと使い分け、それぞれの基本操作、そして共有ライブラリの管理パターンについて解説しました。
重要なポイントをまとめると以下の通りです。
- git submoduleは外部リポジトリへの参照を管理し、厳密なバージョン管理と上流への貢献に適している
- git subtreeは外部リポジトリのファイルを取り込み、シンプルな運用とチームへの学習コスト軽減に適している
- サブモジュールの基本操作は
add、update、syncの3つを押さえておく - サブツリーの基本操作は
add、pull、push、splitを理解しておく - チームの習熟度、プロジェクトの要件、運用の複雑さを考慮して選択する
どちらのアプローチも一長一短があります。プロジェクトの特性とチームの状況に応じて、適切な方法を選択してください。