Gitの内部の動きを知ろう　～その１～

### [目次](https://qiita.com/as-chapa/items/246b03c671bd798d50a8)
# はじめに
本記事は「Git初心者」向けではないです。目的に応じてコマンドを知りたい人は別の方の記事や書籍を参照ください。
本記事ではGitのコマンドの裏で、「.git/」配下がどのように変わっていくかを追うことを目的としています。きっとそこからGitの深淵な世界が見えることを期待して...
（「エンジニアのためのGitの教科書（上級編）」に大きな影響を受けた記事になります。できれば、「エンジニアのためのGitの教科書（上級編）」で書かれていないコマンドの動きまで到達することが目標ですが、基本の部分だけでかなり時間を使ってしまった。。。）

# 確認環境
```
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.4 LTS (Bionic Beaver)"
```
```
$ git --version
git version 2.17.1
```

# 基本コマンド
まずは「git init」で初期化して、「git add」「git commit」の流れを追っていきます。

## git init
まずは基本中の基本から。初期状態はこのような感じで、「local」配下にファイルを作ってgit initを実行します。

```
$ tree
.
└── local

1 directory, 0 files
```
```
$ tree
.
└── local
    └── l_helloworld.txt

1 directory, 1 file
```
中身はこんな感じ。

```
$ cat local/l_helloworld.txt
Hello, world!
```
では、local配下でgit initをします。

```
$ git init
Initialized empty Git repository in /home/*****/qiita/local/.git/
```
ディレクトリを覗きます。

```
$ tree -a
.
├── .git
│   ├── HEAD
│   ├── branches
│   ├── config
│   ├── description
│   ├── hooks
│   │   ├── applypatch-msg.sample
│   │   ├── commit-msg.sample
│   │   ├── fsmonitor-watchman.sample
│   │   ├── post-update.sample
│   │   ├── pre-applypatch.sample
│   │   ├── pre-commit.sample
│   │   ├── pre-push.sample
│   │   ├── pre-rebase.sample
│   │   ├── pre-receive.sample
│   │   ├── prepare-commit-msg.sample
│   │   └── update.sample
│   ├── info
│   │   └── exclude
│   ├── objects
│   │   ├── info
│   │   └── pack
│   └── refs
│       ├── heads
│       └── tags
└── l_helloworld.txt

10 directories, 16 files
```

ディレクトリとファイルがいくつか作られました。
gitではオブジェクトとして、変更履歴を管理しますが、現時点では何も変更されていないので「objects」配下には何もありません。
オブジェクトには「commitオブジェクト」「treeオブジェクト」「blobオブジェクト」があり、それぞれハッシュ値を持っています。それらの動きをコマンドを叩きながら見ていきます。

## git add
では早速「l_helloworld.txt」を追加します。

```
$ git add l_helloworld.txt
$ git status
On branch master

No commits yet

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

        new file:   l_helloworld.txt
```
ステージングに無事にファイルが登録されました。
では.git/配下の変更点を確認してみます。

```
(差分のみに加工しています）
$ tree -a
.
├── .git
│   ├── index
│   ├── objects
│   │   ├── af
│   │   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b


11 directories, 18 files
```
「index」というバイナリファイルと、「objects」配下に1つのファイルができました。「blobオブジェクト」になります。gitでは検索性を高めるためにオブジェクトの最初の2文字でディレクトリを切る動きをします。
まずはindexの中身を見てみましょう。

```
$ git ls-files -s
100644 af5626b4a114abcb82d63db7c8082c3c4756e51b 0       l_helloworld.txt
```
これを見るとl_helloworld.txtのblobオブジェクトのハッシュ値が「af5626b4a114abcb82d63db7c8082c3c4756e51b」であることがわかります。
ではオブジェクトの中身を見ていきます。

```
$ git cat-file -t af5626b4a114abcb82d63db7c8082c3c4756e51b
blob
$ git cat-file -p af5626b4a114abcb82d63db7c8082c3c4756e51b
Hello, world!
```
「l_helloworld.txt」の中身がblobオブジェクトに格納されていることがわかります。

## git commit
それでは先ほどステージングにaddしたファイルをcommitします。

```
$ git commit -m "first commit"
[master (root-commit) 17e88b4] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 l_helloworld.txt
```
```
(差分のみに加工しています）
$ tree -a
.
├── .git
│   ├── COMMIT_EDITMSG
│   ├── logs
│   │   ├── HEAD
│   │   └── refs
│   │       └── heads
│   │           └── master
│   ├── objects
│   │   ├── 04
│   │   │   └── fc877e35cb6c9bd038084d6b0fa8eae043884d
│   │   ├── 17
│   │   │   └── e88b45fdb1b3e8831df0c209204f290d6ca614
│   └── refs
│       ├── heads
│       │   └── master

16 directories, 24 files
```
ここでやっとmasterブランチが作成されました。
「.git/refs/heads/master」の中身を確認します。

```
$ cat .git/refs/heads/master
17e88b45fdb1b3e8831df0c209204f290d6ca614
```

これはmasterが「17e88b45fdb1b3e8831df0c209204f290d6ca614」というcommitオブジェクトを参照していることを指しています。

```
$ git cat-file -t 17e88b45fdb1b3e8831df0c209204f290d6ca614
commit
$ git cat-file -p 17e88b45fdb1b3e8831df0c209204f290d6ca614
tree 04fc877e35cb6c9bd038084d6b0fa8eae043884d
author Chapa <hoge@hoge.com> 1580696403 +0900
committer Chapa <hoge@hoge.com> 1580696403 +0900

first commit
```
treeオブジェクトのハッシュ値やコメントなどが記載されています。

```
$ git cat-file -t 04fc877e35cb6c9bd038084d6b0fa8eae043884d
tree
$ git cat-file -p 04fc877e35cb6c9bd038084d6b0fa8eae043884d
100644 blob af5626b4a114abcb82d63db7c8082c3c4756e51b    l_helloworld.txt
```
ここでやっと先ほどステージングにaddしたオブジェクトと結びつきました。

ファイルを作成して、ステージングにaddしてcommitをするだけでも、これだけgit内部では様々な動きをしながら管理していることがわかります。

その他のコマンドについても、同様に変化を追っていきたいと思いますが、長くなったので導入としてはここまでとします。
（他のコマンドについては、またの機会に記事を作りたいと思います）