yoshiislandblog.net
元営業の駆け出しアラサーSEが、休日にMACと戯れた際の殴り書きメモ。日々勉強。日々進歩。
GitHub-Mark-120px-plus

「.git」ディレクトリの中身を手を動かしながら理解してみる

2023-05-24

git initと打つと勝手にできる.gitディレクトリの中身について、今までconfigファイルくらいしかじっくり見たことが無かったが、
この機会に手を動かしながら漁ってみると、gitの仕組みがざっくりわかってきたので、手を動かした内容を記事に残す

「.git」ディレクトリ配下のファイル

「.git」ディレクトリの中には、以下のようなディレクトリやファイルが格納されている

% tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── main
│           └── yoshi
├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 31
│   │   └── d54c7003b96e13fdf00844f5bf170d1e94138e
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e3
│   │   └── bffa46a643f03890b10ca81b9c7cb3868bc4de
│   ├── e4
│   │   └── 2f4cee51430bbf540201e935825c38f9045fae
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   ├── main
    │   └── yoshi
    └── tags

17 directories, 30 files
%

各ディレクトリ/ファイルの説明は以下の通り

ファイルorディレクトリ名 説明
COMMIT_EDITMSG 最新のコミットメッセージが記載されている一時ファイル
HEAD HEAD(今自分が作業しているポイント)がどこにあるか書かれているファイル
このファイルを直接変更すればHEADの位置を変えられる(git checkoutコマンドと同じ操作になる)
cofig gitの設定が書かれるファイル
description リポジトリの説明が書かれるファイル
hooks/ 便利スクリプトサンプルが色々入っているディレクトリ
参考:Git-のカスタマイズ-Git-フック
index addするとできるファイル
中身は「git ls-files –stage」コマンドで確認できる
info/exclude addしたくないファイルを記載しておく
「gitignore」ファイルはチームで使うものだが、「info/exclude」は個人用に使える
objects/ オブジェクトが格納される最も重要なディレクトリ
init段階では何もない
オブジェクトについては詳細後述する
objects/info/
objects/pack/
よくわからんディレクトリ
公式ページ を読んでも、「その中に pack と info というサブディレクトリを作ります。しかし、ファイルはひとつも作られません。」と書かれていて、ようわからん、、
refs/heads/ ブランチが切られると配下にブランチ名のファイルができる
それぞれのブランチが参照しているcommitオブジェクトのハッシュ値がポインタとして書かれている
refs/tags/ タグが切られると配下にタグ名のファイルができる
それぞれのタグが参照しているtagオブジェクトの値が書かれる

オブジェクトとは

「.git/objects」配下には、例えば以下のようにオブジェクトが格納される
以下の例だと、6つのオブジェクトが格納されている

├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 31
│   │   └── d54c7003b96e13fdf00844f5bf170d1e94138e
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e3
│   │   └── bffa46a643f03890b10ca81b9c7cb3868bc4de
│   ├── e4
│   │   └── 2f4cee51430bbf540201e935825c38f9045fae
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f

オブジェクトそれぞれには、SHA-1ハッシュ値が割り当てられていて、最初の2文字がサブディレクトリの名前で、残りの文字列がファイル名になっている。
上のディレクトリの例だと、最初のオブジェクトのハッシュ値は「207ff1be3cbd9e084569b0efe5e9b46533416524」という事になる


gitのオブジェクトには4種類のタイプがある

オブジェクトのタイプ 説明 中身例
blobオブジェクト その時点のファイルの中身がそのまま書かれている(変更点とかではなく全て)
hoge
fuga
treeオブジェクト ディレクトリ構造が書かれている ※ testdir配下にtestfile.txtがあることを表している
040000 tree dc4727f7a2b2b4d892ef4c01b6cdc5a2875a6678	testdir
100644 blob e42f4cee51430bbf540201e935825c38f9045fae	testfile.txt
commitオブジェクト コミット情報が書かれている
tree e3bffa46a643f03890b10ca81b9c7cb3868bc4de
parent 6761094775f18dca2404cef0ac038c39b43146a5
author yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684393696 +0900
committer yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684393696 +0900

yoshi commit
tagオブジェクト tag情報が書かれている
object 6761094775f18dca2404cef0ac038c39b43146a5
type commit
tag test-tag
tagger yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684850224 +0900

test tag

「objects」ディレクトリ配下に作られるファイルは文字化けしていて直接読めないので、以下コマンドで確認する

