Gitを理解するにはGitの中身の知るのが良い、
と天の声が聞こえてきたので学習がてらまとめることにしました。
この記事は個人メモ的な記事です。
基本的に既出情報なのでタイトルをみてピンと来ているかたは読む必要がありません。
※この記事を読むタイミングとしてはGitの基本的な操作と概念をある程度理解した
あとが良いと思います。曖昧な基準ですが。
Gitの2種類のコマンド
Gitの中身はキーバリュー型のデータストアになっています。
GitにはVCS(Version Control System)としてのUIがありますが、
もともとはファイルシステムとしてのUIを重視していました。
前者を 磁器( Porcelain )コマンド
後者を 配管( Plumbing )コマンド
と呼びます。
洗面器やトイレなどユーザーが直接使うものを磁器、
水道管など通常直接使わないものを配管と呼ぶことで区別しようとしたのかな?
StackOverflowでもそのあたりに関するやりとりがありました。
"Porcelain" is the material from which toilets are usually made (and sometimes other fixtures such as washbasins). This is distinct from "plumbing" (the actual pipes and drains), where the porcelain provides a more user-friendly interface to the plumbing.
Git uses this terminology in analogy, to separate the low-level commands that users don't usually need to use directly (the "plumbing") from the more user-friendly high level commands (the "porcelain").
Makes you wonder if Linus was imagining the potential streams of excrement his plumbing would be used to transport. Plumbing for open source code is a dirty job but someone's got to do it.
つまり、こういうことですね!
・・・え?
配管( Plumbing ) コマンド
Plumbing [plˈʌmɪŋ]
ファイルシステムとしてのコマンド。
low level のコマンド。
Gitの内部動作にアクセスします。
一般の git ユーザーがコマンドラインから実行するのではなく、
新たに作られるツールの処理の一部として利用されることを想定しています。
- cat-file
- commit-tree
- count-objects
などがあります。
Plumbing コマンドについては公式 Reference にまとめて記載してあります。
下記ページの Plumbing Commands にコマンドリストがあります。
磁器( Porcelain ) コマンド
Porcelain [pˈɔɚs(ə)lɪn]
VCSとしてのコマンド。
high level のコマンド。
一般のgitユーザーが通常のオペレーションを行うときに利用するコマンドです。
- checkout
- branch
- remote
などがあります。
Gitの中身
Gitの保管データは .git
ディレクトリに保存されます。
git init
直後の内容を確認すると以下のようになっています。
$ git init Initialized empty Git repository in /path/to/project/.git/ $ tree -a . └── .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 │ ├── prepare-commit-msg.sample │ └── update.sample ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags
Gitオブジェクト
Git はSHA1のハッシュキーとGitオブジェクトからなるキーバリューストアです。
Git オブジェクトは 4 種類あります。
- blob
- tree
- commit
- tag
です。
blob オブジェクト
blob オブジェクトはファイルを保持するGitオブジェクトです。
配管コマンドで blob オブジェクトを作成してみます。
まず git hash-object
コマンドでファイルをGitオブジェクトにします。
$ echo hoge > hoge.txt $ git hash-object -w hoge.txt 2262de0c121f22df8e78f5a37d6e114fd322c0b0 $ cd .git/objects $ tree . ├── 22 │ └── 62de0c121f22df8e78f5a37d6e114fd322c0b0 ├── info └── pack
オブジェクトを git cat-file
コマンドで確認します。
# blob オブジェクトの中身を表示 $ git cat-file -p 2262de0c121f22df8e78f5a37d6e114fd322c0b0 hoge # blob オブジェクトの種類を表示 $ git cat-file -t 2262de0c121f22df8e78f5a37d6e114fd322c0b0 blob
blob オブジェクトを作成できていることを確認できました。
ちなみに blob オブジェクトの実体は元ファイルを zlib 圧縮 したファイルです。
tree オブジェクト
tree オブジェクトはディレクトリを保持するGitオブジェクトです。
内部でディレクトリが格納している blob オブジェクトと tree オブジェクトの情報を持ちます。
配管コマンドで tree オブジェクト作成してみます。
まず git update-index
でインデックスを更新します。
# ファイル、ディレクトリを追加 $ echo hige > hige.txt $ mkdir dir $ echo in_dir > in_dir.txt # 追加したファイルの blob オブジェクトを作成 $ git hash-object -w hoge.txt hige.txt dir/in_dir.txt 2262de0c121f22df8e78f5a37d6e114fd322c0b0 9e4f51055b0950ef098e9e2705f2c4d4e9662857 3d2a2dbf2845e04e2f5c87792f6b8b0ebec54846 # インデックスの更新 $ git update-index --add --cacheinfo 100644 2262de0c121f22df8e78f5a37d6e114fd322c0b0 hoge.txt $ git update-index --add --cacheinfo 100644 9e4f51055b0950ef098e9e2705f2c4d4e9662857 hige.txt $ git update-index --add --cacheinfo 100644 3d2a2dbf2845e04e2f5c87792f6b8b0ebec54846 dir/in_dir.txt
id:koseki2 さん作のツール git-object-browser gem で確認すると以下のようにステージングされています
次に git write-tree
で tree オブジェクトを作成します。
$ git write-tree # 作成した tree オブジェクトの内容を表示 $ git cat-file -p 34cfd122c532d9c704f94d2445b76f1ec5e65f1a 040000 tree 62a8ac5b69c8a18c8401fc65422279368b66a7b7 dir 100644 blob 9e4f51055b0950ef098e9e2705f2c4d4e9662857 hige.txt 100644 blob 2262de0c121f22df8e78f5a37d6e114fd322c0b0 hoge.txt # 作成した tree オブジェクトの種類を表示 $ git cat-file -t 34cfd122c532d9c704f94d2445b76f1ec5e65f1a tree # 作成した tree オブジェクトの内容を表示 $ git cat-file -p 62a8ac5b69c8a18c8401fc65422279368b66a7b7 100644 blob 3d2a2dbf2845e04e2f5c87792f6b8b0ebec54846 in_dir.txt # 作成した tree オブジェクトの種類を表示 $ git cat-file -t 62a8ac5b69c8a18c8401fc65422279368b66a7b7 tree
tree オブジェクトが作成されていることを確認できました。
commit オブジェクト
commit オブジェクトは commit 情報を保持するGitオブジェクトです。
配管コマンドで commit オブジェクトを作成してみます。
tree オブジェクトを作成した後の状態からスタートします。
# commit オブジェクトの作成 $ echo 'first commit' | git commit-tree 34cfd122c532d9c704f94d2445b76f1ec5e65f1a # 作成した commit オブジェクトの内容を表示 $ git cat-file -p 25557e6f6472f90b8962374bce49f764594cb5d9 tree 34cfd122c532d9c704f94d2445b76f1ec5e65f1a author name <email> 1475720628 +0900 committer name <email> 1475720628 +0900 first commit # 作成した commit オブジェクトの種類を表示 $ git cat-file -t 25557e6f6472f90b8962374bce49f764594cb5d9 commit
name はコミットした人の git user.name
です。
email はコミットした人の git user.email
です。
hageファイルを追加して commit オブジェクトを追加してみます
$ echo hage > hage.txt $ git hash-object -w hage.txt 6b096d6a1258bf34c76f9ff789b13082bbcb432d $ git update-index --add --cacheinfo 100644 6b096d6a1258bf34c76f9ff789b13082bbcb432d hage.txt $ git cat-file -p 6b096d6a1258bf34c76f9ff789b13082bbcb432d hage $ git cat-file -t 6b096d6a1258bf34c76f9ff789b13082bbcb432d blob $ git write-tree 7726c104e638b81fc13bb8eb18f1374913804f8b $ echo 'second commit' | git commit-tree 7726c104e638b81fc13bb8eb18f1374913804f8b -p 25557e6f6472f90b8962374bce49f764594cb5d9 1601ae1c2217cce390a57721e73d8aaa3d25af31 $ git cat-file -p 1601ae1c2217cce390a57721e73d8aaa3d25af31 tree 7726c104e638b81fc13bb8eb18f1374913804f8b parent 25557e6f6472f90b8962374bce49f764594cb5d9 author name <email> 1475721658 +0900 committer name <email> 1475721658 +0900 second commit $ git cat-file -t 1601ae1c2217cce390a57721e73d8aaa3d25af31 commit
履歴を確認します
$ git log --stat 1601ae1c2217cce390a57721e73d8aaa3d25af31 commit 1601ae1c2217cce390a57721e73d8aaa3d25af31 Author: name <email> Date: Thu Oct 6 21:40:58 2016 +0900 second commit hage.txt | 1 + 1 file changed, 1 insertion(+) commit 25557e6f6472f90b8962374bce49f764594cb5d9 Author: name <email> Date: Thu Oct 6 21:23:48 2016 +0900 first commit dir/in_dir.txt | 1 + hige.txt | 1 + hoge.txt | 1 + 3 files changed, 3 insertions(+)
そして最後のコミットを .git/refs/heads/master
に設定することで git log で履歴を確認できるようになります。
# master ブランチの refs の設定前 $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: dir/in_dir.txt new file: hage.txt new file: hige.txt new file: hoge.txt # まだ master ブランチの refs の設定がない $ git log fatal: your current branch 'master' does not have any commits yet # master ブランチの参照に最終コミットの SHA1 を設定する $ echo "1601ae1c2217cce390a57721e73d8aaa3d25af31" > .git/refs/heads/master # master ブランチの refs の設定後 $ git status On branch master nothing to commit, working tree clean $ git log --pretty=oneline master 1601ae1c2217cce390a57721e73d8aaa3d25af31 second commit 25557e6f6472f90b8962374bce49f764594cb5d9 first commit
ここまでで、 磁器コマンド の git add
と git commit
相当のことをしました。
tag オブジェクト
tag オブジェクトは tag 情報を保持するGitオブジェクトです。
tag には 軽量タグ と アノテートタグ があります。
tag オブジェクトは アノテートタグ の作成時のみ作成されます。
- 軽量タグの作成
# オプションなしでタグを作成 $ git tag test_tag # tagの参照先はHEADが指している commit の SHA1 になる $ cat .git/refs/tags/test_tag 1601ae1c2217cce390a57721e73d8aaa3d25af31
- アノテートタグの作成
# -a オプションでタグを作成 $ git tag -a test_tag2 -m "version tag2" # tagの参照先はHEADが指している tag オブジェクトの SHA1 になる $ cat .git/refs/tags/test_tag2 6e7a983fb7b48d28632bbe93fee7e02c773d2fc5 # 作成した tag オブジェクトの内容を表示 $ git cat-file -p 6e7a983fb7b48d28632bbe93fee7e02c773d2fc5 object 25557e6f6472f90b8962374bce49f764594cb5d9 type commit tag test_tag2 tagger name 1475730743 +0900 version tag2 # 作成した tag オブジェクトの種類を表示 $ git cat-file -t 6e7a983fb7b48d28632bbe93fee7e02c773d2fc5 tag
tag オブジェクトを作成できていることを確認できました。
参照
GitオブジェクトはSHA1ハッシュで識別されますが、
SHA1は人に優しくないので、人にとって分かりやすい名前をつけることができます。
これが 参照 です。
ブランチの内部では参照が作成されています。
$ git checkout -b hoge $ cat .git/refs/heads/hoge 3b629a6ccc1951695c039a465b336ee7dd9a913e # 参照の本体は hoge ブランチの最新のコミットオブジェクトです $ git cat-file -p 3b629a6ccc1951695c039a465b336ee7dd9a913e ⮂ tree 6a73751cba5a86b1ebe10ab2856b68404aab50fd author name <email> 1475745009 +0900 committer name <email> 1475745009 +0900 test
参照を参照するケースもあります。
例えば以下のような場合
$ git branch -a * master remotes/origin/HEAD -> origin/master remotes/origin/master $ cat .git/refs/remotes/origin/HEAD ref: refs/remotes/origin/master
.git/refs/remotes/origin/HEAD
に設定されている ref: refs/remotes/origin/master
は
参照の参照 = シンボリック参照です。
.git配下のファイル、ディレクトリの説明
HEAD ファイル
HEADは現在使用しているブランチの先頭です。
HEAD ファイルは branch名, commit SHA1 が設定されています。
つまり、参照とシンボリック参照双方を扱います。
# log で commit SHA1 を確認 $ git log --oneline 1601ae1 second commit 25557e6 first commit # HEAD ファイルを確認。 master ブランチを参照している $ cat .git/HEAD ref: refs/heads/master # master ブランチの参照を確認。最後のコミットを指している $ cat .git/refs/heads/master 1601ae1c2217cce390a57721e73d8aaa3d25af31 # 最初のコミットにHEADを移動する $ git checkout 25557e6 Note: checking out '25557e6'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new-branch-name> HEAD is now at 25557e6... first commit # HEADへのリファレンスが最初のコミットへの参照になっている $ cat .git/HEAD 25557e6f6472f90b8962374bce49f764594cb5d9
index ファイル
.git/index
ファイルはある時点でのプロジェクトのディレクトリツリー全体の情報を持つバイナリファイルです。
git ls-files --stage
で参照可能です。
$ git ls-files --stage 100644 3d2a2dbf2845e04e2f5c87792f6b8b0ebec54846 0 dir/in_dir.txt 100644 6b096d6a1258bf34c76f9ff789b13082bbcb432d 0 hage.txt 100644 9e4f51055b0950ef098e9e2705f2c4d4e9662857 0 hige.txt 100644 2262de0c121f22df8e78f5a37d6e114fd322c0b0 0 hoge.txt
objects ディレクトリ
.git/objects
ディレクトリには blob, tree, commit, tag のGitオブジェクトが保存されています。
Gitオブジェクトは40文字から成るSHA1ハッシュの2文字目までのディレクトリと残り38文字をファイル名の形式で保存されています。
Gitオブジェクトは git cat-file -p <SHA1>
で参照可能です。
refs ディレクトリ
.git/refs
ディレクトリ配下にはブランチ、タグへの参照が保存されています
$ cd .git/refs $ tree . ├── heads │ └── master ├── remotes │ └── origin │ ├── HEAD │ └── master └── tags ├── test_tag └── test_tag2
.git/refs/heads/master
を確認すると master ブランチの最後の commit オブジェクトへの参照を表す
SHA1が設定されています。
$ cat heads/master 1601ae1c2217cce390a57721e73d8aaa3d25af31
このように、ブランチやタグの最後の commit を参照できるようになっています