けんごのお屋敷

2014-02-10

git の歴史の辿り方

git にはリビジョンを引数として受け取るコマンドがいくつかあります。git showgit resetgit loggit diff などはその典型でしょうか。これらのコマンドを使う時はリビジョンを指定しますが、その指定の仕方には様々な方法があって、最近自分の知識整理のためにすこし調べてたので、せっかくなのでまとめてみました。

SHA1ハッシュ

git はコミット毎にそのコミットに対して SHA1 のハッシュを生成するので、その値をリビジョンの指定に使えます。

$ git show ed9003bcbdb33a0d626911244e5f8e6e244351e9
commit ed9003bcbdb33a0d626911244e5f8e6e244351e9
Author: Kengo Tateishi <xxxx@xxx.com>
Date:   Sun Feb 9 08:11:25 2014 +0900

    開発環境用の設定を追加

...

コミットのハッシュ値は省略した状態で重複がなければ省略して書くことができます。こういう風に書いても上記と同じ意味になります。

$ git show ed9003b

ブランチ名 / タグ名

git ではブランチやタグは、どこコミットへの参照を持っているかを git が管理しているのでリビジョンの指定に使えます。

$ git show master
commit d1e3309be41606db510d80df67b8826edf1b8f16
Author: Kengo Tateishi <xxxx@xxx.com>
Date:   Sun Feb 9 08:19:31 2014 +0900

    テスト設定変更

ローカルのブランチだけではなく、リモートのブランチも指定できます。

$ git show origin/bugfix
commit 1bc7edf75b611a8daaeff4861c5d88855cc62f6c
Author: Kengo Tateishi <xxxx@xxx.com>
Date:   Sun Feb 8 07:19:30 2014 +0900

    バグフィックス

ブランチやタグは実は git のディレクトリ内の .git/refs/heads.git/refs/tags.git/refs/remotes にブランチやタグの名前と同じファイル名があって、その中身が参照するハッシュの値となっています。

$ cat .git/refs/heads/master
d1e3309be41606db510d80df67b8826edf1b8f16

$ cat .git/refs/remotes/origin/bugfix
1bc7edf75b611a8daaeff4861c5d88855cc62f6c

範囲指定

.. または ... でリビジョンを繋ぐと範囲指定をすることができます。git loggit diff で良く使います。

ここでは、この歴史を使って ..... の違いを見てみます。

ドット2つ

A..B という風にリビジョンを2つのドットで繋げた書き方をします。この書き方だと

  • リビジョンBからは辿れる
  • リビジョンAからは辿れない

を満たすコミットを取得します。要するに A に存在しないコミットです。

$ git log master..payment --oneline
a5b2cd8 設定ファイル追加
ffeddb9 決済のテスト追加
4929e71 決済機能の実装

ここで表示された3つのコミットは payment ブランチでのコミットです。master ブランチからは辿れないですね。リビジョンの指定を逆にするともちろん結果も変わってきます。

$ git log payment..master --oneline
d1e3309 テスト設定変更

仕様的には上記に書いた通り、辿れるか辿れないかを見てコミットを取得するのですが覚えにくいので

  • ブランチを指定する場合は ブランチ間コミットの差分
  • SHA1ハッシュを指定する場合は AからBまでのコミット(ただしAは除く)

という感じで自分は直感的に覚えています。厳密には全然違うかもしれませんが、だいたい思ったとおりの結果は返ってきます。

ドット3つ

A...B という風にリビジョンを3つのドットで繋げた書き方をします。自分はあんまり使わないですが、この書き方だと

  • リビジョンAから辿れる
  • リビジョンBから辿れる
  • ただしAにもBにも存在するコミットは除外

を満たすコミットを取得します。

$ git log master...payment --oneline
d1e3309 テスト設定変更
a5b2cd8 設定ファイル追加
ffeddb9 決済のテスト追加
4929e71 決済機能の実装

仕様的にリビジョンの順番を逆にしても結果は変わりません。

特別な参照名

git では特別な参照名もリビジョンとして指定することができます。

現在作業しているワーキングツリーのコミットを指します。コミットが重なる度に先頭へ移動していきます。また git resetgit merge git rebase などのコマンドでも移動します。

ORIG_HEAD

