Help us understand the problem. What is going on with this article?

巨大な 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 には実際に存在しないものも指定できてしまうので、間違えないように注意が必要です。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away