いつまでたってもgitさんのことががよく分からなくつらい
恥ずかしながら自分、ほぼ毎日なんらかのかたちでgitに触れているのにもかかわらず、いまだによく分かってないです、gitさんのこと・・・
origin/master?あー、リモートリポジトリってやつ?Githubに置かれてるやつ?
インデックス?あー、ステージングとか、あの辺りのやつ?(Sourcetreeさんに頼ってばかりですんません)
ベアリポジトリ?あー、あの、くまーってやつね(なにが
gitさんとちゃんとお付き合いさせていただきたい
きみのとこがちゃんと知りたい!
なのでpro git(日本語版)を読んでみた。
磁器コマンドと配管コマンドとゆうのがある
日々なんとなく使ってるのによく分かってない自分からすると、Gitnの内側あたりをちゃんと理解するべきだと思いました。そのなかで出てくるコマンドの種類がふたつあります。
磁器(Porcelain)コマンド
$ git clone
$ git add
$ git commit
お馴染みですよね。たぶん、磁器のようにどっしりとしてるからそう呼ぶ、と理解。
配管コマンドの集まりが磁器コマンド、というイメージです。
配管(Plumbing)コマンド
$ git hash-object
$ git update-index
$ git write-tree
何やら見慣れぬコマンド・・・
ざっくり言うと、パイプ処理で繋げたりして使用する比較的低水準なコマンドのことを指すもよう。
たとえば、磁器コマンドを使用して
### 1.ファイル作って
$ echo 'aaa' > aaa.txt
### 2~3.インデックスに追加して
$ git add test.txt
### 4~6.コミットする
$ git commit -m 'add aaa.txt'
と同じことを、配管コマンドでしようとすると、以下となります。
コメントの番号は両コマンドが行う処理の内容に対応しています。
### 1.ファイル作って
$ echo 'aaa' > aaa.txt
### 2.blobとして登録して
$ git hash-object -w aaa.txt
72943a16fb2c8f38f9dde202b7a70ccc19c52f34
### 3.インデックスに追加して
$ git update-index --add --cacheinfo 100644 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 aaa.txt
### 4.treeを作って
$ git write-tree
a760aa994bbdd615f7ecb442be6bab636e11eba6
### 5.commitする
$ git commit-tree -m "add aaa.txt" a760aa994bbdd615f7ecb442be6bab636e11eba6
1e87843b08cdcd96216ab42ce56d6e0109c53427
### 6.でもってmasterブランチの参照を更新する
git update-ref refs/heads/master 1e87843b08cdcd96216ab42ce56d6e0109c53427
初見では「???」ですが、gitの内部構造を少し理解すれば「ははーん」となります。
順をおって説明してきますが、まずはgitの内部構造に軽く触れてみます。
gitオブジェクトとオブジェクトデータベース
gitは管理対象のファイルやディレクトリ、それにコミット情報なんかをgitオブジェクト化して扱います。化というのがポイントで、gitはファイルをそのまま登録するわけではありません(後ほど説明します)
また、それらgitオブジェクトを登録する場所をオブジェクトデータベースといいます。
gitオブジェクト
gitが扱うモノのことをgitオブジェクトといいます。
gitオブジェクトは以下の4つです。
blob(big large object)
ファイルです。
ソースコート、画像ファイル、バイナリファイルなど、あらゆるファイルをgitはblobとして扱います。
tree
ディレクトリです。
ただ、ディレクトリを指すのみでなく、コミットするときの単位でもあります。
commit
コミットです。
コミットするtree、作成者、コミット者、コミットメッセージを保持します
tag
タグです。
今回は触れません。
オブジェクトデータベース
オブジェクトデータベースとはgitオブジェクトのデータベースです(そのまま)
具体的にいうと、git init
したあとに作成される.git
ディレクトリ内のobjects
ディレクトリがオブジェクトデータベースです。
では先ほどつらつらとあげた、配管コマンドを使ってファイルをコミットする様子を見ていきましょう。
配管コマンドを使ってコミットしてみる
$ mkdir /git-test && cd /git-test
### ローカルリポジトリを作成
$ git init
Initialized empty Git repository in /git-test/.git/
### .git/objects内のファイルを検索
$ find .git/objects -type f
適当な場所に実験用のローカルリポジトリを作成しておいてください。
リポジトリ作成直後なのでひとつもファイルが登録されていませんね。
ファイルをオブジェクトデータベースに登録する
### 1.ファイル作って
$ echo 'aaa' > aaa.txt
### 2.blobとして登録して
$ git hash-object -w aaa.txt
72943a16fb2c8f38f9dde202b7a70ccc19c52f34
git hash-object
はgitオブジェクトのオブジェクトIDを返します。
オブジェクトIDとは、gitオブジェクトのSHA1ハッシュ値です。gitが扱うリビジョン番号とかと同じノリです。
オプションに-w
を指定すると、対象ファイルをgitオブジェクト化したのち、オブジェクトデータベースに登録します。
このコマンドにより、gitはファイルをgitオブジェクト化します。
gitオブジェクトは、gitオブジェクトの種類(ファイルの場合はblob)とファイルサイズからなるヘッダと、ファイルの内容を連結したもので、そのSHA1ハッシュ値がgitオブジェクトIDです。また、実際にオブジェクトデータベースに登録されるときは圧縮されます。
.git/objects
の内容をリスト表示してみるにはこちらをお試しください。
また、このあたりの詳細についてはオブジェクトストレージが詳しいです。
それでは、現時点でオブジェクトデータベースの中身を覗いてみましょう。
$ find .git/objects -type f
.git/objects/72/943a16fb2c8f38f9dde202b7a70ccc19c52f34
ファイルがひとつ作られていますね。
gitはgitオブジェクトIDの先頭2文字をディレクトリ名として、残りの38文字をファイル名として、git/objects
以下に配置します。
今の状態はファイルをオブジェクトデータベースに登録しただけです。
インデックスに登録する
### 3.インデックスに追加して
$ git update-index --add --cacheinfo 100644 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 aaa.txt
さっきgitオブジェクト化したときに返ったSHA1ハッシュ値をパラメータとして、リポジトリのインデックスに登録します。
新規にインデックスに登録するだけならgit update-index --add aaa.txt
でもいいようです。
インデックスを確認してみましょう。
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: aaa.txt
ステージングされてますね。
コミットする
### 4.treeを作って
$ git write-tree
a760aa994bbdd615f7ecb442be6bab636e11eba6
### 5.commitする
$ git commit-tree -m "add aaa.txt" a760aa994bbdd615f7ecb442be6bab636e11eba6
1e87843b08cdcd96216ab42ce56d6e0109c53427
### 6.でもってmasterブランチの参照を更新する
git update-ref refs/heads/master 1e87843b08cdcd96216ab42ce56d6e0109c53427
まずはgit write-tree
で、現在のインデックスをtree化します。
返るのはtreeのオブジェクトIDです。
オブジェクトデータベースの中身を覗いてみます。
$ find .git/objects -type f
.git/objects/72/943a16fb2c8f38f9dde202b7a70ccc19c52f34
.git/objects/a7/60aa994bbdd615f7ecb442be6bab636e11eba6
さっきの追加したファイルに加え、生成されたtreeが登録されていますね。
さて、それではいよいよ、git commit-tree
コマンドで、このtreeをコミットします。
このコマンドにはコミットメッセージとtreeのオブジェクトIDをパラメータとして渡します。
返る値はコミットオブジェクトのオブジェクトIDです。
コミットオブジェクトはタイムスタンプを含むので、生成したタイミングによりオブジェクトIDが異なります。
最後に、git update-ref
コマンドにより、このコミットオブジェクトのオブジェクトIDをmaster
ブランチとして登録します。
今回は最初のコミットなので指定しませんでしたが、コミットオブジェクトには親であるオブジェクトを指定することができます。これにより、コミットの歴史が表現されるわけですね。
たとえば、このリポジトリにbbb.txt
を追加してそのコミットオブジェクトを追加するときのことを考えてみます。
$ echo 'bbb' > bbb.txt
$ git hash-object -w bbb.txt
f761ec192d9f0dca3329044b96ebdb12839dbff6
$ git update-index --add --cacheinfo 100644 f761ec192d9f0dca3329044b96ebdb12839dbff6 bbb.txt
$ git write-tree
75d27669a0c4e9dd702c71c6ac3307d533493ba5
### 親となるコミットオブジェクトIDを指定してコミットする
$ git commit-tree -m "add bbb.txt" -p 1e87843b08cdcd96216ab42ce56d6e0109c53427 75d27669a0c4e9dd702c71c6ac3307d533493ba5
de5c2b4cf1ce3519102b9cc5a606c3089af02dcc
### 参照を更新
git update-ref refs/heads/master de5c2b4cf1ce3519102b9cc5a606c3089af02dcc
git commit-tree
コマンドの-p
オプションで親コミットオブジェクトIDを指定します。
git log
で今の状況を確認してみましょう。
commit de5c2b4cf1ce3519102b9cc5a606c3089af02dcc (HEAD -> master)
Author: atchy <atchy@example.com>
Date: Sat Jan 16 19:07:21 2021 +0900
add bbb.txt
commit 1e87843b08cdcd96216ab42ce56d6e0109c53427
Author: atchy <atchy@example.com>
Date: Sat Jan 16 18:59:56 2021 +0900
add aaa.txt
ちゃんと歴史が刻まれていますね!
もっとくわしく知りたいあなたへ
pro gitをじっくり読むのもいいですが、
こちらを利用すると、目からウロコがボトボト落ちます。ジョークソフトと表現されていますが、体感で理解できるという意味では冗談抜きかと思いますw
次回予告
今回はgitがどのようにファイルを扱い、コミットの歴史を積み上げていくか、にフォーカスしてみました。
次回はリモートリポジトリまわりについて、お伝えできればと考えています!
続編書きました
いいかげんgitのことをちゃんと知りたいあなたへ(Gitリポジトリ編)
リモートリポジトリ、というよりリポジトリのことについての記事になりました。