はじめに

便利に使わせてもらっているけど、よく考えたら仕組み知らないなぁとか、これってどんな風に管理してるんだろう?と思うものが多くある。

よく利用しているけど、あまり仕組みがわかっていないものの1つに Git がある。

思い立ったが吉日。今回は、 git add したときや、git commit したときに実際Gitリポジトリではどんなことが起こっているのか、実際に手を動かしつつ、ファイルの動きを見ようと思う。

ちなみに、参考にしたのは Git book 。説明自体はすべてそこに書いてあるけど、納得するには実際に自分でやってみるってのが大事だと思う。

内容

Gitがファイルを管理するときに実際内部ではどんなファイルが作られて、どのように管理されているのか見ていこうと思う。

大きく以下のステップで進める。

  1. ローカルにGitリポジトリを作成する
  2. 作業ディレクトリにファイルを作成する
  3. 作業ディレクトリの内容をインデックスに追加する
  4. インデックスに追加した内容をコミットする

環境

  • ローカル環境にGitをインストール済み
  • Gitの初期設定済み(user.nameとuser.emailはglobalで設定済み)
  • Windows環境でcygwinを利用

ローカルにGitリポジトリを作成する

git init してGitリポジトリの用意

手始めに、ディレクトリを作成し、Gitリポジトリを作成する。

そもそもの話として、Gitを利用するにあたってはGithubのようなホスティングサイトは必須ではない。

ローカルのファイル管理のためだけであってもGitは利用できる。

$ mkdir gittest # Create directory
$ cd gittest # Move to gittest dir
$ git init # Initialize Git repository
1
2
3

git init 後のディレクトリ構成

gittest/
` -- .git/ 
     | -- config
     | -- description
     | -- HEAD
     | -- hooks/
     |    ` -- (略)
     | -- info/
     |    ` -- exclude
     | -- objects/
     |    | -- info/
     |    ` -- pack/
     ` -- refs/
          | -- heads/
          ` -- tags/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

ワーキングツリー(作業ディレクトリ)にファイルを作成する

作業ディレクトリ(gittestディレクトリ)でファイルを作成

作業ディレクトリ(gittest直下)にファイルを作成する。

file1.txtfile2.txt を作成。

echo test1 > file1.txt
echo test2 > file2.txt
1
2

ファイル作成時点のGItの状態を確認

git status で現在の状態を確認。

$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        file1.txt
        file2.txt

nothing added to commit but untracked files present (use "git add" to track)
1
2
3
4
5
6
7
8
9
10
11
12

file1.txtfile2.txtUntracked の状態であることがわかる。

つまり、「ワーキングツリーには存在しているが、まだインデックスに追加されておらずGitとして追跡対象になっていない状態」。

ファイル作成時点のディレクトリの状態

gittest/
| -- .git/
|    | -- config
|    | -- description
|    | -- HEAD
|    | -- hooks/
|    |    ` -- (略)
|    | -- info/
|    |    ` -- exclude
|    | -- objects/
|    |    | -- info/
|    |    ` -- pack/
|    ` -- refs/
|         | -- heads/
|         ` -- tags/
| -- file1.txt <- 作成したファイル
` -- file2.txt <- 作成したファイル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

ワーキングツリー(作業ディレクトリ)の内容をインデックスに追加する

作成したファイルをインデックスに追加(=ステージングエリアに追加する)

$ git add file1.txt
$ git add file2.txt
1
2

or

$ git add . # Add all files in working tree to index 
1

追加時点のGitの状態を確認

インデックスに追加後のGitの状態を確認。

$ git status
On branch master

No commits yet

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

        new file:   file1.txt
        new file:   file2.txt
1
2
3
4
5
6
7
8
9
10

Changes to be committed に作成したファイルが表示されており、リポジトリとステージングエリアに差分がある状態であることがわかります。

追加時点のディレクトリの状態

