「.git」ディレクトリの中身を手を動かしながら理解してみる
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のオブジェクトの概要が掴めるはず、、!
以上。