オブジェクトのタイプを確認するコマンド

 
% git cat-file -t 【オブジェクトのハッシュ値】

オブジェクトの中身(コンテンツ)を確認するコマンド

 
% git cat-file -p	 【オブジェクトのハッシュ値】

手を動かしながらオブジェクトを理解してみる

読むだけではようわからんと思うので、手を動かしながらそれぞれのファイルの中身を確認してみる


準備として空の作業用ディレクトリを作成

 
% mkdir work
% cd work
% ls -liah
total 0
29331330 drwxr-xr-x  2 yoshi  staff    64B  5 14 07:46 ./
29204625 drwxr-xr-x  5 yoshi  staff   160B  5 14 07:46 ../
% 

initしたとき

まずは「git init」してみる

 
% git init
Initialized empty Git repository in /Users/yoshi/hogehoge/work/.git/
% 

「.git」ディレクトリができた

 
% ls -liah
total 0
29331330 drwxr-xr-x  3 yoshi  staff    96B  5 14 07:47 ./
29204625 drwxr-xr-x  5 yoshi  staff   160B  5 14 07:47 ../
29331361 drwxr-xr-x  9 yoshi  staff   288B  5 14 07:47 .git/
% 
 
% tree .git
.git
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-merge-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   ├── prepare-commit-msg.sample
│   ├── push-to-checkout.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

8 directories, 17 files
% 

「.git」配下のファイルを見てみる

「HEAD」には、現在のHEADの位置が書かれている

 
% cat .git/HEAD
ref: refs/heads/main
% 

「config」はgitの設定が書かれている

 
% cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
	precomposeunicode = true
% 

「description」ファイルにはリポジトリの説明が書かれるが、デフォルトでは何も書かれていない

 
% cat .git/description
Unnamed repository; edit this file 'description' to name the repository.
% 

「hooks」ディレクトリ配下には、便利そうなスクリプトたちが入っている

 
% cat .git/hooks/applypatch-msg.sample
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.  The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".

. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
% 

「info/exclude」ファイルにはaddから除外したいファイル名を記載する
通常「.git」と同階層に作成する「.gitignore」はチームで共有する除外ファイルで、「info/exclude」は個人で使う除外ファイル

 
% cat .git/info/exclude
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
% 

「objects/info/」と「objects/pack/」ディレクトリは、配下に何も格納されておらず、よくわからん

 
% ls -liah .git/objects/info
total 0
29331394 drwxr-xr-x  2 yoshi  staff    64B  5 14 07:47 ./
29331392 drwxr-xr-x  4 yoshi  staff   128B  5 14 07:47 ../
% 
 
% ls -liah .git/objects/pack
total 0
29331393 drwxr-xr-x  2 yoshi  staff    64B  5 14 07:47 ./
29331392 drwxr-xr-x  4 yoshi  staff   128B  5 14 07:47 ../
% 

「refs/heads/」ディレクトリ配下には、現時点では何もない

 
% ls -liah .git/refs/heads
total 0
29331380 drwxr-xr-x  2 yoshi  staff    64B  5 14 07:47 ./
29331379 drwxr-xr-x  4 yoshi  staff   128B  5 14 07:47 ../
% 

「tags」ディレクトリ配下にも現時点では何もない

% ls -liah .git/refs/tags
total 0
29331381 drwxr-xr-x  2 yoshi  staff    64B  5 14 07:47 ./
29331379 drwxr-xr-x  4 yoshi  staff   128B  5 14 07:47 ../
% 

addしたとき

適当なファイルをつくってみる

 
% echo hogehoge > testfile.txt
%
% ls -liah
total 8
29331330 drwxr-xr-x  4 yoshi  staff   128B  5 14 14:28 ./
29204625 drwxr-xr-x  5 yoshi  staff   160B  5 14 14:28 ../
29331361 drwxr-xr-x  9 yoshi  staff   288B  5 14 14:28 .git/
29344119 -rw-r--r--  1 yoshi  staff     9B  5 14 14:28 testfile.txt
% 
% cat testfile.txt
hogehoge
% 

ファイル作っただけでは当然変わらない

 
% tree .git
.git
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

8 directories, 17 files
% 

「git add」してみる

 
% git add testfile.txt

何やらファイルができた(indexとobjects/配下)ので、できたファイルを確認してみる

 
% tree .git
.git
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── objects
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

9 directories, 19 files
% 

