LoginSignup
6
1

More than 3 years have passed since last update.

いいかげんgitのことをちゃんと知りたいあなたへ(Gitオブジェクト編)

Last updated at Posted at 2021-01-16

いつまでたっても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リポジトリ編)

リモートリポジトリ、というよりリポジトリのことについての記事になりました。

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1