LoginSignup
0

More than 3 years have passed since last update.

posted at

updated at

巨大な Git submodule のバージョンを一瞬で更新する方法

容量が大きいリポジトリをサブモジュールに登録していると、サブモジュールの更新には結構な時間が掛かります。

なぜ時間がかかるかと言うと 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)

これと照らし合わせると、先ほどの .gitmodules100644 は「実行不可能な通常ファイル」として登録されています。一方で 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 には実際に存在しないものも指定できてしまうので、間違えないように注意が必要です。

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0