TT Lab
はじめる
学ぶ 学習パス コース

Git実戦

親が覚えているのはブランチではなくコミット一つ

TT Labで続きを見る

一言でいうと

サブモジュールで、親リポジトリが記録するのはコミットハッシュ1つです。ブランチではありません。そのため、サブモジュールは常にdetached HEADでチェックアウトされ、最新を取得して使った事実を親にコミットしないと、その変化は自分だけのものとして残ります。

なぜ必要なのか

複数のサービスが同じコードを共有する状況はよくあります。社内共通ライブラリ、プロトコル定義、設定テンプレートのようなものです。コピーしておくとすぐに分岐し、パッケージにして公開すると、デプロイの手順が1つ増えます。サブモジュールは、その中間の答えです。別のリポジトリを、自分のリポジトリの1つのディレクトリの位置に、ピンで固定しておきます。

問題は、「ピンで固定する」がどういう意味かを正確に知らないまま、使い始めることにあります。多くの人が、「親がそのライブラリのmainブランチを指している」と考えますが、そうではありません。親のツリーには、その位置にモード160000のエントリが1つ入り、値はコミットハッシュです。ブランチ名は、.gitmodulesにもデフォルトでは書かれません。

$ git ls-tree HEAD vendor/lib
160000 commit 9f3c1a4... 	vendor/lib

この1行が、サブモジュールのすべてです。これを知ると、続くすべての動作が説明できます。

どう動くのか

.gitmodulesは、どこから取得するか(url)とどこに置くか(path)だけを書いた設定ファイルで、追跡される通常のファイルです。実際にどのバージョンなのかは、ツリーの160000のエントリが決めます。2つは役割が違い、そのため、別々にコミットされます。

サブモジュールのディレクトリの中に入ってgit statusを実行すると、次のように表示されます。

HEAD detached at 9f3c1a4

親がブランチではなくコミットを記憶しているため、チェックアウトもコミットで行われます。ここでそのままコミットすると、どのブランチにも付いていないコミットになり、あとで探しにくくなります。サブモジュールで作業する場合は、先にブランチに切り替える必要があります。

git submodule statusの先頭の1文字が、状態を表します。

先頭の文字 意味
(空白) 親が記録したコミットと、実際のチェックアウトが同じです
+ サブモジュールが、親が記録したものとは別のコミットにあります
- まだ初期化されていません(ディレクトリが空です)
U マージの衝突が解決されていません

-は、特によく出会います。git cloneは、デフォルトではサブモジュールを取得しないため、取得した直後にビルドすると、空のディレクトリのために失敗します。git clone --recurse-submodulesで取得するか、取得したあとにgit submodule update --init --recursiveを実行します。普段からgit config submodule.recurse trueをオンにしておくと、checkout・pullがサブモジュールまで追従します。

+は、事故の種です。サブモジュールで最新を取得して使い、正常に動作するのを確認したのに、親でgit add vendor/libによってその変化をコミットしないと、自分のマシンでだけ新しいコードが使われます。同僚がcloneすると、親が記録した古いコミットを取得します。症状は、「自分のコンピューターでは動くのですが」の典型で、原因がサブモジュールだと気づくまで、長くかかります。

もう1つ。git 2.38.1から、サブモジュールをローカルパスやfile://で取得することが、デフォルトでブロックされました(CVE-2022-39253)。悪意のあるリポジトリが.gitmodulesを使って、他人のマシンのファイルを持ち出せたためです。ローカルパスでラボを行ったり、CIでキャッシュ用のリポジトリを使ったりするときは、-c protocol.file.allow=alwaysを明示する必要があります。

現場での姿

次のラボですること

ライブラリのリポジトリと親のリポジトリを自分で作って、サブモジュールとして組み込みます。ローカルパスがデフォルトでブロックされることに、まず出会い、親のツリーに160000のエントリが入るのを目で確認します。そのあと、ライブラリに新しいコミットを積み、サブモジュールだけを進めて、親をコミットしていない状態でcloneし、同僚が古いコードを取得する事故を、そのまま再現します。

公式ドキュメントは、git-submoduleとPro Git 7.11 Submodulesです。