HEAD~1 と HEAD^1 を打ち間違えるとどうなる?
エンジニアになりたての頃、マージミスを修正しようと git reset を使った際、冷や汗をかいた経験があります。当時は ~ (Tilde) と ^ (Caret) は同じもので、どちらを使ってもいいと思い込んでいました。結果はどうなったかというと、master ブランチの直前のコミットに戻るはずが、大昔のフィーチャーブランチに飛んでしまったのです。幸い git reflog に救われましたが、そうでなければ午前中の作業がすべて水の泡になるところでした。
私の 8 人のチームでは、シニアエンジニアでさえ、複雑なマージコミットに遭遇すると一瞬手が止まることがあります。これら 2 つの記号を正しく理解することは、git rebase -i を使いこなす鍵となります。コミット履歴を乱す恐怖から解放され、自信を持って過去のコードを辿れるようになります。
30秒で覚えるクイックヒント
お急ぎの方は、このシンプルなルールだけ覚えておいてください:
- ~ (Tilde): 過去へ一直線に戻ります。
HEAD~3は正確に 3 ステップ前に戻ることを意味します。 - ^ (Caret): マージ時のどの親コミットに進むかを選択します。この記号は、1 つのコミットに 2 つ以上の親がある場合にのみ真価を発揮します。
# 1つ前のコミットに戻る(どちらも同じ結果)
git reset --hard HEAD~1
git reset --hard HEAD^
# 垂直方向に3つ前のコミットに戻る
git reset --hard HEAD~3
# マージコミットの2番目の親を表示する
git show HEAD^2
では、Git に二度と騙されないよう、その本質を詳しく見ていきましょう。
1. Tilde (~):垂直方向に遡る
Git の履歴を 1 本のロープだと考えてみてください。~ は、そのロープを何歩分後ろに下がるかを表します。
HEAD~n と書くと、Git は次のように解釈します。「ここから n ステップ戻れ。もし分岐(マージ)があったら、常に最初の親(first parent)を選べ」。
HEAD~1: 直前の親。HEAD~2: 親の親(祖父)。HEAD~3: 親の親の親(曽祖父)。
HEAD~~~ と書いても HEAD~3 と同じ結果になります。しかし、入力の手間を省き、記号の数え間違いを防ぐためにも数字を使うことをお勧めします。
2. Caret (^):分岐点での方向選択
本当の違いはマージコミットで現れます。通常のコミットには親が 1 つしかありませんが、Feature ブランチを Main に統合すると、そのコミットには 2 つの親が存在することになります。
- 第 1 の親 (Parent 1): マージ前に自分がいたブランチ(例:
main)。 - 第 2 の親 (Parent 2): 取り込んだ側のブランチ(マージしたブランチ)の最後のコミット。
このとき、HEAD^1 はメインの系統に戻ります。一方で HEAD^2 は、マージされた側のサブブランチへと導きます。HEAD^3 を指定すると 3 番目の親を探しますが、これは 4〜5 個のブランチを一度にまとめる Octopus Merge を使った時くらいしかお目にかかれません。
注意点: マージではない通常のコミットでは、HEAD^1 と HEAD~1 は同じ場所を指します。これが、多くの人がこの 2 つを「双子のようなもの」だと誤解する原因です。
3. コミット図による可視化
理解を深めるために、以下の図を見てみましょう:
G (HEAD)
|
F (マージコミット: E を D に統合)
/ \
D E
| |
C |
| /
B /
|
A
G (HEAD) の位置にいる場合、結果は次のようになります:
HEAD~1は F を指す。HEAD~2は D を指す(Git は G -> F -> D とメイン系統を辿る)。HEAD^1は F を指す。F^1は D を指す(マージ前のメイン系統)。F^2は E を指す(マージされたフィーチャーブランチ)。
では、G から E に行きたい場合は? HEAD~1^2 を使います。つまり「1 ステップ戻って F に行き、そこから 2 番目の親へ進む」という意味です。
4. Rebase 時の組み合わせテクニック
git rebase -i でコミットを整理する際、~ と ^ の組み合わせは非常に強力です。長い SHA-1 ハッシュをコピーする手間が省け、正確な「座標」を指定するだけで済みます。
例:HEAD~3^2
HEAD~3:一直線に 3 ステップ戻る。^2:その地点で 2 番目の親(サブブランチ)へ進む。
私の経験から言えるアドバイス:reset --hard を実行する前に、show コマンドで移動先を確認しましょう。
git show HEAD~2^1 --oneline -s
表示されたコミット名が意図通りであれば、そこで初めて Enter キーを押して本番のコマンドを実行します。
実践的なアドバイス:グラフを信頼する
理論は理解できても、10,000 件ものコミットがあるプロジェクトで ~ を目視で数えるのは無謀です。私のチームでは、コマンドを打つ前に必ずグラフを表示するように徹底しています:
git log --oneline --graph --all
グラフを見れば、接続線がはっきりと見えます。HEAD^2 や HEAD~5 の特定も、格段に直感的になります。
よくあるもう一つの状況は git cherry-pick です。マージコミットからコードを取り込もうとすると、Git はどちらの親を選べばいいか分からず停止します。その場合、-m フラグと親の番号を指定する必要があります(例:git cherry-pick -m 1 <commit_hash>)。これこそが、親(Parent)に関する知識が役立つ瞬間です。
まとめ
Git をプロのように使いこなすために、以下を覚えておきましょう:
- 同じブランチ内で時間を遡るには Tilde (~) を使う.
- マージ地点で別のブランチへ切り替えるには Caret (^) を使う。
- ジャンプする前に必ず
git log --graphで道筋を確認する.
これら 2 つの記号をマスターすれば、コードの履歴が迷宮に見えることはもうありません。スムーズなコミット管理を実現しましょう!

