Gitの内部構造とコンテンツアドレス指定オブジェクトモデル
Gitの論理オブジェクトと実際の保存形式を分けて理解し、packfile・参照・clone方式が保存量や転送範囲に与える違いを確認します。
発行・訂正窓口:infostoa · 編集基準
この記事の個別の執筆者・専門査読者の情報は提供されていません。
Gitの保存構造を理解するときは、論理的なオブジェクトモデルとディスク上の保存形式を分けて考えます。Gitは内容からオブジェクトIDを決め、commitはtreeを参照します。ただし、すべてのオブジェクトが常に個別ファイルとして残るわけでも、cloneですべての履歴が必ず転送されるわけでもありません。
オブジェクトIDと4種類のオブジェクト
GitのオブジェクトIDはファイルのバイト列だけをハッシュした値ではありません。オブジェクトの種類と長さを含むヘッダー、区切り、内容がハッシュの入力になります。また、IDの形式はリポジトリがSHA-1形式かSHA-256形式かによって異なります。「常に40桁のSHA-1」とは限りません。
| オブジェクト | 論理的に保持する内容 | ファイル名との関係 |
|---|---|---|
| blob | ファイルのバイト列 | ファイル名やパスは含まない |
| tree | 項目名、モード、オブジェクト種別とID | ディレクトリ階層を構成する |
| commit | ルートtree、親commit(複数可)、作成者・コミッター、メッセージ | プロジェクト状態と履歴を結ぶ |
| annotated tag | 対象オブジェクト、タグ情報、任意の署名 | lightweight tagは独立したtagオブジェクトを持たず、参照として機能する |
同じリポジトリで、内容とオブジェクトヘッダーが同じなら同じ論理blob IDになります。そのため複数のtree項目が同じblobを参照できます。これは論理オブジェクトの重複を避けられるという意味であり、どの保存形式でも物理ディスク上に必ず1つだけ存在するという保証ではありません。
論理上の変更とディスク使用量を分ける
大きなファイルの1バイトを変更すると内容が変わるため、新しいblob IDになります。変更後のファイルを指すtreeや新しいcommitも必要になる場合があります。一方、変更していないファイルは既存のblobを参照し続け、同一内容のtreeを再利用できる場合もあります。
これは論理オブジェクトグラフの説明です。loose objectは個別ファイルとして保存されることがありますが、Gitは複数のオブジェクトをpackfileにまとめ、似たオブジェクトをdelta圧縮することもできます。したがって、1バイトの変更ごとに大きなファイル全体が新しい実体としてディスクに残る、とは言えません。ただし、delta圧縮の節約量がすべてのファイルで同じになるわけでもありません。実際の容量は内容、packの構成、圧縮、保持する履歴に左右されます。
ブランチとタグは対象を指す名前
ブランチはcommitを指す移動可能な参照です。「ブランチはポインター」という説明は役立ちますが、.git/refs/heads/の下に常に41バイトのファイルがあるとは限りません。loose refのほか、packed-refsにまとめられる場合があり、保存形式は設定やref backendにも左右されます。また、ブランチとタグは同じものではありません。annotated tagは独立オブジェクトを持つことがありますが、lightweight tagはrefだけです。
ローカルリポジトリの確認には、次のコマンドを使えます。
git cat-file -t HEAD
git cat-file -p HEAD
git cat-file -p 'HEAD^{tree}'
git show-ref --head
最初のコマンドはHEADのオブジェクト種別を、次はその内容を表示します。tree式はそのcommitの最上位項目を、show-refはリポジトリが認識している参照を表示します。結果は対象リポジトリの状態によって異なります。この記事の確認作業ではコマンドを実行していません。
cloneで常に全履歴が転送されるわけではない
通常のcloneは選択したリモート参照の履歴を取得しますが、「すべてのcloneが全履歴オブジェクトを取得する」という説明は正確ではありません。--depthは浅い履歴を作り、--filter=blob:noneのようなpartial-cloneフィルターは一部のblob転送を必要になるまで遅らせます。実際の転送範囲は対象の参照、指定したフィルター、サーバーの対応に左右されます。
大容量ファイルの問題に対応するときは、まず何が大きいのかを切り分けます。clone時の転送量、作業ツリーの容量、全履歴の保存量、特定ファイルの版が多数残ることは、それぞれ別の問題です。shallow/partial cloneは取得範囲を調整し、Git LFSは大容量ファイルの内容を別の保存経路で扱います。履歴へのアクセス要件、サーバーの対応、チームの復旧・バックアップ手順を確認して選んでください。
参照資料と確認範囲
オブジェクトモデル、packfileとdelta、参照の保存場所、shallow/partial clone、SHA-256形式は、Git Internals: Git Objects、Packfiles、[git cloneのマニュアル](https://git-scm.com/docs/git-clone)、リポジトリのレイアウト、ハッシュ関数移行の資料で確認しました。掲載したコマンドは読者自身のリポジトリを調べるための例であり、この記事の検証で実行したコマンド出力や容量測定はありません。