Chapter 8. サブモジュール

Table of Contents

サブモジュールの落し穴

大きなプロジェクトは自己完結したより小さいプロジェクトを含む場合があります。 例えば、組み込みLinuxディストリビュションのソースツリーは ディストリビュション内にローカルに変更を加えられたソフトウェアのピースが 含まれています;ムービープレーヤーは解凍ライブラリの特定のバージョンで ビルドできるようにする必要があるかもしれません;幾つかの独立したプログラムは 同じビルドスクリプトを共有しているかもしれません。

集中型のリビジョン管理システムでは、1つのリポジトリ内に各モジュールを含む ことによってこれを実現します。開発者は全てのモジュールあるいは必要なモジュール だけをチェックアウトすることができます。API や翻訳の移動や更新といった 幾つかのモジュールにまたがったファイルを1回のコミットで変更することができます。

Gitは部分的なチェックアウトを許可していない為、Git の複製アプローチでは 開発者は興味のないモジュールのコピーまで取得しなくてはなりません。 Git は各ディレクトリの変更をスキャンしなくてはならず、 莫大なコミットをチェックアウトすることで、想像以上に Git は遅くなります。

一方プラスの面として、分散型のリビジョン管理システムは外部ソースを より良い形で統合します。集中型モデルでは、外部プロジェクトの1つの任意の スナップショットをそれ自身のリビジョン管理ツールからエクスポートし、 ローカルのリビジョン管理ツールにベンダーブランチとしてインポートします。 変更履歴は全て隠れてしまいます。分散型のリビジョン管理システムでは 外部の履歴全てを複製することができ、よりいっそう容易に開発を進め、 ローカルの変更を再マージすることもできます。

Git のサブモジュール機能は外部のプロジェクトを サブディレクトリとしてリポジトリに含ませることができます。 サブモジュールはそれ自身で独自性をもってメンテナンスされます; これは、サブモジュールは自身のリポジトリとコミットIDとを持つということであり、 そのサブモジュールを含むプロジェクト("親プロジェクト")を複製した場合、 同じリビジョンのサブモジュールを全て容易に複製できます。 親プロジェクトの部分チェックアウトは可能です:サブモジュールを 複製する、しないをサブモジュールごとにしていすることができます。

git-submodule(1) コマンドは Git 1.5.3 から利用可能になりました。 Git 1.5.2 を使用しているユーザもリポジトリ内のサブモジュールのコミットを 参照し、それらをマニュアルでチェックアウトすることはできます; 初期のバージョンでは全てのサブモジュールを認識することはできないでしょう。

サブモジュールがどのように利用できるかを見るため、 例として4つのリポジトリを作成します:

$ mkdir ~/git
$ cd ~/git
$ for i in a b c d
do
        mkdir $i
        cd $i
        git init
        echo "module $i" > $i.txt
        git add $i.txt
        git commit -m "Initial commit, submodule $i"
        cd ..
done

そして、親プロジェクトを作成し、全てのサブモジュールを追加します:

$ mkdir super
$ cd super
$ git init
$ for i in a b c d
do
        git submodule add ~/git/$i $i
done

注意:親プロジェクトを公開する予定がある場合は、ローカルのURLは使用しないでください!

git submodule がどのようなファイルを作成するか見ましょう:

$ ls -a
.  ..  .git  .gitmodules  a  b  c  d

git submodule add <repo> <path> コマンドは幾つかのことをしています:

親プロジェクトをコミットします:

$ git commit -m "Add submodules a, b, c and d."

そして、親プロジェクトを複製してみます:

$ cd ..
$ git clone super cloned
$ cd cloned

サブモジュールのディレクトリが作成されていますが、それらは空です:

$ ls -a a
.  ..
$ git submodule status
-d266b9873ad50488163457f025db7cdd9683d88b a
-e81d457da15309b4fef4249aba9b50187999670d b
-c1536a972b9affea0f16e0680ba87332dc059146 c
-d96249ff5d57de5de093e6baff9e0aafa5276a74 d

注意:あなたの環境では、コミットオブジェクトの名前が上記のものとは 違っているかもしれません。しかし、それらはあなたのリポジトリの HEAD コミットの オブジェクト名と一致しているはずです。このことは git ls-remote ../a で 確認できます。

サブモジュールの取得は2つの手順で行ないます。まず git submodule init を実行しサブモジュールのリポジトリURLを .git/config に追加します:

$ git submodule init

そして git subumodule update を実行すると、リポジトリの複製と 親プロジェクトにて指定されているコミットをチェックアウトが行われます:

$ git submodule update
$ cd a
$ ls -a
.  ..  .git  a.txt

git submodule updategit submodule add の間の大きな違いは git submodule update が特定のコミットをチェックアウトするのに対し git submodule add はブランチの先端をチェックアウトするという点です。 それは、タグをチェックアウトするのに似ています: つまり head から引き離され、ブランチ上に位置しません。

$ git branch
* (no branch)
  master

head から引き離されているサブモジュール内で変更を加えたい場合は、 ブランチを作成またはチェックアウトし、変更を加え、サブモジュール内の変更を 公開し、新しいコミットを参照するように親プロジェクトを更新します:

$ git checkout master

または

$ git checkout -b fix-up

そして、

$ echo "adding a line again" >> a.txt
$ git commit -a -m "Updated the submodule from within the superproject."
$ git push
$ cd ..
$ git diff
diff --git a/a b/a
index d266b98..261dfac 160000
--- a/a
+++ b/a
@@ -1 +1 @@
-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b
+Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24
$ git add a
$ git commit -m "Updated submodule a."
$ git push

サブモジュールも更新したい場合は、 git pull をした後に git submodule update を実行します。

サブモジュールの落し穴

サブモジュールを参照する親プロジェクトの変更を公開する前には 必ずサブモジュールの変更を公開してください。もしサブモジュールの変更を 公開し忘れた場合、リポジトリを複製できなくなるでしょう:

$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a
$ git commit -m "Updated submodule a again."
$ git push
$ cd ~/git/cloned
$ git pull
$ git submodule update
error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git.
Did you forget to 'git add'?
Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'

親プロジェクトにより記録されたコミットよりも手前にサブモジュールのブランチを 巻き戻すべきではありません。

そうした場合、ブランチを最初にチェックアウトせずにサブモジュール内の変更を コミットした時に git submodule update は安全に動作しません。 何も言わずに変更を上書きしてしまいます。

$ cat a.txt
module a
$ echo line added from private2 >> a.txt
$ git commit -a -m "line added inside private2"
$ cd ..
$ git submodule update
Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'
$ cd a
$ cat a.txt
module a

注意:変更はサブモジュールの reflog にまだ残っています。

ただし、変更をコミットしていない場合は残っていません。