git submodule
何が起きるかを確認しながら操作した。
まず、物理的に(フィイルシステム上で)入れ子リポジトリを作る。何もしなくても、gitは入れ子リポジトリを認識する。親をリモートへプッシュすると、子リポジトリの実体は送らないで、git-module-test2 → 27b35aabc6df [27b35aabc6df] のような参照情報だけをリモートに送る。
リモートをクローンすると、入れ子リポジトリは空のディレクトリとして出現する。ホントにカラッポ! .git/ 内にも入れ子リポジトリに関する何のメタデータもないように思える。実体として子リポジトリを持つ親は、それを認識して管理下に置くが、転送対象にしないようだ。
一方、git add submodule repo-url subdir-name としてサブモジュールを作ったときは、.gitmodulesというメタデータファイルが出来て、.git/modules/名前/ にリポジトリ実体が保存される。サブモジュールの作業サブディレクトリ側の .git はファイルで、
gitdir: ../.git/modules/git-module-test3
のように、リポジトリ実体への相対パス参照が書いてある。アプリケーション固有の相対シンボリックリンクと言っていいだろう。
正式なサブモジュールは、親側に組み込まれた存在となり、リポジトリ実体としても単一の構造体となる。階層構造が、ストレージ実体レベルでもメタデータでもキチンと存在する。
親モジュールは、サブモジュールの変更を認識するが、ステージングやコミットやプッシュには一切手を出せないようだ。メカニズムは一枚岩でも、管理体制はあくまで別で、親子と言えども分離している。
サブモジュール付きのリポジトリをリモートにプッシュすると、サブモジュール情報付きで転送される。が、物理入れ子のときとどう違うか? さらに調べる。
サブモジュール付きのリポジトリのリモートをローカルにクローンすると、サブモジュールは空のディレクトリ。メタデータ .gitmodulesによりファッチ元は分かるんので再帰的なフェッチは可能。しかし取ってこない。
[PS ~\tmp]
tmp > git clone --recursive https://m_hiyama@bitbucket.org/m_hiyama/git-module-test1.git
.\git-module-test1-clone-rec
Cloning into '.\git-module-test1-clone-rec'...
remote: Counting objects: 17, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 17 (delta 5), reused 0 (delta 0)
Unpacking objects: 100% (17/17), done.
Checking connectivity... done.
No submodule mapping found in .gitmodules for path 'git-module-test2'[PS ~\tmp]
No submodule mapping found in .gitmodules for path 'git-module-test2' のせいで取ってこないのか?
git-module-test2/ を削除してもなお、シツコク「git-module-test2の情報がない」と言う。サブモジュール登録してない入れ子リポジトリgit-module-test2の情報がどっかに残っているようだ。しょうがないから手動で.gitmodulesを書き換えて、git-module-test2の情報を人為的に提供。
[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone > git submodule init
No submodule mapping found in .gitmodules for path 'git-module-test2'[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone > git submodule status
-377a896e0a897d962bd43be3e6db5bff41d00a6e git-module-test2
-52882c9f3263cf7f136adb29a30b024e2a26691f git-module-test3[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone >
ここで、git submodule init。
[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone > git submodule init
Submodule 'git-module-test2' (https://m_hiyama@bitbucket.org/m_hiyama/git-module-test2.git) registered for path 'git-module-test2'
Submodule 'git-module-test3' (https://m_hiyama@bitbucket.org/m_hiyama/git-module-test3.git) registered for path 'git-module-test3'[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone >
これはどうやら、.git/configに次のエントリーを追加するらしい。
[submodule "git-module-test2"] url = https://m_hiyama@bitbucket.org/m_hiyama/git-module-test2.git [submodule "git-module-test3"] url = https://m_hiyama@bitbucket.org/m_hiyama/git-module-test3.git
この段階では相変わらずサブディレクトリはカラッポ。git submodule updateする。
[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone > git submodule update
Cloning into 'git-module-test2'...
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
Checking connectivity... done.
fatal: reference is not a tree: 377a896e0a897d962bd43be3e6db5bff41d00a6e
Cloning into 'git-module-test3'...
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
Checking connectivity... done.
fatal: reference is not a tree: 52882c9f3263cf7f136adb29a30b024e2a26691f
Unable to checkout '377a896e0a897d962bd43be3e6db5bff41d00a6e' in submodule path 'git-module-test2'
Unable to checkout '52882c9f3263cf7f136adb29a30b024e2a26691f' in submodule path 'git-module-test3'[PS ~\tmp\git-module-test1-clone]
git-module-test1-clone >
リモートからの転送が起き、サブモジュールのリポジトリ実体が親の.gitにマージ(.git/modules/内にコピー)されて、サブディレクトリにはシンボリックリンクである.gitフィルが作られただけ。サブディレクトリのワーキングコピーは作られない。
どういうつもりかワカランが、空のサブディレクトリgit statusは、ファイルが削除された状態(削除分はステージングされている)。なんでワーキングコピーを展開しないのか? 中途半端な状態にする理由がワカラン。
サブモジュールのサブディレクトリに降りて、git checkoutしないとワーキングコピーは現れない。サブモジュールのstatusがクリーンになっても、親から見たらクリーンではなくて、アンステージチェンジがある状態。親と子の見方は違う。
分かったこと/ハマリどころは:
- 物理的な入れ子はどうも良くない。サブモジュールのinitに失敗する。
- 物理的には分離して(つまり、サブディレクトリに置かないで)別リポジトリ管理が良さそう。
- 物理的に別なリポジトリを、公開ハブ(github, bitbucket)を通じて、git submodule add する。
- あとは通常の(教科書的な)サブモジュールの運用をするのが無難。
- 謎の入れ子リポジトリ情報は、バイナリファイル .git/index にあるかも知れない。少なくとも文字列としてはサブディレクトリ名が検出できる。
- git submodule init をしないと、submodule関係の操作が空振りする。git submodule init, git submodule update, git submodule checkout が必要。
- サブモジュールはそれ自体で管理すべきもので、親からの干渉は受けない。ただし、子がクリーンになっても親はダーティ状態。親は親としてのコミット&プッシュが必要。
- キチンとサブモジュールを使っていれば、git clone --recursive により、submodule init/update/checkout を自動でやってくれる。
gitのオブジェクトの種類にsubmoduleオブジェクトがあるんで、おそらく、物理的な入れ子のリポジトリは自動的にsubmoduleオブジェクトになるのだろう。だが、オブジェクトIDの物理的存在場所が分からないのでうまく扱えない、という事情だと予測している。