複数プロジェクトのコード管理が楽になる!Git Submoduleの使い方🔗
「同じコードを3つのプロジェクトにコピペ...また修正漏れた!」
「プロジェクトごとにバージョンがバラバラで、どれが最新?」
「共通ライブラリの管理、もっと楽にならないかな...」
こんな悩み、ありませんか?
こんにちは、亀ちゃん🐰🐢です。
X (Twitter): @usagi_kamechan
複数のプロジェクトで同じコードを使っていると、修正のたびにあちこち更新しないといけなくて、本当に大変ですよね。
私も開発をしていて、コピペ地獄に苦しんだ経験があります。
でも、Git Submoduleを使ったら、この悩みがスッキリ解決しました!
🌟 この記事でわかること
- Git Submoduleって何?どんな時に使うの?
- 複数プロジェクトで共通コードを管理する方法
- 実際の使い方(追加・クローン・更新)
- チーム開発で気をつけること
- よくあるトラブルと対処法
1. 私が困っていたこと
マイクロサービスを開発していた時のことです。
プロジェクトの構成はこんな感じでした:
【本番環境】 【開発環境】
┌─────────┐ ┌──────────────┐
│サービスA │ │ メインリポジトリ │
│(独立) │ │ │
└─────────┘ │ ├─ サービスB │
│ └─ サービスA ? │
┌─────────┐ │ (どう管理?) │
│サービスB │ └──────────────┘
│(独立) │
└─────────┘
- サービスA:データを管理するWebアプリ(独立したリポジトリ)
- サービスB:サービスAの機能を利用するアプリケーション(別リポジトリ)
本番環境では別々に動くけど、ローカルでの開発・動作確認では両方を一緒に動かす必要がありました。
何が困っていたか
開発時は両方のリポジトリを手動で管理しないといけない...
サービスAが更新されたら、サービスB側でも最新版を取り込みたいのに、いちいち手動でコピー。
チームメンバーの環境構築時、2つのリポジトリを説明するのが大変。
「リポジトリは分離したいけど、開発時は一緒に扱いたい...どうすれば?」
そう思っていた時、Git Submoduleに出会いました。
2. Git Submoduleって何?
Git Submoduleは、Gitリポジトリの中に別のGitリポジトリを含められる機能です。
イメージとしては、「入れ子構造」ですね。
【通常のリポジトリ】 【Submodule使用時】
┌────────────┐ ┌─────────────────┐
│ my-project │ │ my-project │
│ │ │ (親リポジトリ) │
│ ├─ src/ │ │ │
│ ├─ docs/ │ │ ├─ src/ │
│ └─ ... │ │ ├─ docs/ │
└────────────┘ │ └─ libs/ │
│ └─ common/ │← これがサブモジュール
│ (子リポジトリ)
└─────────────────┘
↓
特定のコミットを参照
┌───────────────┐
│ common-library│
│ (別リポジトリ) │
│ │
│ commit: abc123│
└───────────────┘
親リポジトリの中に、子リポジトリ(サブモジュール)を配置できます。
ポイントは「バージョンの固定」
重要なのは、親リポジトリは子リポジトリの特定のコミット(バージョン)を参照するということ。
つまり、「このプロジェクトは、あのライブラリのこのバージョンを使う」と明確に管理できるんです。
勝手に最新版に更新されることはありません。
自分で明示的に更新するまで、同じバージョンが使われ続けます。
これが、予期しない変更を防いでくれる大きなメリットです🌟
3. どんな時に使うの?活用例
Git Submoduleが活躍する場面をいくつか紹介します。
ケース1:複数プロジェクトで共通のライブラリを使う
3つのWebアプリで同じ認証ライブラリを使っている場合。
【従来の方法:コピペ地獄】
┌──────────┐ ┌──────────┐ ┌──────────┐
│ WebアプリA │ │ WebアプリB │ │ WebアプリC │
│ ├─ auth/ │ │ ├─ auth/ │ │ ├─ auth/ │
│ (コピー) │ │ (コピー) │ │ (コピー) │
└──────────┘ └──────────┘ └──────────┘
❌ 修正が3箇所必要!バージョンがバラバラ!
【Submoduleを使う方法】
┌──────────┐ ┌──────────┐ ┌──────────┐
│ WebアプリA │ │ WebアプリB │ │ WebアプリC │
│ └─ auth/ │ │ └─ auth/ │ │ └─ auth/ │
└─────┬────┘ └─────┬────┘ └─────┬────┘
└───────┬──────┴──────────────┘
↓ 全て参照
┌───────────────┐
│ auth-library │
│ (共通リポジトリ)│
└───────────────┘
✅ 修正は1箇所だけ!バージョン管理も明確!
ライブラリをサブモジュールにすれば、各プロジェクトから同じコードを参照できます。
バグ修正したら、ライブラリ側を更新して、各プロジェクトで最新版を取り込むだけ。
コピペ不要です!
ケース2:外部ライブラリのバージョンを固定したい
オープンソースのライブラリを使っているけど、パッケージマネージャーで管理できない場合。
特定のコミットを固定して使いたい時に便利です。
ケース3:チーム開発でルールやドキュメントを共有
複数のプロジェクトで同じコーディング規約やドキュメントを共有したい。
サブモジュールにすれば、一箇所で管理して、全プロジェクトで同期できます。
ケース4:マイクロサービスのローカル開発
私のケースのように、本番では別々に動くサービスを、開発時は一緒に扱いたい場合。
サブモジュールとして含めることで、環境構築がスムーズになります。
4. 実際に使ってみよう!基本の3ステップ
ここからは、実際の使い方を見ていきましょう。
難しそうに見えるかもしれませんが、基本は3つのステップだけです。
【Git Submoduleの基本フロー】
ステップ1 ステップ2 ステップ3
┌─────────┐ ┌──────────┐ ┌──────────┐
│サブモジュール│ │クローン時に │ │サブモジュール│
│を追加 │ ───→ │一緒に取得 │ ───→ │を更新 │
└─────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
git submodule git clone git submodule
add --recursive update --remote
ステップ1: サブモジュールを追加する
既存のリポジトリにサブモジュールを追加するには、このコマンドを使います:
git submodule add <サブモジュールのリポジトリURL> <配置先のパス>
例えば、libs/commonというディレクトリに共通ライブラリを追加する場合:
git submodule add https://github.com/yourteam/common-library.git libs/common
このコマンドを実行すると、何が起こるのか?
【実行前】 【実行後】
my-project/ my-project/
├─ src/ ├─ src/
├─ docs/ ├─ docs/
└─ README.md ├─ libs/
│ └─ common/ ← 新しく追加!
├─ .gitmodules ← 設定ファイル
└─ README.md
.gitmodules の中身:
[submodule "libs/common"]
path = libs/common
url = https://github.com/yourteam/common-library.git
- 指定したパスにサブモジュールがクローンされます
-
.gitmodulesというファイルが作成されます(サブモジュールの設定を保存) - サブモジュールの情報が
.git/configにも追加されます
.gitmodulesファイルは、チームメンバーと共有されます。
このファイルのおかげで、他のメンバーも同じサブモジュールを使えるようになります。
ステップ2: サブモジュール付きのリポジトリをクローンする
サブモジュールを含むリポジトリをクローンする方法は2つあります。
方法1: 最初から全部取得する
git clone --recursive <リポジトリのURL>
--recursiveオプションを付けると、サブモジュールも一緒にクローンされます。
一番簡単な方法です!
方法2: 後からサブモジュールを取得する
すでにクローンしてしまった場合は、このコマンドで取得できます:
git submodule update --init --recursive
--init:サブモジュールを初期化
--recursive:サブモジュールの中にさらにサブモジュールがある場合も全て取得
このコマンド、よく使うので覚えておくと便利です🔥
ステップ3: サブモジュールを更新する
サブモジュールの最新版を取り込みたい場合の方法です。
パターンA: サブモジュールの最新版に更新する
git submodule update --remote
このコマンドで、サブモジュールが最新のコミットにアップデートされます。
更新後は、親リポジトリでもコミットが必要です:
git add .
git commit -m "Update submodule to latest version"
git push
パターンB: サブモジュール内で直接変更する
サブモジュールの中で修正したい場合もあります。
その時はこんな流れです:
# サブモジュールのディレクトリに移動
cd libs/common
# ブランチをチェックアウト(重要!)
git checkout main
# 変更を加える
# ...
# サブモジュール内でコミット&プッシュ
git add .
git commit -m "Fix bug in common library"
git push
# 親リポジトリに戻る
cd ../..
# 親リポジトリでもサブモジュールの参照を更新
git add libs/common
git commit -m "Update submodule reference"
git push
ここで重要なのは、コミットが2箇所で必要ということ。
【サブモジュール変更時のコミットフロー】
Step 1: サブモジュール内で作業
┌─────────────────┐
│ libs/common/ │
│ (サブモジュール) │
│ │
│ ✏️ コード修正 │
│ ✅ git commit │← ① サブモジュール側でコミット
│ ⬆️ git push │
└─────────────────┘
Step 2: 親リポジトリで参照を更新
┌─────────────────┐
│ my-project/ │
│ (親リポジトリ) │
│ │
│ libs/common │← 参照先が変わった
│ (abc123→def456) │
│ ✅ git commit │← ② 親リポジトリでコミット
│ ⬆️ git push │
└─────────────────┘
⚠️ 両方のコミットが必要!片方だけだとチームメンバーが同期できません
- サブモジュール側でコミット
- 親リポジトリでサブモジュールの参照をコミット
最初はちょっと面倒に感じるかもしれませんが、これがバージョン管理の明確さにつながります。
5. チーム開発で気をつけること
チームでGit Submoduleを使う時の注意点をまとめます。
注意点1: コミットが2箇所必要
さっきも触れましたが、サブモジュールを変更したら:
- サブモジュール側でコミット&プッシュ
- 親リポジトリでサブモジュールの参照を更新してコミット&プッシュ
この2ステップが必要です。
片方だけだと、他のメンバーが同じ状態を再現できなくなってしまいます。
注意点2: チームメンバーへの情報共有
Git Submoduleは通常のGitより少し複雑です。
チームメンバー全員が理解している必要があります。
READMEに以下を書いておくと親切です:
- クローン時は
--recursiveオプションを使うこと - 更新時は
git submodule update --init --recursiveを実行すること - サブモジュールを変更する時の手順
注意点3: プルした後はサブモジュールも更新
他のメンバーがサブモジュールを更新したら、自分の環境でも取り込む必要があります:
【チームメンバーのワークフロー】
開発者A 開発者B
┌──────────┐ ┌──────────┐
│サブモジュール│ │ │
│を更新 │ │ │
│⬆️ push │ │ │
└─────┬────┘ │ │
│ │ │
│ ① サブモジュール更新 │ │
│ │ │
↓ │ │
┌──────────┐ │ │
│親リポジトリ│ │ │
│参照を更新 │ │ │
│⬆️ push │ │ │
└─────┬────┘ │ │
│ │ │
│ ② 親リポジトリ更新 │ │
│ ↓ │
└────────────────→ ┌──────────┐ │
│git pull │ │
└──────────┘ │
↓ │
┌──────────┐ │
│git │ │← ⚠️ これを忘れずに!
│submodule │ │
│update │ │
└──────────┘ │
git pull
git submodule update --init --recursive
この2つのコマンドをセットで実行する習慣をつけましょう。
便利なTips: エイリアスを設定する
毎回長いコマンドを打つのは大変なので、エイリアスを設定すると便利です:
# サブモジュール更新用のエイリアス
git config --global alias.sup 'submodule update --init --recursive'
これでgit supと打つだけで更新できます!
チーム運用を楽にする工夫
リサーチの中で見つけた良い方法として、pre-commitフックの活用があります。
サブモジュール側の変更をプッシュし忘れるミスを防ぐために、コミット前にチェックする仕組みを入れるんです。
自動化できることは自動化すると、ヒューマンエラーが減って安心です🌟
6. よくあるトラブルと対処法
初心者がハマりやすいポイントと、その解決方法をまとめます。
【トラブルシューティング早見表】
問題 解決策
┌─────────────────┐ ┌──────────────────┐
│サブモジュールが │ │git submodule │
│空っぽ! │ ───→ │update --init │
│ │ │--recursive │
└─────────────────┘ └──────────────────┘
┌─────────────────┐ ┌──────────────────┐
│Detached HEAD │ │cd libs/common │
│って何? │ ───→ │git checkout main │
└─────────────────┘ └──────────────────┘
┌─────────────────┐ ┌──────────────────┐
│更新が反映されない│ │親リポジトリで │
│ │ ───→ │git add & commit │
│ │ │を忘れずに! │
└─────────────────┘ └──────────────────┘
┌─────────────────┐ ┌──────────────────┐
│マージコンフリクト│ │正しいコミットを │
│が起きた │ ───→ │チームで相談して │
│ │ │checkout │
└─────────────────┘ └──────────────────┘
トラブル1: 「Detached HEAD状態」って何?
サブモジュールのディレクトリでgit statusを見ると、detached HEADと表示されることがあります。
これは、特定のコミットを直接参照している状態。
【Detached HEAD の状態】
通常のブランチ上 Detached HEAD状態
┌─────────────┐ ┌─────────────┐
│ main │← HEAD │ main │
│ ↓ │ │ │
│ ○─○─○─● │ │ ○─○─○─○ │
│ コミット │ │ ↑ │
└─────────────┘ │ HEAD │← ブランチではなく
│ │ コミットを直接参照
└─────────────┘
✅ ブランチで作業できる ⚠️ ここで変更すると危険!
ブランチではなく、コミット単体を見ている状態です。
解決策:作業前にブランチをチェックアウトする
cd libs/common
git checkout main
これで通常のブランチ上で作業できます。
トラブル2: サブモジュールが空っぽになってる!
リポジトリをクローンしたのに、サブモジュールのディレクトリが空。
これは、--recursiveオプションなしでクローンした時によく起こります。
解決策:サブモジュールを初期化・更新する
git submodule update --init --recursive
これで中身が取得されます。
トラブル3: 更新したのに反映されない
サブモジュールを更新したはずなのに、チームメンバーの環境で反映されない。
原因は、親リポジトリでサブモジュールの参照を更新していないことがほとんど。
解決策:親リポジトリでもコミットする
git add <サブモジュールのパス>
git commit -m "Update submodule reference"
git push
サブモジュール側のプッシュだけでなく、親側のコミット&プッシュも忘れずに!
トラブル4: マージコンフリクトが起きた
複数人がサブモジュールの参照を変更すると、マージコンフリクトが発生することがあります。
解決策:適切なコミットを選んで解決
cd <サブモジュールのパス>
git checkout <正しいコミットハッシュ>
cd ..
git add <サブモジュールのパス>
git commit
どちらのバージョンを採用するか、チームで相談して決めましょう。
7. まとめ - やってみた感想
Git Submoduleを使う前は、「難しそう...」と思っていました。
でも、実際に使ってみたら、思ったよりシンプルでした。
最初の一歩は確かにちょっと複雑です。
コミットが2箇所必要とか、detached HEADとか、慣れないうちは戸惑います。
でも、コピペ地獄から解放されたメリットは本当に大きかったです。
使ってみて良かったこと
【Git Submoduleのメリット・デメリット】
✅ メリット ⚠️ デメリット
┌─────────────────┐ ┌─────────────────┐
│複数プロジェクトで │ │初心者には少し │
│コードを一元管理 │ │学習コストがある │
├─────────────────┤ ├─────────────────┤
│バグ修正が │ │コミットが2箇所 │
│一箇所で済む │ │必要で手間 │
├─────────────────┤ ├─────────────────┤
│バージョン管理が │ │チーム全体の │
│明確で安全 │ │理解が必要 │
├─────────────────┤ ├─────────────────┤
│環境構築が │ │忘れやすい │
│スムーズ │ │更新コマンド │
└─────────────────┘ └─────────────────┘
- 複数プロジェクトで同じコードを管理するのが楽になった
- バグ修正が一箇所で済むようになった
- チームメンバーの環境構築がスムーズになった
- バージョン管理が明確になって、予期しない変更が減った
最初の一歩を踏み出すなら
いきなり本番のプロジェクトで使うのではなく、まずは個人プロジェクトで試してみるのがおすすめです。
小さく始めて、慣れてきたらチーム開発に導入する。
そんな流れがいいと思います。
代替手段も検討しよう
Git Submoduleが万能というわけではありません。
プログラミング言語のパッケージマネージャー(npm、pip、など)が使える場合は、そちらの方が簡単です。
また、Git Subtreeという別の方法もあります。
プロジェクトの性質に合わせて、最適な方法を選びましょう。
最後に
複数のプロジェクトでコードを共有する必要があるなら、Git Submoduleは試してみる価値があります。
最初はちょっと戸惑うかもしれませんが、慣れれば強力なツールです。
ぜひ使ってみてください!🔥
📌 コマンド早見表
よく使うコマンドをまとめました。困った時の参考にどうぞ!
┌─────────────────────────────────────────────────┐
│ Git Submodule コマンド早見表 │
└─────────────────────────────────────────────────┘
【追加】
git submodule add <URL> <path>
サブモジュールを新規追加
【クローン】
git clone --recursive <URL>
サブモジュール含めてクローン
【初期化・取得】
git submodule update --init --recursive
サブモジュールを初期化して取得
※既にクローン済みの場合
【更新】
git submodule update --remote
サブモジュールを最新版に更新
【確認】
git submodule status
サブモジュールの状態を確認
【プル後の同期】
git pull && git submodule update --init --recursive
親リポジトリとサブモジュールを両方更新
※セットで実行!
【エイリアス設定(便利!)】
git config --global alias.sup 'submodule update --init --recursive'
→ git sup で更新できるようになる