gittest/
| -- .git/
|    | -- config
|    | -- description
|    | -- HEAD
|    | -- index <- ★
|    | -- hooks/
|    |    ` -- (略)
|    | -- info/
|    |    ` -- exclude
|    | -- objects/
|    |    | -- 18/ <- ★
|    |    |    ` -- 0cf8328022becee9aaa2577a8f84ea2b9f3827
|    |    | -- a5/ <- ★
|    |    |    ` -- bce3fd2565d8f458555a0c6f42d0504a848bd5
|    |    | -- info/
|    |    ` -- pack/
|    ` -- refs/
|         | -- heads/
|         ` -- tags/
| -- file1.txt
` -- file2.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

わかりやすく、git add する前のディレクトリの状態との差分を見てみる。

$ diff -r ./ ../beforeAdd/
./.git のみに存在: index
./.git/objects のみに存在: 18
./.git/objects のみに存在: a5
1
2
3
4

git add する前に比べ、.gitディレクトリ以下のファイルに変化があり、具体的には以下の変化が起こっている。

  • .git/以下にindexファイルが存在
  • *.git/objects/*以下に、18a5 ディレクトリが存在
    • 各ディレクト下に0cf83… *bce3f…*ファイルが存在
indexファイルを探る
$ cat .git/index
DIRC      ]#4□R□]#4□□8          □□           □□□□%e□□XUZoB□PJ
□□□  file1.txt ]#-4X□□]#A        cT          □□           □2□"□□
骢Wz□□□+□8'         file2.txt 0□□|□l□#□D□□□&*□□
1
2
3
4

テキストファイルではなさそう。ただ、file1.txtfile2.txt という文字列が見えるので、インデックスの情報を管理しているものと思われる。

ちなみに、インデックス(ステージングエリア)についてのより詳しい情報は以下が参考になった。

*.git/objects/*配下のファイルを探る
$ cat .git/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827
xK□□OR0c(I-.1□ □
1
2
$ cat .git/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5
xK□□OR0c(I-.1□ □
1
2

こちらも、テキストファイルではなさそう。

結論からいうと、これがGitでファイルを管理しているオブジェクトみたい。

GItオブジェクトを確認するためのコマンド git cat-file -p [^1]で見ると中身を見ることができ、先ほどのファイルの情報であることがわかる。

ディレクトリ名とファイル名とを合わせたものを指定して確認可能。

$ git cat-file -p 180cf8328022becee9aaa2577a8f84ea2b9f3827 # dir name(18) and file name
test2
1
2
$ git cat-file -p a5bce3fd2565d8f458555a0c6f42d0504a848bd5 # dir name(a5) and file name
test1
1
2

ただし、このオブジェクトには、テキストの中身の情報しか管理されておらず、ファイル名等のメタデータは保持されていないようである。

なお、git cat-file -t [^1] を利用すると、Gitオブジェクトのどのタイプであるのかも確認できる。(先に言ってしまうと、コミット等もその実体はGitオブジェクト。)

$ git cat-file -t 180cf8328022becee9aaa2577a8f84ea2b9f3827
blob
1
2

blobタイプであることがわかる。ここのblobも、いわゆるBinary Large Objectのことであると思われる。

なお、このタイプは以下のようになっているらしい。

  • ファイルはzlibライブラリを用いて圧縮されスナップショットとして保管される
  • スナップショットはSHA-1のハッシュ値で管理される
  • ハッシュ値は40文字から成り、最初2文字がディレクトリ、残り38文字がファイル名になる

インデックス(ステージングエリア)について

git ls-files --staged[^2] を利用して、ステージングエリアについての情報を確認可能。

$ git ls-files --stage
100644 a5bce3fd2565d8f458555a0c6f42d0504a848bd5 0       file1.txt
100644 180cf8328022becee9aaa2577a8f84ea2b9f3827 0       file2.txt
1
2
3

この内容は以下のことを示している。

  • ステージングエリアで、追跡しているファイルとそのスナップショットの情報を持っている
  • 先頭の数字はファイルの種別とパーミッション

インデックスに追加した内容をコミットする

ここまででコミットの準備は整っているので、コミットする。

$ git commit -m "first commit"
[master (root-commit) 72b7d54] first commit
 2 files changed, 2 insertions(+)
 create mode 100644 file1.txt
 create mode 100644 file2.txt
1
2
3
4
5

コミット時点のGitの状態を確認

$ git status
On branch master
nothing to commit, working tree clean
1
2
3

コミット直後は、作業ディレクトリ、ステージングエリア、リポジトリすべてに差分がない状態。

コミット時点のディレクトリの状態

gittest/
| -- .git/
|    | -- config
|    | -- description
|    | -- HEAD
|    | -- index <- ★
|    | -- hooks/
|    | -- info/
|    | -- logs/ <- ★
|    |    | -- HEAD 
|    |    ` -- refs
|    |         ` -- heads
|    |              ` -- master
|    | -- objects/ <- ★
|    |    | -- 18/
|    |    |    ` -- 0cf8328022becee9aaa2577a8f84ea2b9f3827
|    |    | -- 72/
|    |    |    ` -- b7d54f5f61e4efc258c07e18bfa5de6cb7235e
|    |    | -- a5/
|    |    |    ` -- bce3fd2565d8f458555a0c6f42d0504a848bd5
|    |    | -- f2/
|    |    |    ` -- e7ebe6858ea413216238e19dfbc5b224e9f7e7
|    |    | -- info/
|    |    ` -- pack/
|    ` -- refs/ <- ★
|         | -- heads/
|         |    `-- master
|         ` -- tags/
| -- file1.txt
` -- file2.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

こちらもわかりやすく、 git commit する前との差分を見てみた。

$ diff -r ./ ../beforeCommit
./.git のみに存在: COMMIT_EDITMSG
バイナリーファイル ./.git/index と../beforeCommit/.git/index は異なります
./.git のみに存在: logs
./.git/objects のみに存在: 72
./.git/objects のみに存在: f2
./.git/refs/heads のみに存在: master
1
2
3
4
5
6
7

Gitオブジェクトを探る

コミットにより、 72/b7d54f5f61e4efc258c07e18bfa5de6cb7235e と f2/e7ebe6858ea413216238e19dfbc5b224e9f7e7 というオブジェクトが増えている。

以前仕入れた知識を生かして、git cat-file コマンドで確認してみる。

commitオブジェクト

まずは、72/b7d54f5f61e4efc258c07e18bfa5de6cb7235e から。ディレクトリ名とファイル名を合わせてハッシュ値を示しているので、合わせたものをコマンドの引数として渡してみる。

$ git cat-file -t 72b7d54f5f61e4efc258c07e18bfa5de6cb7235e
commit
1
2

片方は、commitタイプのオブジェクトであることがわかる。

Gitの内側-Gitオブジェクト(コミットオブジェクト)

$ git cat-file -p 72b7d54f5f61e4efc258c07e18bfa5de6cb7235e
tree f2e7ebe6858ea413216238e19dfbc5b224e9f7e7
author charagaki <charagaki***@***.com> 1560680357 +0100
committer charagaki <charagaki***@***.com> 1560680357 +0100

first commit
1
2
3
4
5
6

中身を見ると、コミットに関する情報を保持していることがわかる。

見慣れない「tree」というものがあるが、この値は、もう一つのオブジェクトのハッシュ値を示しているようである。もう片方については後でみていくのでここでは一旦飛ばす。

いってみれば「コミット」とは、「commit」オブジェクトを作成していることだといえる。

treeオブジェクト

もう片方も見ていく。

$ git cat-file -t f2e7ebe6858ea413216238e19dfbc5b224e9f7e7
tree
1
2

Gitの内側-Gitオブジェクト(ツリーオブジェクト)

$ git cat-file -p f2e7ebe6858ea413216238e19dfbc5b224e9f7e7
100644 blob a5bce3fd2565d8f458555a0c6f42d0504a848bd5    file1.txt
100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827    file2.txt
1
2
3

中身を見ると、ファイル名の情報と、ファイルの実体であるblobオブジェクトを指していることがわかる。

なお、このblobオブジェクトはさきほど、git add した時点で作成されたものである。

さきほどの、コミットオブジェクトの情報と合わせてみると、以下のようになっていることがわかる。

commit:   [72b7d54..]
             | 
             v 
tree  :   [f2e7ebe..] 
          |       |   
          |       | 
          v       v 
blob  :   [a5b..] [180..]
          file1   file2 
1
2
3
4
5
6
7
8
9

[^1] : Git - git-cat-file Documentation

[^2] : Git - git-ls-files Documentation