Sat, Oct 8, 2016

git checkoutを図解する

git checkoutを図解する

この記事を読んだ、またはGitのオブジェクトモデルを理解していることを前提に、Gitgit checkout というコマンドについて説明する。

このコマンドは普通ブランチを切り替えるものと説明されるが、主たる機能は オブジェクト格納領域から指定されたファイルを取り出し、ワーキングディレクトリに配置する ものである。 つまりこれがGitにおけるチェックアウトで、チェックアウト=ブランチの切り替えではない。

コマンドに与える引数によっては HEAD の付け替え、つまりはブランチの切り替えもする、というだけ。

git checkout の動作を HEAD の付け替えの有無によって分けて考えると分かりやすく覚えやすいので、以下そのように説明する。


HEADを付け替えないgit checkout

HEAD を付け替えない git checkout は、引数にワーキングディレクトリ内の ファイルまたはディレクトリへのパスを与えた場合 のもの。 ディレクトリを指定した場合はそれ以下の全ファイルが操作対象となる。 パスは絶対パスかカレントディレクトリからの相対パスで、複数指定できる。

つまりは以下の様なコマンド形式になる。

git checkout <パス(複数可)>

これを実行すると、指定したファイルについて、インデックスが指しているブロブ をオブジェクト格納領域から取り出し、ワーキングディレクトリのファイルを置き変える。


上のスライドではインデックスが指しているブロブを取り出したが、任意のブロブを取り出すこともできる。 この場合、以下の様なコマンド形式を使う。

git checkout <コミット> <パス(複数可)>

このコマンド形式だと、指定したコミットが指すツリー以下のブロブ が取り出される。 <コミット>の部分には、コミットオブジェクトのSHA1ハッシュ値、参照(i.e. ブランチかタグ)、シンボリック参照(e.g. HEAD)を指定できる。(実際にはこれらが全てではないが、実用的にはこの3種。)

この形式だと、ワーキングディレクトリだけでなく、取り出すブロブを指すよう インデックスも更新される ことに注意。


HEADを付け替えるgit checkout

HEAD を付け替える git checkout は、引数に パスを与えない場合 のもの。 代わりにコミットを与える。

つまりは以下の様なコマンド形式になる。

git checkout <コミット>

<コミット>の部分には、コミットオブジェクトのSHA1ハッシュ値、参照(i.e. ブランチかタグ)、シンボリック参照(e.g. HEAD)を指定できる。(実際にはこれらが全てではないが、実用的にはこの3種。)

これを実行すると、指定したコミットが指すツリー以下の全てのブロブ を指すようインデックスを更新し、それらのブロブをオブジェクト格納領域から取り出してワーキングディレクトリに配置する。

この上更にHEADを付け替えるわけだが、付け替え先は<コミット>の種類によって以下の三通りある。

  • <コミット>がブランチ: HEADはそのブランチを指すよう更新される。
  • <コミット>がSHA1ハッシュ値: HEADはコミットを指すよう更新される。
  • <コミット>がタグかシンボリック参照: HEADはタグかシンボリック参照が指すコミットを指すよう更新される。



上のスライド中のコミットをチェックアウトした例を見ると分かるが、コマンド実行前後でワーキングディレクトリからファイルが削除されることもある。 これは多分、実際にはインデックスの更新処理の前に、HEADが指すコミットに含まれるファイルをワーキングディレクトリから削除する処理があるからだと考えられる。

また、上のスライドには表現していないが、コマンド実行前にワーキングディレクトリやインデックスに未コミットな変更が入っている場合、Gitはそれをコマンド実行後のワーキングディレクトリに適用しようとしてくれる。 これは例えばあるブランチで作った変更を別のブランチにコミットしたいようなときは便利だが、checkoutしたコミットに別途変更が入っているとその適用は失敗し、コマンドがエラーになるので、普通はコマンド実行前にgit stashしておくのが無難。