clock-up-blog

go-mi-tech

Gitの話:コミットオブジェクトは未来を知らない

Git Advent Calendar 2017 - Qiita 1日目

Gitのコミット構造に少し踏み込んだ話をします。Gitをある程度使ってみたことのある人向けの話です。

コミットオブジェクトは未来を知らない

Git のコミットオブジェクト群は後方参照 (backward reference) のハッシュチェーン構造により履歴の繋がりが管理されています。
f:id:kobake:20171202123833p:plain

各コミットオブジェクトは「parent」という参照で自分の親コミット(直前の履歴にあたるコミット)に繋がる形になっています。

コミットオブジェクトが持っている他コミットへの参照は「親コミット(直前のコミット)」のみです。それ以外の一切のコミットオブジェクト間依存はありません。

つまり、コミットオブジェクトは自分の次に来るコミット情報を一切保持しません。

コミットオブジェクトの状態は不変

方便として「コミットの書き換え」という言い方がよくされ、実際にその手段として「git commit --amend」コマンドが提示されることがあります。

履歴の書き換えのために「git commit --amend」が使えることは確かですが、意識してほしい点として、「実際に git commit --amend はコミットオブジェクトを変更しているわけではない」ことを覚えておいてください。

以下はコミット「d2f1d37」の履歴を書き換えたいケースですが、既存のコミットオブジェクトを書き換えるような操作はGitではできません。
f:id:kobake:20171202131602p:plain

履歴の変更のためにできる操作は「コミットオブジェクトの作り直し」です。「git commit --amend」は「コミットオブジェクトの書き換え」ではなく「コミットオブジェクトの作り直しとHEADの移動」を行います。
f:id:kobake:20171202142459p:plain

今回のケースではコミット「d2f1d37」は残したまま新しいコミット「3251bdb」が作られて、そちらにHEADポインタが移ります。

一般的にコミットログは HEAD からの参照を辿る形で表示されるため、今回のケースでは最終的に「3251bdb」「4f4bc08」「c20ab03」のみが見える形となりました。「d2f1d37」の存在は残っていますがそこへたどり着く参照(矢印の向き先)がなくなったため、コミットログからは「消えた」ように見えます。

表層的な見た目としては既存コミットが書き換えられたような印象を受けますが、実際に発生しているのはこのような現象です。

コミットオブジェクトの状態は不変であり未来に依存しない

冒頭で、「コミットオブジェクトは未来を知らない」と書きましたが、これは言い方を変えれば「未来の情報を持つ必要がない(未来への依存がない)」ということです。

ある「コミットX」が積まれ、その「ハッシュ値X」が決まった時点で、その「コミットX」の状態は不動のものとなります。

未来への依存がないということはつまり、未来永劫どのような操作が行われようとも「コミットX」の状態は絶対に変わらない、ということです。

不変であるコミットオブジェクトの取り扱い

今回の履歴書き換えの対象となったコミット「d2f1d37」も例に漏れず不変です。このコミット「d2f1d37」は作られた時点から一度も変更されていませんし、誰も変更することはできません。

その代わり「git commit --amend」操作により別コミット「3251bdb」の作成と HEAD の移動が行われました。

結果、コミット「d2f1d37」は変更されていないことには変わらないのですが、どこからも参照されない不要のものとなりました。このようなコミットオブジェクトはガベージコレクション対象となり、そのうち削除されます。


今日の話は以上です。

});