git resetgit merge など、通常のコミットとは違う極端な HEAD の移動が発生した時に、移動前の HEAD のコミットを指します。

MERGE_HEAD

git merge を実行して、コンフリクトなどが発生してマージをしている最中にマージ元のブランチの先頭のコミットを指します。

CHERRY_PIKC_HEAD

MERGE_HEAD とほぼ同じですが git cherry-pick を実行した場合のものです。

FETCH_HEAD

git fetch した時、リモートから取得したコミットの先頭のコミットを指します。


こんな風に指定したり

$ git log ed9003b..HEAD
69796ef Merge branch 'payment'
d1e3309 テスト設定変更
a5b2cd8 設定ファイル追加
ffeddb9 決済のテスト追加
4929e71 決済機能の実装
c184d3c 無視ファイル追加

あと、よく見るマージの取り消しコマンドなど

$ git reset --hard ORIG_HEAD

こういう風に使えます。

世代をたどる

特定のリビジョンの後ろに ^ または ~ を付けたリビジョンを指定することもできます。この ^~ の違いがすごいわかりにくいのですが…

ここでは、この歴史を使ってその違いを見てみます。

^ について

^ を指定することで、親のコミットを取得することができます。よく聞く話かとは思いますが git のコミットには必ず 1 つは親が存在します(一番最初のコミットを除いて)。親というのは要するに直前のコミットのこと。そしてマージが発生すると、そのマージコミットについては 2 つの親を持つことになります。

上に示した歴史の場合 69796ef がマージコミットになっており、その親は 2 つ存在しており、マージ先の d1e3309 が 1 つ目の親で、マージ元の a5b2cd8 が 2 つ目の親です。本流の d1e3309 の親は c184d3c ですし payment ブランチの a5b2cd8 の親は ffeddb9 です。これをふまえると

$ git show HEAD^   # HEAD の 1 番目の親。つまり d1e3309
$ git show HEAD^^  # HEAD の 1 番目の親の 1 番目の親。つまり d1e3309^ と同じ。つまり c184d3c
$ git show HEAD^^^ # HEAD の 1 番目の親の 1 番目の親の 1 番目の親。つまり ed9003b

$ git show HEAD^1  # HEAD^ と同じ。つまり d1e3309
$ git show HEAD^2  # HEAD の 2 番目の親。つまり a5b2cd8
$ git show HEAD^3  # 親は 2 つしかないのでこれはエラー

$ git show HEAD^1^ # HEAD の 1 番目の親の 1 番目の親。つまり d1e3309^ と同じ。つまり c184d3c
$ git show HEAD^2^ # HEAD の 2 番目の親の 1 番目の親。つまり a5b2cd8^ と同じ。つまり ffeddb9

図示するとこんな感じ。

~ について

親のコミットを取得できるのは ^ と同じなのですが ~ の方はマージコミットがあっても本流の方だけをたどっていきます。

$ git show HEAD~   # HEAD の親。つまり d1e3309
$ git show HEAD~~  # HEAD の親の親。つまり d1e3309^ と同じ。つまり c184d3c
$ git show HEAD~~~ # HEAD の親の親の親。つまり ed9003b

$ git show HEAD~1  # HEAD~ と同じ。つまり d1e3309
$ git show HEAD~2  # HEAD~~ と同じ。つまり c184d3c
$ git show HEAD~3  # HEAD~~~ と同じ。つまり ed9003b

図示するとこんな感じ。

まとめ

こうやって見ると色々指定の仕方があります。本当はこれ以外にも日付を指定したりとかあるみたいなのですが、あまり使わなさそうだったのでそこまでは調べませんでした。自分がよく使うのは

$ git diff HEAD^                                    # ひとつ前のコミットとの差分
$ git reset --hard ORIG_HEAD                        # マージ取り消しなど
$ git diff d1e3309^..d1e3309                        # 特定のコミットの差分
$ git rebase -i HEAD^^^                             # 3つ前のコミットまでrebase interactiveする
$ git log origin/master..origin/feature --name-only # コードをレビューする時などファイル名一覧を取得

とかでしょうか。いろんなリビジョンの指定方法がありますが、全部はなかなか覚えられないので範囲指定と ^ とかはよく使うので覚えておいて損はなさそうです。

  • このエントリーをはてなブックマークに追加