🙆

Git 内部構造とオブジェクトについて

に公開

はじめに

参考文献に記載しているサイトについて、自分なりにまとめ直したもの。

対象

  • コミットの仕組みを理解したい人
  • .gitディレクトリで何が行われているか知りたい人
  • git add ~ git commitまで何を行っているか知りたい人

git addの役割

結論を言うと下記二つを行っている

  • Indexファイルの更新
  • blobオブジェクトの生成

結論を解いていく
まずREADME.mdをaddしてみる。

$ git add .

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   README.md

addしたことで何が起こっているのかというと
.git/indexファイルに、Addしたファイルが記載されている。
addをするとindexファイルに書き込まれる為、git addを完結にまとめると「ファイルをindexに登録する。」と言う

$ cat .git/index

DIRCf�#G>;f�#�	S7�����x�mb|��P��/Aw�l�	README.md��
��\�
    �odh�uL�9�

文字化けされたファイルは、git ls-filesコマンドで全体を見ることができる。
ここで重要なのがblobハッシュである。
このハッシュ値の意味を知るためにgitオブジェクトについて理解する必要がある。

$ git ls-files -s(--stage)

100644 8178c76d627cade75005b40711b92f4177bc6cfc 0	README.md
ファイルのパーミッション, blobハッシュ, ファイルのコンフリクトファイル, ファイル名

gitのオブジェクト

Gitオブジェクトの特徴

  • SHA-1ハッシュにる識別子が付与されている
  • 全部で4種類のオブジェクトがある。
  • .git/objects/ ディレクトリから確認可能

オブジェクトの種類は下記である。

  • commitオブジェクト
    • コミットの情報が入っている
  • tree オブジェクト
    • ディレクトリの情報が入っている
  • blob オブジェクト
    • ファイルの情報が入っている
  • tag オブジェクト
    • annotated tagの情報が入っている

blobオブジェクト

ファイルの情報が入っているオブジェクトになる。
先ほどのblobハッシュは、blobオブジェクトに紐づくKeyになる。

先ほどの例のblobハッシュを用いて、blobオブジェクトの中身を見る。

  • git cat-file -p {Blobハッシュ} - オブジェクトの中身を表示
  • git cat-file -t {Blobハッシュ} - オブジェクトのタイプを表示
$ git cat-file -p 8178c76d627cade75005b40711b92f4177bc6cfc
readme

$ git cat-file -t 8178c76d627cade75005b40711b92f4177bc6cfc
blob

なぜGitはBlobハッシュからディレクトリがわかるのか。

gitハッシュ値の頭二つの文字をディレクトリの場所を指定し、それ以降をファイル名と認識する。


GitObjectのファイルPath
git/objects/81/78c76d627cade75005b40711b92f4177bc6cfc

Blobハッシュ値
8178c76d627cade75005b40711b92f4177bc6cfc

このようにGitはBlobハッシュからBlobオブジェクトを探している。

Treeオブジェクトは生成しない。

後述するTreeオブジェクトはディレクトリの情報を持つが、Treeオブジェクトはコミット時にしか生成しない為、git addの際はIndexの更新 + blobオブジェクトの生成のみと認識してください。

git commitの役割

結論を言うと下記二つを行っている

  • treeオブジェクトの生成
  • commitオブジェクトを生成
  • HEADを新しいcommitハッシュに書き換え

まずディレクトリを

どう言うことかを見ていく
まずdir/tree-README.mdをコミットして、後述のコミットオブジェクトからTree IDを確認する。

$ git cat-file -p HEAD
tree b93ab1cbf066587c31d6bb0fa4a94385a6037fcb
author xxx <xxxx> 1711775287 +0900
committer xxxx <xxxx> 1711775287 +0900

make tree object

Treeオブジェクト

Treeオブジェクトのgitハッシュを細かく見ていく。
typeがblobとtreeのものが存在する。

$ git cat-file -p b93ab1cbf066587c31d6bb0fa4a94385a6037fcb
100644 blob 8178c76d627cade75005b40711b92f4177bc6cfc	README.md
040000 tree 2ccf9d7e775b527013c7d3b4879f80698e9d4662	dir

$ git cat-file -p 2ccf9d7e775b527013c7d3b4879f80698e9d4662
100644 blob 44136f7075f3cb8aa85065d96cf4515d87ac8694	tree-README.md

つまり、Treeオブジェクトの参照を介してBlobオブジェクトに到達する。ディレクトリ階層と同様の概念だと言える。

イメージ

             /- blob
コミットオブジェクト       /-blob
             \- tree
                    \-tree
                          \-blob

コミットオブジェクト

