容量が大きいリポジトリをサブモジュールに登録していると、サブモジュールの更新には結構な時間が掛かります。
なぜ時間がかかるかと言うと git submodule update --remote
をすると新しい内容のダウンロード(とそのチェックアウト)が発生するためです。小さいリポジトリであれば何も問題ありませんが、巨大なリポジトリになると無視できない時間が掛かりますし、ディスク容量も消費します。(私の見たことのあるプロジェクトでは一回の更新に10分以上かかってました😫)
このダウンロードを避け、手早くサブモジュールのバージョンだけを更新したいという話です。
尚、今回の方法は事前に更新後の Commit ID (ハッシュ値) を知っている必要があります。事前に更新後の Commit ID が分かっている状況であればダウンロードを省略して更新できるよ!という趣旨です。
サブモジュールのダウンロードを省略した更新コマンド
先に結論を書くと、git submodule update
の代わりに git update-index
コマンドを使うと参照するコミットを強制的に書き換えられます。このコマンドは一瞬で終わります。(ここで指定する Commit ID は外部リポジトリのものです。)
$ git update-index --cacheinfo "160000" "更新後のCommitID" "サブディレクトリのパス"
実際にプッシュまで行うときの流れは以下のようになるでしょう。
$ git update-index --cacheinfo "160000" "d732318e5286f6e06beb5d1c958a9ecaee4fe1b5" "vendor/laradock"
$ git commit -m "Update submodule"
$ git push origin
これでなぜ更新できるのか、仕組みを知りたい人は以下も読んでみてください。
そもそも Git submodule とはどんな仕組み?
git submodule
は、他のリポジトリの内容を、自分のリポジトリにサブモジュール(サブディレクトリ)として登録し、参照するための仕組みです。基本的な仕組みについては Git submodule の基礎 などが参考になると思います。
今回の話で重要になるのが、サブモジュールを登録する側の参照元リポジトリでは、外部リポジトリの特定コミットへの参照だけを登録する 点です。参照先にあるファイルの実体については登録しません。
たとえ巨大なリポジトリをサブモジュールとして登録したとしても Git のデータベース容量はほとんど増えません。参照先の実体の内容を git submodule update --init
などでダウンロードするときに初めてローカルのディスク容量を消費します。
Git のデータベースにはどのように登録される?
Git のデータベースに参照だけが登録されることを実際に確認してみます。
# テスト用の参照元リポジトリを適当に作る
$ mkdir parent-repo
$ cd parent-repo
$ git init
# 外部リポジトリをサブモジュールとして追加する
$ git submodule add https://github.com/laradock/laradock vendor/laradock
Cloning into '/tmp/parent-repo/vendor/laradock'...
Resolving deltas: 100% (5379/5379), done.
# 追加後に Git にステージされているファイルの一覧を出力する
$ git ls-files --stage
100644 c0d8e4208598468cf860099446f32ba7127b6759 0 .gitmodules
160000 8203e5da2992db2e242a6dd9733f12f5c664842e 0 vendor/laradock
上記の結果を見ると git submodule add
により2つのファイルが Git に追加されています。サブモジュールの一覧を持つメタファイル .gitmodules
と、サブモジュールの vendor/laradock
です。
注目すべきは出力された1列目の数字です。この数字はモードビットと呼ばれ、ビット列の前半部分がファイル種別を、後半部分がパーミッションを表します。
主なモードビットの一覧:
- 100644 (
1000000110100100
): 実行不可能な通常ファイル - 100755 (
1000000111101101
): 実行可能な通常ファイル - 040000 (
0100000000000000
): ディレクトリ - 120000 (
1010000000000000
): Symbolic link - 160000 (
1110000000000000
): Gitlink (Submodule)
これと照らし合わせると、先ほどの .gitmodules
の 100644
は「実行不可能な通常ファイル」として登録されています。一方で vendor/laradock
は「Gitlink」として、つまり 外部リポジトリの特定コミットへの参照として登録されています。
実際に参照している外部リポジトリの Commit ID は以下のコマンドで確認できます。
$ git submodule status
8203e5da2992db2e242a6dd9733f12f5c664842e vendor/laradock (v9.3-22-g8203e5d)
Git submodule の参照先を更新するには?
サブモジュールのリモート上の変更を取り込みたい場合は通常 git submodule update --remote
のコマンドを実行します。このコマンドはサブモジュールのサブディレクトリ上で git fetch
してから git merge origin/master
を行うのと同じことです。このときの git fetch
で実体のダウンロードとチェックアウトが発生します。
このダウンロードを回避し Git データベース上の参照ポインターだけを書き換えるコマンドが、冒頭に書いた git update-index
になります。
$ git update-index --cacheinfo "160000" "d732318e5286f6e06beb5d1c958a9ecaee4fe1b5" "vendor/laradock"
ここで引数に 160000
という見覚えのある数字が出てきました。これは先ほどのモードビットの Gitlink を意味します。そして次の引数で Gitlink の参照先の Commit ID を指定します。
Commit ID には実際に存在しないものも指定できてしまうので、間違えないように注意が必要です。