まずは「index」だが、そのまま確認すると文字化けしている

 
% cat .git/index
DIRCd`q�17o�d`q�17o���w���	�\(�����
                                        ӶV(d��_
                                               testfile.txt�����!�]Tq�hk��Z�E�%
% 

「index」の中身を読むコマンドで確認してみると、現在ステージングされているオブジェクトのハッシュ値などが書かれている

 
% git ls-files --stage
100644 e9bc11025c28829eedf6d30cd3b65628648cad5f 0	testfile.txt
% 

「objects」配下にはまさにステージングされているオブジェクトができているが、やはり文字化けして直接確認はできない

 
% cat .git/objects/e9/bc11025c28829eedf6d30cd3b65628648cad5f
xK��OR�d��OOa.-�I%
%

オブジェクトの中身を見るコマンドで確認してみると、「t」オプションで、オブジェクトタイプが「blob」オブジェクトであること、pオプションで、blobオブジェクトの中身も確認できる

 
% git cat-file -t e9bc11025c28829eedf6d30cd3b65628648cad5f
blob
%
% git cat-file -p e9bc11025c28829eedf6d30cd3b65628648cad5f
hogehoge
%

commitしたとき

次に「git commit」してみる

commit実行

 
% git commit -m "first commit"
[main (root-commit) 6761094] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 testfile.txt
%

また、いくつかファイルができている

 
% tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── main
├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── main
    └── tags

14 directories, 25 files
%

「COMMIT_EDITMSG」ファイルにはコミットメッセージが書かれているが、あくまで一次ファイルでこちらのファイルの中身を変更してもコミットメッセージは変わらない

 
% cat .git/COMMIT_EDITMSG
first commit
%

「logs/HEAD」ファイルには、「git log」コマンドで出てくる内容が格納されているが、こちらもあくまで一次ファイルでこちらのファイルの中身を変更してもログは変わらない

 
% cat .git/logs/HEAD
0000000000000000000000000000000000000000 6761094775f18dca2404cef0ac038c39b43146a5 yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684391713 +0900	commit (initial): first commit
% 

「logs/refs/heads/」ディレクトリ配下には、ブランチごとにファイルができて、ブランチごとのログが格納されている

 
% cat .git/logs/refs/heads/main
0000000000000000000000000000000000000000 6761094775f18dca2404cef0ac038c39b43146a5 yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684391713 +0900	commit (initial): first commit
%

参考まで、「git log」コマンドの出力結果は以下

 
% git log
commit 6761094775f18dca2404cef0ac038c39b43146a5 (HEAD -> main)
Author: yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com>
Date:   Thu May 18 15:35:13 2023 +0900

    first commit
% 

「objects/」ディレクトリ配下を確認すると、「tree」オブジェクトと「commit」オブジェクトが新たにできている

 
% git cat-file -t 207ff1be3cbd9e084569b0efe5e9b46533416524
tree
% 
% git cat-file -p 207ff1be3cbd9e084569b0efe5e9b46533416524
100644 blob e9bc11025c28829eedf6d30cd3b65628648cad5f	testfile.txt
% 
 
% git cat-file -t 6761094775f18dca2404cef0ac038c39b43146a5
commit
% 
% git cat-file -p 6761094775f18dca2404cef0ac038c39b43146a5
tree 207ff1be3cbd9e084569b0efe5e9b46533416524
author yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684391713 +0900
committer yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684391713 +0900

first commit
%

「/refs/heads/」ディレクトリ配下には、最後にコミットされたコミットオブジェクトのハッシュ値が書かれている

 
% cat .git/refs/heads/main
6761094775f18dca2404cef0ac038c39b43146a5
%

branchを新しく作ったとき

今まで「main」ブランチのみだったため、ブランチを新しく作ってみる

 
% git branch yoshi
% 

いくつかファイルが増えている

 
% tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── main
│           └── yoshi
├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   ├── main
    │   └── yoshi
    └── tags

14 directories, 27 files
%

これと

 
% cat .git/logs/refs/heads/yoshi
0000000000000000000000000000000000000000 6761094775f18dca2404cef0ac038c39b43146a5 yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684392777 +0900	branch: Created from main
% 

これ

% cat .git/refs/heads/yoshi
6761094775f18dca2404cef0ac038c39b43146a5
% 

checkoutしたとき

作成したyoshiブランチにチェックアウトしてみる

 
% git checkout yoshi
Switched to branch 'yoshi'
%

「tree」コマンドで見た結果は変わらない

 
% tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── main
│           └── yoshi
├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   ├── main
    │   └── yoshi
    └── tags

14 directories, 27 files
% 

「HEAD」ファイルの中身を確認すると、mainからyoshiにポインタが変わっている

 
% cat .git/HEAD
ref: refs/heads/yoshi
%

新ブランチでaddしたとき

新しく作ったyoshiブランチでaddしてみる


このファイルを

 
% cat testfile.txt
hogehoge
% 

こう書き換える

 
% cat testfile.txt
hoge
fuga
% 

diffの結果

 
% git diff
diff --git a/testfile.txt b/testfile.txt
index e9bc110..e42f4ce 100644
--- a/testfile.txt
+++ b/testfile.txt
@@ -1 +1,2 @@
-hogehoge
+hoge
+fuga
% 

「git add」してみる

% git add testfile.txt
% 

addした後のtreeコマンド出力結果

 
% tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── main
│           └── yoshi
├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e4
│   │   └── 2f4cee51430bbf540201e935825c38f9045fae
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   ├── main
    │   └── yoshi
    └── tags

15 directories, 28 files
%

indexのポインタ変わっている

 
% git ls-files --stage
100644 e42f4cee51430bbf540201e935825c38f9045fae 0	testfile.txt
%

今回新たにできたblobオブジェクトファイルには、ファイルの変更差分ではなく、変更後のファイルの中身がそのまま書かれる

 
% git cat-file -t e42f4cee51430bbf540201e935825c38f9045fae
blob
%
% git cat-file -p e42f4cee51430bbf540201e935825c38f9045fae
hoge
fuga
%

新ブランチでcommitしたとき

次は、yoshiブランチでcommitしてみる

 
% git commit -m "yoshi commit"
[yoshi 31d54c7] yoshi commit
 1 file changed, 2 insertions(+), 1 deletion(-)
%

commitした後のtreeコマンド出力結果

 
% tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
(略)
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── main
│           └── yoshi
├── objects
│   ├── 20
│   │   └── 7ff1be3cbd9e084569b0efe5e9b46533416524
│   ├── 31
│   │   └── d54c7003b96e13fdf00844f5bf170d1e94138e
│   ├── 67
│   │   └── 61094775f18dca2404cef0ac038c39b43146a5
│   ├── e3
│   │   └── bffa46a643f03890b10ca81b9c7cb3868bc4de
│   ├── e4
│   │   └── 2f4cee51430bbf540201e935825c38f9045fae
│   ├── e9
│   │   └── bc11025c28829eedf6d30cd3b65628648cad5f
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   ├── main
    │   └── yoshi
    └── tags

17 directories, 30 files
%

コミットメッセージが更新されている

 
% cat .git/COMMIT_EDITMSG
yoshi commit
%

ログも変わっている

 
% cat .git/logs/refs/heads/yoshi
0000000000000000000000000000000000000000 6761094775f18dca2404cef0ac038c39b43146a5 yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684392777 +0900	branch: Created from main
6761094775f18dca2404cef0ac038c39b43146a5 31d54c7003b96e13fdf00844f5bf170d1e94138e yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684393696 +0900	commit: yoshi commit
%

「/refs/heads/yoshi」の中身は、新たなコミットオブジェクトのハッシュ値になった

 
% cat .git/refs/heads/yoshi
31d54c7003b96e13fdf00844f5bf170d1e94138e
%

「objects」ディレクトリ配下には、commitオブジェクトとtreeオブジェクトができている

 
% git cat-file -t 31d54c7003b96e13fdf00844f5bf170d1e94138e
commit
%
% git cat-file -p 31d54c7003b96e13fdf00844f5bf170d1e94138e
tree e3bffa46a643f03890b10ca81b9c7cb3868bc4de
parent 6761094775f18dca2404cef0ac038c39b43146a5
author yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684393696 +0900
committer yoshi-island <xxxxxx+yoshi-island@users.noreply.github.com> 1684393696 +0900

yoshi commit
%
% git cat-file -t e3bffa46a643f03890b10ca81b9c7cb3868bc4de
tree
%
% git cat-file -p e3bffa46a643f03890b10ca81b9c7cb3868bc4de
100644 blob e42f4cee51430bbf540201e935825c38f9045fae	testfile.txt
%

ここまで触ると何となくGitのオブジェクトの概要が掴めるはず、、!

以上。


参考:10.2 Gitの内側 – Gitオブジェクト