はじめに
読みました。
エンジニアのためのGitの教科書[上級編] Git内部の仕組みを理解する
- 作者: 河村聖悟
- 出版社/メーカー: 翔泳社
- 発売日: 2016/01/19
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
もうgitがないと仕事にならない生活をしています。仕事のすべてはgitに預けた、ここにないものは何もない、というスタイルで仕事しているのでチームメンバーに対しての情報開示が非常に楽ですし、差分管理、つまりdiffが容易に確認できて、save / resetが簡単にできるが気持ちよすぎて、何でもgit以下においちゃいますね。でもバイナリはダメですよ。
で、今でこそ一通りのコマンドは使いこなせるようになりましたが、まだまだ周囲にはgitの使い方がわかってないひとも多く、チーム内ではわりとgitがわかるほうになってきました。
せっかくなのでもう一歩深く学んでgitマスターになって、勉強会開こう!ということで、何事も知るには内部構造。.git以下の動きを解説してくれるこの本を読みました。
この本、かなり良いです。ていねいに、各コマンドで何が生成され、中身がどうなっているのかを、実際に動かしながら示してくれます。基本オブジェクトの解説から入り、各コマンドの内部の動き、最後には低レベルのgitコマンドを使ってのgit commit / add のshell実装までします。本書の内容を実際に動かし、内容を理解できたら1段階レベルがあがるでしょう。
とはいえ、動かしながらだと内容も大きくなるので2回に分けて実践していきます。
git init
git initしたら何が起きるの?
適当なディレクトリを作って試してみましょう。
take@MacBook-Air ~/g/git_study> mkdir git_init_test take@MacBook-Air ~/g/git_study> cd git_init_test/ take@MacBook-Air ~/g/g/git_init_test> git init Initialized empty Git repository in /Users/take/github/git_study/git_init_test/.git/ take@MacBook-Air ~/g/g/git_init_test> tree .git/ .git/ ├── HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.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 8 directories, 14 files
HEAD,config,descriptionの3ファイルに、hooks,info,objects,refsのディレクトリが作成されます。
HEAD
headファイルは現在のブランチを示すシンボリック参照情報を保存しています。
take@MacBook-Air ~/g/g/git_init_test> cat .git/HEAD
ref: refs/heads/master
config
git configで設定できる、gitのconfigファイルです。これはリポジトリ固有設定になります。システム固有設定(–system)は/etc/gitconfig、ユーザ固有設定(–global)は~/.gitconfig になります。
take@MacBook-Air ~/g/g/git_init_test> cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true
詳細はman git-configで見るとして、ここではconfig内容の解説は行いません。
description
take@MacBook-Air ~/g/g/git_init_test> cat .git/description Unnamed repository; edit this file 'description' to name the repository.
リポジトリのdescriptionです。githubではここの値は読まないようですが、gitlabはどうなんだろうか。会社行ったら確認します。
hooks
ここ以下はgitが用意しているhookのsampleが置かれています。これらの拡張子を外して編集すれば動かすことができます。デフォルトのコミットメッセージを用意したり、コミット規約を守らせるためにshellでスクリプトが書けます。
take@MacBook-Air ~/g/g/git_init_test> ls .git/hooks/ applypatch-msg.sample post-update.sample pre-commit.sample pre-rebase.sample prepare-commit-msg.sample commit-msg.sample pre-applypatch.sample pre-push.sample pre-receive.sample update.sample
info/exclude
take@MacBook-Air ~/g/g/git_init_test> 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] # *~
除外ファイルパターンを記載することができます。なお.gitignoreは除外ディレクトリパターンと記載していますが。。。.gitignoreでも除外ファイル記載可能なので、大きな違いは除外パターンを共有したいかどうか、ではないでしょうか。自分だけ除外したい場合はここのinfo/excludeに記載するといいでしょう。(.gitはgitの管理対象ではないでしょうから)
参考:Git | excludeファイルにローカル環境だけ無視したいファイルを登録 - Tbpgr Blog
object
gitはオブジェクトという単位で情報を持ちます。objectには
の3種類があります。
refs
ブランチの参照とタグオブジェクトを格納する場所です。
git addしてみる
では、git addしたときどのように変化するか見てみましょう。
take@MacBook-Air ~/g/g/git_init_test> echo "git test" > test.txt take@MacBook-Air ~/g/g/git_init_test> git add test.txt
実行前の.gitディレクトリをコピーしておいたので、diffをとってみます。
take@MacBook-Air ~/g/g/git_init_test> diff -r .git_before/ .git Only in .git: index Only in .git/objects: f6
indexファイルと、objectが増えたようですね。
index
indexファイルはindex情報、どのファイルが追跡状態にあるかを保存してます。しかし、バイナリファイルなので中身を読むことはできません。
stageしているファイルは以下で確認できます。git statusでも確認できますね。
take@MacBook-Air ~/g/g/git_init_test> git ls-files --stage 100644 f6edd6e7a290f009aa685d3acd3153b495a69ea8 0 test.txt
brob object
brobオブジェクトが作成されました。git cat-fileで見てみましょう。
take@MacBook-Air ~/g/g/git_init_test> git cat-file -t f6edd6e7a290f009aa685d3acd3153b495a69ea8 blob take@MacBook-Air ~/g/g/git_init_test> git cat-file -p f6edd6e7a290f009aa685d3acd3153b495a69ea8 git test
この16進数の数字はSHA1のhashです。
take@MacBook-Air ~/g/g/git_init_test> openssl sha1 test.txt SHA1(test.txt)= 06aaaf302f1370f12298c6cbf7e436c8a6bdef05
確認できましたね。brobの内容のハッシュがobject名になっています。かつ、先頭2文字でディレクトリを分けていますね。名前空間をわけて処理をいい感じにしてるんでしょうね。
take@MacBook-Air ~/g/g/git_init_test> tree .git/objects/ .git/objects/ ├── f6 │ └── edd6e7a290f009aa685d3acd3153b495a69ea8 ├── info └── pack 3 directories, 1 file
git commitしてみる
さて、次はcommitをしてみましょう。同じように現在の.gitをコピーしておいて、diffをとってみようと思います。
take@MacBook-Air ~/g/g/git_init_test> git commit -m "test commit" [master (root-commit) 379e87a] test commit 1 file changed, 1 insertion(+) create mode 100644 test.txt take@MacBook-Air ~/g/g/git_init_test> diff -r .git_before/ .git Only in .git: COMMIT_EDITMSG Binary files .git_before/index and .git/index differ Only in .git: logs Only in .git/objects: 37 Only in .git/objects: 5e Only in .git/refs/heads: master
indexファイルが変わり、COMMIT_EDITMSG、logs、そしてobjectが2つ増えました。refs以下にheadsが増えました。
COMMIT_EDITMSG
直前のcommitメッセージが保存されています。–amendで直前のみ変更されるのはこのためなんでしょうね。
take@MacBook-Air ~/g/g/git_init_test> cat .git/COMMIT_EDITMSG test commit
logs
HEADの参照情報です。直前のコミットがないので0000… になっていますね。
take@MacBook-Air ~/g/g/git_init_test> cat .git/logs/HEAD 0000000000000000000000000000000000000000 379e87a44b39250d5717de286499cd1c52f9d623 kondo takeshi <take.she12@gmail.com> 1502256942 +0900 commit (initial): test commit
reflogで同等の情報を得られます。
take@MacBook-Air ~/g/g/git_init_test> git reflog 379e87a HEAD@{0}: commit (initial): test commit
commit object
ではHEADが参照している379e87aのオブジェクトを見てみましょう。
take@MacBook-Air ~/g/g/git_init_test> git cat-file -t 379e87 commit take@MacBook-Air ~/g/g/git_init_test> git cat-file -p 379e87 tree 5ed0c0c091b633152a962cfd92f538204ae2847c author kondo takeshi <take.she12@gmail.com> 1502256942 +0900 committer kondo takeshi <take.she12@gmail.com> 1502256942 +0900 test commit
これがメタデータを保存しているcommitオブジェクトですね。そしてcommitオブジェクトはtreeオブジェクトを参照しています。
なお、これもコミット内容のsha1ハッシュなはずだが、単純に内容をhashかけただけではだめだった。
これはcommitという文字と、commitオブジェクトの中身のバイト数、そしてコミットオブジェクトの中身を合わせているらしいが、なぜか再現できず。。。
参考:Gitのコミットハッシュ値は何を元にどうやって生成されているのか - Mercari Engineering Blog
tree object
take@MacBook-Air ~/g/g/git_init_test> git cat-file -t 5ed0c0 tree take@MacBook-Air ~/g/g/git_init_test> git cat-file -p 5ed0c0 100644 blob f6edd6e7a290f009aa685d3acd3153b495a69ea8 test.txt
treeオブジェクトはファイルモードと、blobオブジェクトの参照を持っています。
おわりに
エンジニアのためのgit教科書の初級編相当をなぞってみました。.git以下にあるファイルの種類、3つのオブジェクトの内容と関係、git add/commit時の動きを学びました。
次回はより多くのコマンドについて挙動を追いかけてみます。