commitは、差分の箇所意外にも、リポジトリのルートディレクトリを含む全てのtreeを自動で生成してくれる。
所謂スナップショットをしてくれている為、チェックアウトに映った際にすぐ状態を復元できる。

commitオブジェクトは、以下の情報を持つ

  • リポジトリのルートディレクトリのtreeハッシュ
  • コミットしたユーザの情報
  • タイムスタンプ
  • コミットメッセージ
  • 親のコミットハッシュ
  • 親がないcommitは、initial commit
  • 親が2つあるのは、merge commit

先ほどの下記がコミットオブジェクトの情報。

$ git cat-file -p HEAD
tree b93ab1cbf066587c31d6bb0fa4a94385a6037fcb
author xxx <xxxx> 1711775287 +0900
committer xxxx <xxxx> 1711775287 +0900

make tree object

イメージ

             /- blob
1. コミットオブジェクト       /-blob
   |          \- tree
   |                \-tree
   |                       \-blob
   |
   |
   |         /- blob
2. コミットオブジェクト       /-blob
             \- tree
                    \-tree
                          \-blob

改ざんを防ぐ

コミットオブジェクトに親子ミットのハッシュ値を含めることで、セキュリティを上げている。

               /- コミット3 - 親のハッシュ値が変更された為、親を書き換える必要がある
コミット1 → コミット2
           ↑   \- コミット4 - 親のハッシュ値が変更された為、親を書き換える必要がある
     変更する

タグオブジェクト

タグオブジェクトは、注釈付きタグをつける時に作成されるオブジェクトで、タグではない。
実際にタグと注釈付きタグの違いについて見ていく。

まず、基礎知識としてrefsが何かを知る。

refs(リファレンス)

refsはcommitオブジェクトを指し示すポインタ。
その為、ファイルの中は、commitオブジェクトのハッシュ値が書かれているのみ。

refsは下記の特徴を持っている

  • commitオブジェクトのポインタ(ハッシュ値を持ってる)。
  • 簡単に指し示す先を変更できる(tagはできない)
  • .git/refs や、.git/HEAD で確認できる。
  • 3種類
    • branch
    • HEAD
    • tag

それぞれのオブジェクトは、.git/refs/格納されている。

.git/refs
├── heads
│   ├── branch1
│   └── main
└── tags
    └── tag1

タグ

タグはオブジェクトの中身はコミットIDを指しているのみでシンプル。

$ cat .git/refs/heads/branch1
0cd8d59abb48c93252b4b98435144898787fdceb

$ git cat-file -p 0cd8d59abb48c93252b4b98435144898787fdceb
tree b93ab1cbf066587c31d6bb0fa4a94385a6037fcb
author xxxx <xxx> 1711775287 +0900
committer xxx <xxxx> 1711775287 +0900

make tree object

$ git cat-file -t 0cd8d59abb48c93252b4b98435144898787fdceb
commit

注釈付きタグ

注釈付きタグは、直接コミットオブジェクトを指定せず、タグオブジェクトを指している。
タグオブジェクトからはを付けた人の情報やタグや注釈を含んでいることがわかる。

$ git tag annotated_tag -m "tag with annotation"

$ cat .git/refs/tags/annotated_tag
38033a087ab2803221fdbecd33c3e84c20366327

$ git cat-file -t 38033a087ab2803221fdbecd33c3e84c20366327
tag

$ git cat-file -p 38033a087ab2803221fdbecd33c3e84c20366327
object 0cd8d59abb48c93252b4b98435144898787fdceb
type commit
tag annotated_tag
tagger xxxx <xxxx> 1711780117 +0900

tag with annotation

$ git cat-file -t 0cd8d59abb48c93252b4b98435144898787fdceb
commit

イメージ

                              /- tree
シンプルなタグ: commitオブジェクト
                              \- blob

                                       /-tree
注釈付きタグ: タグオブジェクト - commitオブジェクト
                                       \-blob

まとめ

  • treeからコミットごとにファイルを全て複製している為、git checkoutで迅速に環境を切り替えられる理由がわかる。

  • Gitが管理するのはあくまでもblobオブジェクトであるため、空ディレクトリは管理対象外になる理由がわかる。

  • タグとタグオブジェクトは違うもので、タグが指しているのはあくまでもオブジェクトであり、それが直接コミットを指すか(軽量タグ)、タグオブジェクトを指すか(注釈付きタグ)の違いがある。

参考文献

https://speakerdeck.com/mixi_engineers/2023-git-training
https://git-scm.com/book/ja/v2/Gitの内側-Gitの参照
https://github.blog/jp/2021-01-06-commits-are-snapshots-not-diffs/

Discussion