git cat-fileでGitとちょっと仲良くなる

この記事は[PONOS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ponos)の13日目の記事です。
昨日は、@iixd_pogさんの[Unityの画面管理はSceneかPrefabどちらにすべきか](https://qiita.com/iixd_pog/items/148f3c4fb478ec36c9f9)でした。

## はじめに

この記事では`git cat-file`コマンドをご紹介します。
さらに、使い方の解説も兼ねてGitオブジェクトが何なのかを探っていきたいと思います。

個人的に、このコマンドで遊んでみたことでGitの理解が深まったと感じています。
皆さまがGitと仲良くなるための一助になれば幸いです:smiley:


### 動作確認用リポジトリ

記事を書くときに使ったサンプル的なリポジトリです。

https://github.com/honeniq/git-object-hoge

これをクローンして頂ければ、例に出てくるハッシュ値などもそのままで試せると思います。


## `git cat-file`とは？

[git-cat-file](https://www.git-scm.com/docs/git-cat-file)

`git`のサブコマンドのひとつ。
Gitオブジェクトの種類や内容について表示するコマンドです。


### 基本的な使い方

```
git cat-file <type> <object>
```

表示したいGitオブジェクトの種類とオブジェクトIDを指定して使います。
が、後述する `-p` オプションが便利すぎるのであまり使わないかも。

### 便利なオプション

色々オプションがありますが、一番よく使うのは `-p` かなと思います。
`-p` を付けると、いわゆる「Pretty-print」をやってくれる動作となります。

```
git cat-file -p <object>
```

指定したオブジェクトの種類を自動判別し、見やすい形で表示してくれます。
`<type>`の指定も不要ですし、こちらの方が直感的かも。

今後この記事では、ひたすらこのオプションばかり使っていきます。


## Gitオブジェクトの種類

Gitは超絶便利なバージョン管理システムですが、その内側はGitオブジェクトで構成されています。
そして、Gitオブジェクトには大きく分けて4つの種類があります。
参考: [Gitの内側 - Gitオブジェクト](https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88)

- コミットオブジェクト
- ツリーオブジェクト
- ブロブオブジェクト（データオブジェクト）
- タグオブジェクト

それぞれどんな用途でどんな情報を持っているのか、実際にコマンドを叩いて見てみましょう。

### コミットオブジェクト

コミットオブジェクトはその名の通り、コミットの情報を保管するオブジェクトです。

`cat-file` で中身を見てみるとこんな感じになります。

```sh
$ git cat-file -p a00d
tree 21eeb2e86fbafc30830ecf25dc242d5c5ceaa954
parent 069dec119f2b6c4398b25149cedaa0efa84755fb
author honeniq <honeniq@gmail.com> 1670912169 +0900
committer honeniq <honeniq@gmail.com> 1670912169 +0900

Add fuga
```

次に、 `git-show` や `git-log` でコミットを表示したとき。

```sh
$ git log -n 1 a00d
commit a00d1945c41dde9675dda8cfe22380e276a8f04c
Author: honeniq <honeniq@gmail.com>
Date:   Tue Dec 13 15:16:09 2022 +0900

    Add fuga
```

書式のちょっとした違いはあるけど、だいたい同じ情報が出てきますね。
コミットオブジェクトの中身は **コミットそのもの** だと言えそうです。
異なる点は、 `cat-file` で表示したほうには`tree`と`parent`の情報があるところでしょうか。

`tree`はこのコミットが参照するツリーオブジェクト、`parent`は1つ前のコミットオブジェクト（親コミット）を指します


### ツリーオブジェクト

続いてツリーオブジェクトの中身を見てみましょう。

あるツリーオブジェクトの中身はこんな感じ。

```sh
$ git cat-file -p bdf3
100644 blob 5a58749fdda4388f0bf1040383e7e480ec6be8f5	hoge.txt
040000 tree 1695cfed85647375865ac1f9394c94a1707272a5	lib
# アクセス権 種類 ハッシュ値 ファイル名
```
ツリーオブジェクトには、そのツリーに含まれるオブジェクトの情報が入っています。
ファイルシステムにおけるディレクトリとツリー、ファイルとブロブが対応しているイメージです。

ツリーの中にさらにツリーが入っていることもあり、これを繰り返していくと複雑なツリー構造になります。

```sh
# リポジトリルートのツリーオブジェクト
$ git cat-file -p bdf3
100644 blob 5a58749fdda4388f0bf1040383e7e480ec6be8f5	hoge.txt
040000 tree 1695cfed85647375865ac1f9394c94a1707272a5	lib

# lib/ のツリーオブジェクト
$ git cat-file -p 1695
100644 blob 9128c3eb56a3547e2cca631042366bf0f37abe67	fuga.txt
100644 blob 0ef7e93778bbdf711c34afb388a40bf3c74e34c9	piyo.txt
```

ディレクトリ・ファイルの状態を見るとこう。

```
$ tree -L 2
.
├── hoge.txt
└── lib
    ├── fuga.txt
    └── piyo.txt
```

ツリーオブジェクトが記録している情報と一致しているのがわかります。


### ブロブオブジェクト（データオブジェクト）

次はデータオブジェクトです。
さっきのツリーオブジェクトから……

```sh
# lib/ のツリーオブジェクト
$ git cat-file -p 1695
100644 blob 9128c3eb56a3547e2cca631042366bf0f37abe67	fuga.txt
100644 blob 0ef7e93778bbdf711c34afb388a40bf3c74e34c9	piyo.txt
```

`fuga.txt` のブロブオブジェクトを調べてみましょう。

```sh
$ git cat-file -p 9128
fuga

$ cat lib/fuga.txt
fuga
```

中身が表示されました。`cat`で見た中身とも一致しています。
ブロブオブジェクトにはファイルの中身そのものが入っているみたいですね。

さらに言うと、ブロブオブジェクトは中身以外の情報を持っていないので、ファイル名などの属性情報はツリーオブジェクトから貰ってくるようです。
また、ファイルの作成日時や所有者の情報はツリーにもブロブにも書かれていないので、Gitは管理していないみたいですね。

### タグオブジェクト

最後はタグオブジェクトです。
実はGitのタグには2種類あって、タグ作成時のコメントの有無によって変化します。 [^1]

[^1]: この記事を書いているときに初めて知りました。参考: [Gitの内側 - Gitの参照](https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%81%AE%E5%8F%82%E7%85%A7#:~:text=outside%20of%20refs/-,%E3%82%BF%E3%82%B0,-%E3%81%93%E3%82%8C%E3%81%A7%20Git)

#### コメントなし（軽量タグ）

コメントがない場合、タグは特定のコミットオブジェクトへの参照を持っているだけです。

```sh
$ cat .git/refs/tags/v1.0-light
3685f753ac86b6500e240b796b48fdf643fc354c

$ git cat-file -p 3685
tree bdf3d7065da23ab84acc446c1415aca5a0667129
parent 78a2d49ba8c32b9ed674e6d93cffaa3c0a2c6135
author honeniq <honeniq@gmail.com> 1670914612 +0900
committer honeniq <honeniq@gmail.com> 1670914612 +0900

# タグが指しているのは特定のコミット
$ git log -n 1
commit 3685f753ac86b6500e240b796b48fdf643fc354c (HEAD -> main, tag: v1.0-light, tag: v1.0, origin/main)
Author: honeniq <honeniq@gmail.com>
Date:   Tue Dec 13 15:56:52 2022 +0900

    Fix hoge
```

#### コメントあり

コメント付きでタグを作った場合、タグオブジェクトが作成されます。

```sh
$ cat .git/refs/tags/v1.0
ea6446acfb62c0d0e6f68d6b60f3169c710cafff

$ git cat-file -p ea64
object 3685f753ac86b6500e240b796b48fdf643fc354c
type commit
tag v1.0
tagger honeniq <honeniq@gmail.com> 1670925496 +0900

tag v1.0
```

コメントなしの場合、タグは`3685...`のコミットオブジェクトを直接指していましたが、今回はタグオブジェクトを指しています。
結局タグオブジェクトの中で`3685...`を指しているわけですが、コメント等の情報も一緒に保管できています。

よーく見ると`type`の情報も入っていますね。
実はタグはコミット以外のオブジェクトにも付けられます。

```sh
$ git tag -a super-important-file -m 'super important file hoge.txt!!' 5a58

$ cat .git/refs/tags/super-important-file
92f5585cb92aab5f30507b829f83f2f84b6d286b

$ git cat-file -p 92f5
object 5a58749fdda4388f0bf1040383e7e480ec6be8f5
type blob
tag super-important-file
tagger honeniq <honeniq@gmail.com> 1670926708 +0900

super important file hoge.txt!!
```

> あらゆるGitオブジェクトに対してタグを付けることができます。 例えば Git のソースコードリポジトリでは、メンテナが自分のGPG公開鍵をブロブオブジェクトとして追加し、そのオブジェクトにタグを付けています。
参考: [Gitの内側 - Gitの参照](https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%81%AE%E5%8F%82%E7%85%A7#:~:text=%E3%81%BE%E3%81%9F%E3%80%81%E3%81%93%E3%81%AE%E9%A0%85%E7%9B%AE%E3%81%8C%E5%BF%85%E3%81%9A%E3%81%97%E3%82%82%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%A0%E3%81%91%E3%82%92%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%99%E3%82%8B%E3%82%82%E3%81%AE%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84%E3%81%93%E3%81%A8%E3%82%82%E8%A6%9A%E3%81%88%E3%81%A6%E3%81%8A%E3%81%84%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82)

知らなかった。勉強になりますね。


## まとめ

Gitはシンプルなパーツをたくさん組み合わせて動いているんだなと、改めて感心しました。
以前よりもちょっとだけ解った気になりつつ、これからも便利に使っていきます:metal:

明日は:tada:@ackylaさん:tada:です。