鱒身(Masu_mi)のブログ

知った事をメモする場所。

GitのオブジェクトDBと索引

Gitの内部構造の概略と調べるのに必要な箇所を整理する。 なおソースへのリンクはv2.1.0-rc2 を参照している。 Git について勉強した事がなかったので調べた。

Gitの概要

Git は分散型バージョン管理システムと呼ばれている。

Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

ref. http://git-scm.com/

2層のアーキテクチャ

Git は次の2層アーキテクチャで実現されている。

  1. 連想記憶ファイルシステム(content-addressable filesystem)
    Git は対象をオブジェクトの集まりとして捉え管理している。
  2. VCS ユーザ・インタフェース(VCS user interface)
    参照・シンボリック参照と呼ばれる要素を用いてbranch, tag などを表現している

これらは、プロジェクトルート[1] に存在する.git ディレクトリの中に収まっている。

コアファイルの構成(.git/)

.git ディレクトリは以下の様になっている。 行った操作履歴により存在するファイルは異なる[#other_files]_が基本的な構造は変わらない。 自分の環境を例に出す。

$ (cd path/to/proj_root  && tree -L 1 .git)
.git
├── COMMIT_EDITMSG
├── HEAD
├── ORIG_HEAD
├── branches
├── config
├── description
├── hooks
├── index
├── info
├── logs
├── objects
└── refs

上記のファイル・ディレクトリが<連想記憶ファイルシステム(CAF)/VCS ユーザ・インタフェース(VCSUI)>に関係が強いか? ファイル、ディレクトリなら格納されるファイルの種類、目的などの概要を整理すると以下にになる。

名前 関連 ファイルタイプ 概要
HEAD VCSUI file: ref|sym_ref ブランチ・コミットなど今のHEADの位置を指す
index CAF file: index stage に対応するcacheされているblobへの参照が保持されている
logs VCSUI dir : log commit が作成された時の変化(前後のcommit)を記録している
objects CAF dir : object オブジェクトを保持する
refs VCSUI dir : ref commit, tag への参照を保持する
ORIG_HEAD VCSUI file: ref(only?) マージなど危険な操作の直前のコミットを指す
COMMIT_EDITMSG VCSUI file: text 前回のコミットメッセージが含まれている
hooks VCSUI dir : scripts gitにhookする実行ファイルを配置
branches ? dir : ? 調べてない
description ? file: ? 調べてない
info ? dir : ? 調べてない

システムの骨格を理解するのが目的なので触れるのは ORIG_HEAD より上に並べたファイル群に絞る。

Git システムを構成するファイルの種類

前節でファイルタイプとして書いたように、Git システムは幾つかの種類のファイルで作られている。 ここでは、ファイル・タイプを2層のアーキテクチャで区分けして説明する。

content-addressable filesystem を支えるファイル

Content-Addressable Filesystem は、作業ディレクトリのツリー情報(ファイル内容・メタ情報・ディレクトリの格納関係)・変更履歴(コミット)・タグ、を全て格納しており、 格納しているデータベース部分・次に格納する情報をまとめたインデックス、の2つで構成されている。

  • Git Objects 管理対象になっているファイル・ディレクトリ・コミット・タグなど全てを表現する
  • Index git 運用でのstage に対応するObjectの一覧を保持する

Git Objects

Gitプロジェクトの歴史を4種類のオブジェクトの組み合わせで表現している 。 各構造体の定義を見るとstruct object を最初のメンバに含んだ形になっている。

タイプ名 表現対象 関連ソース
blob ファイルの内容 https://github.com/git/git/blob/v2.1.0-rc2/blob.h#L8-L10
tree ディレクトリが保持するファイル名とファイルのメタデータ https://github.com/git/git/blob/v2.1.0-rc2/tree.h#L8-L12
comit コミット https://github.com/git/git/blob/v2.1.0-rc2/commit.h#L16-L23
tag タグ(オブジェクトに対するコメント付きの参照) https://github.com/git/git/blob/v2.1.0-rc2/tag.h#L8-L13

Objectタイプ名と表現対象を連結しzlibで圧縮した内容を持つファイルになる。

そのファイルの置かれる場所はファイルの内容をsha1 を取ることで名前が一意に決まる。 具体的にはハッシュ値の上位2桁が.git/objects 以下のディレクトリ名に対応し残りの38桁がファイル名になる。 tree .git/objects とかするとハッシュに対応するようにファイルが置かれている事がわかる。 この “ディレクトリ名+ファイル名” をgit-cat-file [3] すると中身を読むことが出来る。

$ (cd path/to/proj_root && tree .git/objects)
# とてもたくさんのファイルが流れてく
$ git cat-file -p ${SHA1_FILE_NAME}

ファイルの内容はblobが保持する。blob 以外のオブジェクトは他のオブジェクトのハッシュ値を経由し関係を保持する。

作業ディレクトリのツリー構造はtree オブジェクトが保持するが以下の様にリンクで実現されている。

http://git-scm.com/figures/18333fig0901-tn.png

コミット履歴についても同様でcommit オブジェクトの持つリンクで実現される。

http://git-scm.com/figures/18333fig0903-tn.png

ref. http://git-scm.com/book/en/Git-Internals-Git-Objects

tag オブジェクトの役割はgit-tag で作られるタグと即座には対応しない。 公式資料 にある通り実態は2種類ある。

タグは後述する参照がオブジェクトのハッシュ値をrefs/tags 以下に保持することで実現される。 この時どのようなオブジェクトでもタグ付けが可能だが、タグを生成するときにコメントを記述した場合には、対象オブジェクトを指すtag オブジェクトが生成される。 この時、タグに対応した参照はtag オブジェクトを指している。

Index

stage に対応するblob オブジェクトへの一覧が格納されている。 サブディレクトリが存在してもtree を介した表現にはならない。 登録されたファイルのプロジェクトルートからの相対パスとblob オブジェクトの名前(sha1) が格納されている。 このためGit では空ディレクトリをトラック出来ないという制約に繋がる。

git ls-files –stage で内容を確認できる。

$ git ls-files --stage

VCS User Interface を支えるファイル

  • 参照(Git References) Commitオブジェクトの名前を含むテキストファイルになっている
  • シンボリック参照 参照ファイルのファイルパスを含む
  • ログ Commitの変更を記録しているテキストファイル

参照

オブジェクトの名前に対応するハッシュ値を保持したテキストファイル

オブジェクトは名前がハッシュ値になってしまうので人間から指定が識別しづらい。 そこで、重要なオブジェクトを人間が扱えるように保持するファイルが役に立つ。 後述するシンボリック参照と共にVCS ユーザ・インタフェースを支えている。

主に .git/refs 以下に配置される。

$ find .git/refs -type f
.git/refs/heads/master
.git/refs/remotes/origin/master
.git/refs/tags/test
$ git cat-file -p `cat .git/refs/heads/master`
http://git-scm.com/figures/18333fig0904-tn.png

ref. http://git-scm.com/book/en/Git-Internals-Git-References

シンボリック参照

参照に対するエイリアスとして働き”ref: “ を先頭に持つテキストファイル
$ cat .git/HEAD
ref: refs/heads/master

Log

特定のブランチ, HEADなどの指す先が変更された時の記録で、各変更前後のcommit を保持している。

脚注

[1]管理対象にしているトップディレクトリ(基本的には git init したディレクトリ)
[2]状況により packed-refs なども存在するなど以下と必ず一致するとは限らない。
[3]Git にはObject ファイルの中身を確認する低レイヤコマンド(配管(Plumbing)と呼ばれる)がある。