あらすじ
この記事は Git Advent Calendar 2013 の 14 日目の記事です。
- 12 日目: @ton1517さん - gitのサブコマンドを自分で作る - ton-tech-ton
- 13 日目: @horimislimeさん - git mergeでコンフリクトが発生するか前もって調べる方法 - Qiita [キータ]
- 14 日目: ここ
- 15 日目: kyanroさん - git - rebase 直後に、自分が修正していたファイルが変更されたかどうかを調べる - Qiita [キータ]
とりあえず 12 日目へつなげておきますね。
最近 Subversion を使うことが多く、めっきり Git を使っていないので、復習として .git/objects
の中身を追ってみた。
参考
- Pro Git 9章 Git - Gitの内側
- アリスとボブのGit入門レッスン Chapter 14
- Dive into .git 日本語版 - SlideShare
- 見えないチカラ: 【翻訳】Gitをボトムアップから理解する
結論
とりあえず add さえしていれば 、ファイルを救える。
以下、 .git/objects
下で起こる事。
git add したとき
$ git add
した瞬間、 add したファイルが.git/objects
下に生成される- Git で決められたヘッダ情報と、ファイル中身から算出される SHA1 でファイル構造が決定する
- 具体的には頭 2 文字でディレクトリを作り、残りの文字でファイル名
SHA1 557db03de997c86a4a028e1ebd3a1ceb225be238
ファイル .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
- add したときに作られるこのファイルは
blob
オブジェクトと呼ばれるblob
オブジェクトは構造など持たない- 構造を管理するのは後述する他のオブジェクト
- このファイルは
$ git cat-file
で中身を確認できる(他のコマンドでもいける)- zlib 圧縮されているだけなので、自力で解凍して中身を確認することもできる
git commit したとき
$ git commit
すると、 2 つ以上のファイルが.git/objects
下に生成されるcommit
オブジェクトとtree
オブジェクト
commit
オブジェクトからは、以下のことがわかるtree
オブジェクト(コミットした時のファイル一覧)- 1つ前の
commit
オブジェクト - merge した場合など、複数の親を持つ場合は親の分だけ前のオブジェクトの情報を持つ
- 作成した人、コミットした人
- コミットログ
$ git cat-file -p 047f8f6f81e483c5c3004cd80d5ecb81a7f64090
tree c3b5990fc81599cd04934d349b2c08a952f16c18 # コミットした時のファイル一覧
parent c11123084bd99af451ddf8eaad378b9a0a5ae695 # 1つ前の commit オブジェクト
author kk_Ataka <kk_ataka@example.com> 1385538273 +0000 # 作成した人
committer kk_Ataka <kk_ataka@example.com> 1385538273 +0000 # コミットした人
add test.txt # コミットログ
tree
オブジェクトからは、以下のことがわかる- Git リポジトリルートからのファイル一覧
- それらの SHA1
blob
オブジェクトかtree
オブジェクトか(ファイルかディレクトリか)- パーミッション
tree
オブジェクトは構造をもたないblob
オブジェクトをまとめてくれる
$ git cat-file -p c3b5990fc81599cd04934d349b2c08a952f16c18
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 README
040000 tree 85a2934a71f7385034f934f9ccc8c746b73d4f44 testdir
tree
オブジェクト(ディレクトリ) は続く限り繰り返される。
git rm したとき
- 何も起こらない
- コミットした時は、いつもどおり
commit
オブジェクトとtree
オブジェクトができる- ただし、
tree
オブジェクトからは rm したファイルが除外されている
- ただし、
ここから先、実際にごちゃごちゃ Git をいじってみて心で理解する。
実際にやってみる
前準備
test 用ディレクトリを作成。
$ mkdir git_test
$ cd git_test/
ここで $ git init
。 .git
ディレクトリが作成され、通常はここにソースなどを置いていく。
$ git init
Initialized empty Git repository in /home/vagrant/git_test/.git/
$ ls -la
total 12
drwxrwxr-x 3 vagrant vagrant 4096 Nov 26 03:08 .
drwx------ 4 vagrant vagrant 4096 Nov 26 03:08 ..
drwxrwxr-x 7 vagrant vagrant 4096 Nov 26 03:08 .git
今回は、さらに深く進んでいく。
$ cd .git
$ ls -la
total 40
drwxrwxr-x 7 vagrant vagrant 4096 Nov 26 03:08 .
drwxrwxr-x 3 vagrant vagrant 4096 Nov 26 03:08 ..
drwxrwxr-x 2 vagrant vagrant 4096 Nov 26 03:08 branches
-rw-rw-r-- 1 vagrant vagrant 92 Nov 26 03:08 config
-rw-rw-r-- 1 vagrant vagrant 73 Nov 26 03:08 description
-rw-rw-r-- 1 vagrant vagrant 23 Nov 26 03:08 HEAD
drwxrwxr-x 2 vagrant vagrant 4096 Nov 26 03:08 hooks
drwxrwxr-x 2 vagrant vagrant 4096 Nov 26 03:08 info
drwxrwxr-x 4 vagrant vagrant 4096 Nov 26 03:08 objects
drwxrwxr-x 4 vagrant vagrant 4096 Nov 26 03:08 refs
各ファイルの役割はこんな感じ。全てはわかっていない。
ファイル | 役割 |
---|---|
branches/ | 新しいバージョンでは使用しない |
config | リポジトリ固有の設定 |
description | GitWebプログラムで使用する |
HEAD | 現在チェックアウトされているブランチ |
index | Gitのステージングエリアの情報を保管 |
hooks/ | フックスクリプト集 |
info/ | .gitignore に記述したくない無視パターンをを保持 |
objects/ | Gitで管理しているファイルの格納場所 |
refs/ | ブランチ内のコミットオブジェクトをさすポインタ |
今回は、この .git
ディレクトリの中でさらに git init
して .git
ディレクトリ内の変更を管理できるようにした。とりあえず全部 add 。
$ git init
Initialized empty Git repository in /home/vagrant/git_test/.git/.git/
$ git add branches/ config description HEAD hooks/ info/ objects/ refs/
$ git commit -m "fitst commit"
まだ変更点はなし。
$ git status
# On branch master
nothing to commit (working directory clean)
git add でファイルを追加
操作手順
Git で管理するファイルは .git/objects
ディレクトリ下で管理される。
何かファイルを作ってみる。
$ echo "Hello World" >> README
$ cat README
Hello World
作っただけでは、 .git
ディレクトリに変化は起きない。
$ cd .git
$ git status
# On branch master
nothing to commit (working directory clean)
ここで $ git add
してみる。
$ git add README
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: README
#
.git/objects
下が何か変わっている。(indexも変わっているが、今はスルー)
$ cd .git
$ git status
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# index
# objects/
ファイルがひとつできてる。
$ find ./objects/ -type f
./objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
これは、今 add した README ファイルの SHA1 ハッシュ。
- はじめの 2 文字でディレクトリを作り、残り 38 文字でファイル名
- なんでこういう分けかたになっているかは調べ切れなかった
$ git cat-file
コマンドでこのハッシュからファイルの中身を確認できる。(中身は $ git show
でも見ることができる)
$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
blob # type
$ git cat-file -s 557db03de997c86a4a028e1ebd3a1ceb225be238
12 # size
$ git cat-file -p 557db03de997c86a4a028e1ebd3a1ceb225be238
Hello World # 中身
また、Git のオブジェクトは zlib 圧縮されているファイルなので、自力で展開することもできる。(ただし、validなフォーマットで圧縮はされていないのでgunzipなどでは展開できないらしい)
$ cd .git/objects/55
$ ls
7db03de997c86a4a028e1ebd3a1ceb225be238
$ irb
irb(main):001:0> require 'zlib'
=> true
irb(main):002:0> f = File.open('7db03de997c86a4a028e1ebd3a1ceb225be238', 'r')
=> #<File:7db03de997c86a4a028e1ebd3a1ceb225be238>
irb(main):003:0> Zlib::Inflate.inflate(f.read)
=> "blob 12\x00Hello World\n"
ヘッダ+ファイルの中身が確認できた。 blob
オブジェクトは 「 "blob" + スペース + ファイルサイズ + "\x00" (NUL 制御コード) + 中身
」というという構造らしいので、これで OK のようだ。
ここで出てきた blob
っていうのは、 Git のオブジェクトの種類の1つ。全部で4つある。
- blob
- tree
- commit
- tag
git commit でファイルコミット
いよいよコミットしてみる。
操作手順
$ git commit -m "README first commit"
[master (root-commit) c111230] README first commit
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 README
一心不乱に .git/objects
へ。(それ以外にもいろいろできてる)
$ cd .git/
$ find ./objects/ -type f
./objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
./objects/67/dc4302383b2715f4e0b8c41840eb05b1873697
./objects/c1/1123084bd99af451ddf8eaad378b9a0a5ae695
何か新しいオブジェクトが 2 個( 67dc
と c111
)増えてる。
中身を見ると、さっき出てきた 4 つのオブジェクトの中の commit
オブジェクトと、 tree
オブジェクトだ。
$ git cat-file -t c11123084bd99af451ddf8eaad378b9a0a5ae695
commit
$ git cat-file -s c11123084bd99af451ddf8eaad378b9a0a5ae695
182
$ git cat-file -p c11123084bd99af451ddf8eaad378b9a0a5ae695
tree 67dc4302383b2715f4e0b8c41840eb05b1873697
author kk_Ataka <kk_ataka@example.com> 1385505619 +0000
committer kk_Ataka <kk_ataka@example.com> 1385505619 +0000
README first commit
この commit
オブジェクトからは以下のようなことがわかる。
tree
オブジェクトの SHA1- 作成した人、コミットした人
- コミットログ
$ git cat-file -t 67dc4302383b2715f4e0b8c41840eb05b1873697
tree
$ git cat-file -s 67dc4302383b2715f4e0b8c41840eb05b1873697
34
$ git cat-file -p 67dc4302383b2715f4e0b8c41840eb05b1873697
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 README
この tree
オブジェクトからは以下のようなことがわかる。
- Git リポジトリルートからのファイル一覧
- SHA1
- 種類
- パーミッション
※ tree
オブジェクトは $ git ls-tree
でも確認できる。
次に、コミットログを増やしたいのと、もうちょっと複雑にしたいので、以下のようにディレクトリを作ってその中にファイルを作る。
testdir
ディレクトリを作る- その下に
test.txt
を作る
$ mkdir testdir
$ echo "test file" >> testdir/test.txt
$ git add testdir/test.txt
$ git commit -m "add test.txt"
[master 047f8f6] add test.txt
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 testdir/test.txt
オブジェクト。
$ find .git/objects/ -type f
.git/objects/04/7f8f6f81e483c5c3004cd80d5ecb81a7f64090
.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
.git/objects/c3/b5990fc81599cd04934d349b2c08a952f16c18
.git/objects/67/dc4302383b2715f4e0b8c41840eb05b1873697
.git/objects/85/a2934a71f7385034f934f9ccc8c746b73d4f44
.git/objects/c1/1123084bd99af451ddf8eaad378b9a0a5ae695
.git/objects/16/b14f5da9e2fcd6f3f38cc9e584cef2f3c90ebe
047f
c3b5
85a2
16b1
が増えた。
中身を確認すると、さっきまでとは趣きが違うところがいくつかある。
$ git cat-file -t 047f8f6f81e483c5c3004cd80d5ecb81a7f64090
commit
$ git cat-file -p 047f8f6f81e483c5c3004cd80d5ecb81a7f64090
tree c3b5990fc81599cd04934d349b2c08a952f16c18
parent c11123084bd99af451ddf8eaad378b9a0a5ae695
author kk_Ataka <kk_ataka@example.com> 1385508273 +0000
committer kk_Ataka <kk_ataka@example.com> 1385508273 +0000
add test.txt
parent
として、このコミットの親がわかるようになってる。
$ git cat-file -t c3b5990fc81599cd04934d349b2c08a952f16c18
tree
$ git cat-file -p c3b5990fc81599cd04934d349b2c08a952f16c18
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 README
040000 tree 85a2934a71f7385034f934f9ccc8c746b73d4f44 testdir
$ git cat-file -t 85a2934a71f7385034f934f9ccc8c746b73d4f44
tree
$ git cat-file -p 85a2934a71f7385034f934f9ccc8c746b73d4f44
100644 blob 16b14f5da9e2fcd6f3f38cc9e584cef2f3c90ebe test.txt
tree
オブジェクトとして、 testdir が追加されてる。
$ git cat-file -t 16b14f5da9e2fcd6f3f38cc9e584cef2f3c90ebe
blob
$ git cat-file -p 16b14f5da9e2fcd6f3f38cc9e584cef2f3c90ebe
test file
blob
はファイルの内容なのでとりあえず特筆する点なし。
親が複数あるとき
merge したときなど、親を複数持つときもあるんじゃなかろうか。
ということで試してみる。まずは test
ブランチを新しく作成し、ファイルを適当に作ってコミット。
$ git checkout -b test
Switched to a new branch 'test'
$ echo "test branch" >> README
$ git add README
$ git commit -m "test branch"
[test 38c9e30] test branch
1 files changed, 1 insertions(+), 0 deletions(-)
リポジトリのグラフを見てみる。(「g」コマンドは $ git log --graph
に色々手を加えたもののエイリアス)
$ git g
* 38c9e30 f60ab37 2013-11-28 kk_Ataka (HEAD, test) test branch
* c2333bd 1a53959 2013-11-28 kk_Ataka (master) master branch
* 047f8f6 c3b5990 2013-11-27 kk_Ataka add test.txt
* c111230 67dc430 2013-11-27 kk_Ataka README first commit
では、 master
ブランチに戻り、 master
ブランチに test
ブランチをマージ。
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff test
Merge made by recursive.
README | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
改めてグラフ。現在の master
ブランチ(= b870b7
)はさっきまで master
だったもの(= c2333
)と、 test
ブランチのもの(= 38c9e3
)が親になっているように見える。
$ git g
* b870b73 f60ab37 2013-11-28 kk_Ataka (HEAD, master) Merge branch 'test'
|\
| * 38c9e30 f60ab37 2013-11-28 kk_Ataka (test) test branch
|/
* c2333bd 1a53959 2013-11-28 kk_Ataka master branch
* 047f8f6 c3b5990 2013-11-27 kk_Ataka add test.txt
* c111230 67dc430 2013-11-27 kk_Ataka README first commit
ということで確認。まずは今の場所の commit
オブジェクトのハッシュを改めて取得。
$ git rev-parse HEAD
b870b7313d2fcfda308204b12d5534c5ccac95cb
commit
オブジェクトの中身を見てみる。
$ git cat-file -p b870b7313d2fcfda30
tree f60ab375c7a4408527f814167a0dc9285f1cbcca
parent c2333bd833a82784ca06a960890a739d43f32c5e
parent 38c9e304d897e71c479bf78fe4def6bcb3ad1b72
author kk_Ataka <kk_ataka@example.com> 1385633710 +0000
committer kk_Ataka <kk_ataka@example.com> 1385633710 +0000
Merge branch 'test'
2つある!
リポジトリからファイルを消したとき
消したらどうなるんだろう。
まずは適当なファイルを追加する。
$ echo "hello" >> hello.txt
$ git add hello.txt
$ find .git/objects/ -type f
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
add したらひとつできてる。 commit したら commit
オブジェクトと tree
オブジェクトのふたつができて、計みっつになるはず。
$ git commit -m "first commit"
(略)
$ find .git/objects/ -type f
.git/objects/aa/a96ced2d9a1c8e72c56b253a0e2fe78393feb7 # tree
.git/objects/30/5fd379be250870d1e55584241637dacb44ec82 # commit
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a # blob hello.txt
$ git status
# On branch master
nothing to commit (working directory clean)
なってる。
次に、ファイルを削除。
$ git rm hello.txt
rm 'hello.txt'
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: hello.txt
#
$ find .git/objects/ -type f
.git/objects/aa/a96ced2d9a1c8e72c56b253a0e2fe78393feb7
.git/objects/30/5fd379be250870d1e55584241637dacb44ec82
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
削除しただけだと変わらないか。
コミットするとどうか。
$ git commit -m "delete hello.txt"
(略)
$ find .git/objects/ -type f
.git/objects/aa/a96ced2d9a1c8e72c56b253a0e2fe78393feb7 # tree
.git/objects/30/5fd379be250870d1e55584241637dacb44ec82 # commit
.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a # blob hello.txt
.git/objects/72/e0a285935e33b62877ecb809fd4b6fac6d416d
新たにふたつできてる。 commit
オブジェクトと tree
オブジェクトか。
$ git cat-file -t 72e0
commit
$ git cat-file -p 72e0
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent 305fd379be250870d1e55584241637dacb44ec82
author kk_Ataka <kk_ataka@example.com> 1386303674 +0000
committer kk_Ataka <kk_ataka@example.com> 1386303674 +0000
delete hello.txt
$ git cat-file -t 4b82
tree
$ git cat-file -p 4b82
$
あ、 tree
オブジェクトのリストから消えるだけか。
というか、よく考えたら git rm
したからってオブジェクトが消えたらチェックアウトで戻せなくなるな。
課題
まだよくわかってない。
.git/index
がどうなっているのか。.git/objects
下の info と pack$ git gc
コマンドを使って整理した場合に複数のオブジェクトのファイルを pack ファイルに圧縮する
- オブジェクトが勝手に消えるときはあるのか。
- detached HEAD 状態になったコミットは gc とか走ると消えそうなんだけど…
メモ
今回使った色々なコマンド。
cat-file
$ git cat-file -p HEAD # HEAD = commit オブジェクト
tree c3b5990fc81599cd04934d349b2c08a952f16c18
parent c11123084bd99af451ddf8eaad378b9a0a5ae695
author kk_Ataka <kk_ataka@example.com> 1385508273 +0000
committer kk_Ataka <kk_ataka@example.com> 1385508273 +0000
add test.txt
$ git cat-file -p c3b5990 # tree オブジェクト
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 README
040000 tree 85a2934a71f7385034f934f9ccc8c746b73d4f44 testdir
hash-object
$ git hash-object README # blob オブジェクト
557db03de997c86a4a028e1ebd3a1ceb225be238
rev-parse
$ git rev-parse HEAD
047f8f6f81e483c5c3004cd80d5ecb81a7f64090
ls-tree
$ git ls-tree HEAD
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 README
040000 tree 85a2934a71f7385034f934f9ccc8c746b73d4f44 testdir
$ git ls-tree 85a293
100644 blob 16b14f5da9e2fcd6f3f38cc9e584cef2f3c90ebe test.txt
ls-files
$ git ls-files --stage
100644 d7f3c505ac2e28aaac5fa9210adb1115b8c57d72 0 README
100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0 new.txt
100644 16b14f5da9e2fcd6f3f38cc9e584cef2f3c90ebe 0 testdir/test.txt
show
$ git show d7f3c505ac2e28a
Hello World
master branch
test branch
$ git show HEAD
commit b870b7313d2fcfda308204b12d5534c5ccac95cb
Merge: c2333bd 38c9e30
Author: kk_Ataka <kk_ataka@example.com>
Date: Thu Nov 28 20:15:10 2013 +0000
Merge branch 'test'