マージコミットと通常のコミットのただ一つの違い

タイトルは先日、会社の先輩に突然出題されたもの。その時は答えられず教えてもらった。

コミットオブジェクトとは

Gitによって記録されるのは、SHA-1でハッシュ化されたものにすぎない。Gitの内部では、ファイルのコンテンツをハッシュ化したblobオブジェクトと、それを指すことでディレクトリ名やファイル名を記録できるtreeオブジェクトが生成される。このtreeオブジェクトを指すものがコミットオブジェクトであり、コミットオブジェクトを指定すればtreeオブジェクトがわかり、そこを見ることでblobを取り出せる。
しかし、それだけだとコミットの順番がわからない。そこでコミットオブジェクトは、生成時に直前のコミットオブジェクトのSHA-1ハッシュ値を指定する。こうすることで、あるコミットから親のコミットをたどることができ、コミットの履歴がわかるようになる。

コミットオブジェクトを見るには、 git cat-file -p <コミットオブジェクトのSHA-1値> と指定すればよい。

$ git cat-file -p 5c8c49c    
tree 00906ce530257852785a7149b8df0c6a75a48ad2
parent cdba5fbd79ee3a484cfd45a97cfbbe90f77c1e6e
author Udomomo <udomomo@example.com> 1563115462 +0900
committer Udomomo <udomomo@example.com> 1563115462 +0900

add bar.txt

このように、parentとして前のコミットが表示されるのがわかる。

マージコミットとは

これをふまえて考えると、マージコミットもtreeオブジェクトを指すという点では同じである。しかし、違う点が1つだけある。以下はmasterブランチにfeatブランチをマージしたときのコミットを取り出したものである。

git cat-file -p 4afea17
tree 76ed3224820042a02d6af1886a2fb3259f1d8cb5
parent f8d2537e168efec54ce288769e1f4ca460258330
parent 9b0e505f0fd9a9b540f9783ebcbef9f2be3bddb4
author Udomomo <udomomo@example.com> 1563115699 +0900
committer Udomomo <udomomo@example.com> 1563115699 +0900

Merge branch 'feat'

parentが2つあるのがわかる。それぞれ、masterブランチとfeatブランチの以前のコミットを示している。
これがマージコミットと通常のコミットの違いである。逆に言えば違いはこれしかない。Gitの内部では両方ともコミットオブジェクトであり、ブランチというのは利用者向けにわかりやすく作られた概念にすぎない。

もう1つ異なる性質のコミット

マージコミットと通常のコミットは親の数が違うが、この点でいうともう1つ異なるコミットがある。それはinitial commitである。initial commitを取ってきてみると以下のようになる。

$ git cat-file -p cdba5fb
tree 09a13b897d3d0f528d487c704da540cb952d7606
author Udomomo <udomomo@example.com> 1563115391 +0900
committer Udomomo <udomomo@example.com> 1563115391 +0900

first commit

parentが1つもない。最初のコミットだけは例外的にparentを持たないことがわかる。