Git

【git】HEAD^ と HEAD~ の違いをやさしく解説【ハットとチルダ】

Gitでコミットを指定するときに出てくる ^(ハット)と ~(チルダ)。
なんとなく「一つ前のコミット」を表すんだろうな、というのは分かるけれど、この2つって結局何が違うの?どっちを使えばいいの?と、あいまいなまま使っている人は多いと思います。

そこで本記事では、HEAD^HEAD~ の違いについて、初心者の方にも分かるようにやさしく解説していきます。

この記事を読めば、普段使いの結論と、マージのときに効いてくる本当の違いの両方がスッキリ分かりますよ。

この記事で分かること

ポイント

^(ハット)と ~(チルダ)の基本的な違い
HEAD^^HEAD~2 が同じになる理由
• マージコミットで2つの記号をどう使い分けるか

大前提:HEAD とコミットの親子関係

本題に入る前に、前提となる HEAD とコミットの関係について確認しておきましょう。

HEAD は「今いるコミット」を指す目印です。新しくコミットするたびに、HEAD はその最新コミットに移動していきます。

そして、どのコミットにも「」がいます。親とは「一つ前のコミット」のことで、コミットは親から子へと一直線につながっていきます。

A <- B <- C <- HEAD

この図だと、HEAD の親は CC の親は B……という関係ですね。

^~ も、この親をさかのぼっていくための記号です。まずはここを押さえておきましょう。

結論:普段は ~ だけでOK

細かい話に入る前に、結論から言ってしまいます。

記号意味
~n第1親を n回さかのぼる(例:HEAD~2 は2つ前)
^nn番目の親を選ぶ(例:HEAD^2 は2番目の親)

そして大事なのが、枝分かれのない普通の履歴では HEAD^HEAD~ はまったく同じということです。

HEAD^  =  HEAD~  =  HEAD~1  →  すべて「一つ前のコミット」

なので、ふだん「数コミット戻りたいだけ」なら ~ を使っておけば十分です!

たとえば直前のコミットを取り消すなら、こんな感じですね。

$ git reset HEAD^      // 直前のコミットを取り消す(変更内容は残る)

ただし、HEAD がマージコミットを指しているときは少し挙動が異なるので注意が必要です。 このあたりは後ほど「違いが出るのはマージコミットだけ」の章で詳しく解説していきます。

ここまでが「とりあえず使う」ための知識です。
ここからは「なぜ記号が2つあるのか」をもう一歩深く理解したい人向けの内容になります。

HEAD^^HEAD~2 が同じになるカラクリ

^ を2つ重ねた HEAD^^ は、HEAD~2 と同じ「2つ前のコミット」を指します。

HEAD^^  =  HEAD~2   (どちらも2つ前)
HEAD^^^ =  HEAD~3   (どちらも3つ前)

「じゃあ ^~ も結局同じでは?」と思うかもしれませんが、ここに勘違いの落とし穴があるんです。

実は、^ は番号を省略すると、暗黙に ^1(第1親)として扱われます。
つまり、

HEAD^^  は実は  HEAD^1^1  の省略形
        =「第1親の、第1親」
        =「第1親を2回たどる」
        =  HEAD~2

ということなんですね。

^^~2 と同じになるのは、「番号を省略すると第1親になる」からであって、「^ は第1親しかたどれないから」ではありません。
番号を明示すれば、第1親以外も選べるんです。これが次の章につながる重要なポイントになります。

違いが出るのは「マージコミット」だけ

^~ の本当の違いは、マージコミットで初めて姿を現します。

マージをすると、1つのコミットが2つの親を持ちます。
次の図を見てください。feature ブランチを main に取り込んでマージコミット M を作った状況です。

          A---B---C        (feature)
         /         \
D---E---F---G-------M       (main, HEAD = M)

このとき M の2つの親は、

第1親 = G(マージした側 = もともといた main
第2親 = C(取り込んだ側 = feature

になります。
第1親は「マージを実行したときに自分がいたブランチ」、第2親は「取り込んできたブランチ」と覚えておくといいですよ。

ここで各記号がどこを指すか見ていきましょう。

表記意味行き先
M^(=M^1第1親G
M^2第2親C
M~(=M~1第1親(M^ と同じ)G
M~2第1親を2回F

注目していただきたいのが M^2(第2親 = C)です。
これはたった1つ上の親なのに、~ ではどうやっても指せません。

なぜなら、~ は常に第1親しかたどらないからです。
M~2M~3 も、ずっと G→F→E… という第1親のライン上を進むだけで、feature 側の C には絶対に入りません。

つまり ^~ の違いは「どれだけ遠いか(距離)」ではなく、「どの親を選ぶか(枝の選択)」なんですね。
feature 側に乗り移りたいときだけ ^2 の出番、というわけです。

^2 の先も、ちゃんと遡れる

^2 で第2親に移れるのは分かった。でも、その先(C より過去)には行けないのでは?」
——これはよくある疑問だと思います。

答えは「行けます」!

^2 は行き止まりではなく、単に「ここで feature 側に乗り換えた」という1ステップにすぎません。
リビジョンの記号は、左から順に「相対的な移動」を積み重ねていく仕組みだからです。

M^2 を評価し終えた時点で、現在地は C に移っています。
そこからさらに記号を続ければ、C を起点に遡れます。

表記動き行き先
M^2第2親へC
M^2^(=M^2^1→ その第1親へB
M^2~2→ 第1親を2回A

このように、第2親に入ったあとは ^ でも ~ でも自由に続けられます。
M^2~2 のように混ぜてもOKです。「^2 で指定したら、それより過去には行けない」ということは起きないので安心してください。

まとめ

今回は HEAD^HEAD~ の違いについて解説してきました。

まとめると、

縦に遡るなら ~、枝を選ぶなら ^
HEADが分岐の内コミットを指している場合は HEAD^ = HEAD~。気にせず使ってOK
HEAD^^ = HEAD~2 になるのは「^ の番号を省略すると第1親になる」から
• マージで「取り込んだ側」に入りたいときだけ ^2 が必要
^2 の先も ^~ を続ければ普通に遡れる

といった感じでした!

ふだんは ~ を使って、マージの枝に入りたいときだけ ^2 を思い出す。これだけ覚えておけば、まず困ることはないと思います。

最後までお読みいただきありがとうございました。

-Git