git submoduleを簡潔に理解して使う

git submodule機能を使うと、gitリポジトリの中に別のgitリポジトリを置けます。

ただし、このように2階建てのリポジトリを持つと、ベースの親リポジトリと内蔵している子リポジトリがそれぞれ別のタイミングで更新されるため、両方の更新を管理する必要があります。

子リポジトリのセットアップ&更新

親リポジトリは通常のgit操作の手順と違いはありません。既存の一般的なリポジトリ内で、リモートリポジトリを組み込むコマンドは以下の通りです。

git submodule add remoteserver:child-repository.git local_subdir

このコマンドで、local_subdirにリモートリポジトリのファイル群がcloneされます。

また、親リポジトリ(外側)にはこの作業時点の子リポジトリ(内側)のコミットハッシュが記録されます。

この状態で、リポジトリが二重に存在していて、子リポジトリのlocal_subdir内に移動すると、子のリモートoriginに対してgit pull, push, …を実行する動作になります。

このように子リポジトリを変更してリモートと同期した場合、親リポジトリに記録されている参照ハッシュも更新されるため、親ディレクトリで他の変更を行っていない場合でも、親のリポジトリのgit commit, git pushを行う必要があります。

このように、概念的に分かりやすいのは親と子のディレクトリを行き来して、それぞれのリポジトリに対してgit commit関連の操作を行うことです。

submodule入りリポジトリをclone取得した場合の操作

submoduleを登録した親リポジトリは当然別の環境からもgit cloneできます。 clone直後は子リポジトリの実ファイルは存在していないため、親子のファイル群一式を取得するのは以下のような手順が必要になります。

git clone remoteserver:parent-repository.git localdir
cd localdir
git submodule init
git submodule update

なお、git clone --recursiveオプションを使うと、1コマンドで同じ操作が可能なため、一般的な用途ではこれで良いと思います。

git clone --recursive remoteserver:parent-repository.git localdir

そして、この点がsubmoduleの挙動で一番重要な点なのですが、この操作直後には子リポジトリが detached HEAD という状態になっていて、一般的なリポジトリのブランチ操作と異なる状態になっています。

これはgit submoduleがブランチではなく個別コミットのハッシュを参照管理する設計になっているためです。

前述のように親子のディレクトリを行き来して管理するには、このタイミングでmasterブランチ(または任意のブランチ)に移っておくことが必要です。

cd local_subdir
git checkout master

子リポジトリが何らかのブランチを指している限りは一般的な感覚でgit commit関連操作を行えます。

detached HEADになると予想外の挙動になるため、よく分からなくなった場合にはlocal_subdir内でgit branchコマンドを実行して現状のブランチ状態を確認することが有効です。

その他のトラブルのケース

子リポジトリのリモートURLは冒頭のgit submodule addコマンドの時点で親リポジトリに登録されています。

自前でリポジトリを構築しているような場合、gitコマンドのホスト名指定には注意が必要です。

gitコマンドのホストには ~/.ssh/config のホスト設定を指定できますが、git submodule add時に自分でつけた愛称のようなホスト名を指定してしまうと、ローカル環境の.ssh/configに依存してしまいます。

後日他のマシンでgit clone --recursiveできないということにならないよう、ホスト名はFQDNを指定しておいた方が無難です。

また、子リポジトリのgit push忘れにも注意が必要です。子リポジトリに変更があった場合、親リポジトリだけgit pushされた状態が起こりえます。

git clone --recursiveはリモートリポジトリから取得するため、子リポジトリをpushし忘れていると、他の環境からは親リポジトリが指している子リポジトリを取得できない状態になってしまいます。

サブモジュールのリポジトリ設定変更

後日、サブモジュールのリポジトリ設定を変更する場合には、プロジェクトルートの.gitmodulesファイルを編集する必要があります。

サブモジュールのディレクトリ内でもgit remote set-urlコマンドが動作しますが、このコマンド実行では親リポジトリに設定が反映せず、別の環境でプロジェクトをクローンした際などに変更が効かないので注意が必要です。

また、プロジェクトにより主のブランチが master と main が異なることが一般的ですが、サブモジュールのbranchも.gitmodulesに指定できます。

中馬崇尋
Chuma Takahiro