Git の HEAD^ と HEAD~ の違い


Git コマンドでよく見る HEAD^ と HEAD~ の違いって何なんでしょうか. どちらも HEAD の親のコミットを意味するものですが...

例えば HEAD の親のコミットのログを見たいという時に, 次のコマンド打ち込んでも:

git log HEAD^

もしくは, このコマンドでも:

git log HEAD~

同じ結果が表示されますが, HEAD の後に付け加えられる ^ (キャレット) と ~ (チルダ) では一体どのような違いがあるのでしょうか.

“^” の意味

まず ^ の場合, ^ は一番目の親という意味で, ^1 の速記バージョンです.

^2 とすると二番目の親という意味になります.

このように ^[<n>] とすると n 番目の親ということになります.

次のようなコミットヒストリの場合:

    B  C  D
     \ | /
      \|/
       A <- master - HEAD

A の親である B, C, D は左から右にソートされているという前提で, HEAD^ は HEAD の一番目の親を指定します. なので B というコミットが指定されます.

同様に HEAD^2 は二番目の親なので C が指定され, HEAD^3 は三番目の親として D が指定されます.

^ はもう一つの ^ を付け加えて ^^ のように書くこともできます.

この意味は一番目の親の一番目の親という意味になります.

なので ^1^1 と書くこともできます.

同じように親のコミットが左から右にソートされている, 次のようなコミットヒストリの場合:

    E  F  G  H  I
     \ |  |  | /
      \|  |  |/
       B  C  D
        \ | /
         \|/
          A <- master - HEAD

HEAD^^ と書くと HEAD の一番目の親の一番目の親ということで左上の E が指定されます.

HEAD^^2 の場合, 一番目の親の二番目の親ということになるので F が指定されます.

HEAD^2^ で二番目の親の一番目の親という意味になるので G となり, HEAD^3^ で三番目の親の一番目の親ということで H, HEAD^3^2 で三番目の親の二番目の親となり I が指定されます.

^3^2^ なら三番目の親の二番目の親の一番目の親を指定することになります.

このように ^ はいくらでも世代の数に応じて付け足していくことができます.

また親のコミットであれば, どんなに複雑に分岐されていようと ^[<n>] を連ねて指定することができます.

“~” の意味

今度は ~ の意味について説明したいと思います.

~~1 の速記で, 一番目の親という意味になります.

ただ ~[<n>]n は何番目の親でなく, 上がる階層の数を意味します.

また上がる階層に複数の親が存在している場合, 暗黙的に一番目の親を選択します.

なので結果的に ~^ と同じ, 一番目の親を指定することになります.

~2 とすると, 一番目の親の一番目の親を指定することになります.

~3 なら一番目の親の一番目の親の一番目の親ということになります.

前と同じ次のコミットヒストリで:

    E  F  G  H  I
     \ |  |  | /
      \|  |  |/
       B  C  D
        \ | /
         \|/
          A <- master <- HEAD

HEAD~ もしくは HEAD~1 とすると B が指定され, HEAD~2 とすると E が指定されます.

繰り返しになりますが, このように複数の親が存在するコミットヒストリで, ~ を使って階層を上がる際は一番目の親が選択されます.

なので一番目の親ではない F, G, H, I などは ~ を使って指定することはできません.

そのため, コミットヒストリが次のようにひと続きで, 複数の親が存在しないコミットヒストリを遡って古いコミットを指定するときに ~ を使うのが適しています.

    D
    |
    C
    |
    B
    |
    A <- master <- HEAD

このようなコミットヒストリの場合, 階層の数を指定する ~<n>n の部分だけを変更するばいいですからね.

^ で指定すると階層の数に応じて ^ を付け足さなければなりませんので, 10 上の階層を指定するとなると ^^^^^^^^^^ となりますが, ~ だったら ~10 と書けばいいので簡潔になります.

“^” と “~” を合わせて使う

^~ は一緒に組み合わせて使うことができます.

同じように親が左から右にソートされた, 次のようなコミットヒストリの場合:

    D  E  F
     \ | /
      \|/
       C
        \
         \
          B
           \
            \
             A <- master <- HEAD

F を指定するとすると HEAD~2^3 と書くことができます.

このように ^~ は指定されるコミットが存在する限り, 自由に組み合わせることができます.

さらなる例

man git-rev-parse で表示されるドキュメントには次のような素晴らしい例が記載されていますので, 紹介させていただきたいと思います:

    Here is an illustration, by Jon Loeliger. Both commit nodes B and C are parents of
    commit node A. Parent commits are ordered left-to-right.

        G   H   I   J
         \ /     \ /
          D   E   F
           \  |  / \
            \ | /   |
             \|/    |
              B     C
               \   /
                \ /
                 A

        A =      = A^0
        B = A^   = A^1     = A~1
        C = A^2  = A^2
        D = A^^  = A^1^1   = A~2
        E = B^2  = A^^2
        F = B^3  = A^^3
        G = A^^^ = A^1^1^1 = A~3
        H = D^2  = B^^2    = A^^^2  = A~2^2
        I = F^   = B^3^    = A^^3^
        J = F^2  = B^3^2   = A^^3^2

これは Jon Loeliger による一つのイラストです. B と C 両方のコミットノードは A のコミットノードの親です. 親のノードは左から右にソートされています.

僕はこの Jon Loeliger さんのイラストはとても参考にさせていただきました.

このイラストは B から J まで A の色々な親のコミットを A から指定する書き方が記載されています.

あと A 以外のコミットから指定する書き方も記載されています.

まとめ

何番目の親であろうと指定できる ^, ひと続きのコミットヒストリを一気に遡って指定できる ~, それらを組み合わせて, コミットヒストリに散らばる数多のコミットたちを縦横無尽に指定していただけたらと思います.

(ちょっとかっこよく言い過ぎたかな…)

最後までお読みくださいましてありがとうございました.