このブログは、旧・はてなダイアリー「檜山正幸のキマイラ飼育記 メモ編」(http://d.hatena.ne.jp/m-hiyama-memo/)のデータを移行・保存したものであり、今後(2019年1月以降)更新の予定はありません。

今後の更新は、新しいブログ http://m-hiyama-memo.hatenablog.com/ で行います。

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の物理的存在場所が分からないのでうまく扱えない、という事情だと予測している。