Table of Contents
Difference between HEAD~ and HEAD^
git diffやgit resetのとき直面するHEAD^(caret)とHEAD~(tilde)はどのような違いがあるのか?
という疑問にぶち当たったのでまとめてみました. サマリーは以下のテーブルとなります:
| 表記 | 意味 |
|---|---|
~N |
N-th前のcommit |
^N |
N番目のparent commit |
What is Git HEAD?
Git 公式ドキュメントを見てみると, HEADについて以下のような説明がされています:
HEADnames the commit on which you based the changes in the working tree.
単純化してしまうと, HEADは現在のworking areaのベースとなる場所を指し示すポインターのことです. HEADというポインターがどこを示しているかは, repositoryの中の .git/HEAD に格納されています.
gitGraph
commit id:"A0001"
commit id:"B0002"
branch develop
commit id: "C0003"
commit id: "D0004 [HEAD]"
checkout main
commit id:"E0005"
commit id:"F0006"
上記の流れで開発が進行した状況を考えます. そして現在develop branchにいるとします. まずこのときのHEADを以下のコマンドで確認してみます.
1
2
3
4
% cat .git/HEAD
ref: refs/heads/develop
% cat .git/refs/heads/develop
D0004
このようにdevelopにいるときのHEADはdevelop branch自体を指しており, develop branchはD0004という最新のcommitを指しています.
次に, mainへswitchしてからHEADを確認してみます:
1
2
3
4
5
% git switch main
% cat .git/HEAD
ref: refs/heads/main
% cat .git/refs/heads/develop
F0006
mainにいるときのHEADはmain branch自体を指しており, main branchはF0006という最新のcommitを指しています. これはGitの内部挙動的には, branchがcommit-hashをreferenceしているに過ぎないものというのに関係しています.
最後に, 任意の過去のcommit-hashを指定してcheckoutした時のHEADを確認してみます.
1
2
3
% git switch --detach B0002
% cat .git/HEAD
B0002
このとき, HEADは直接commit-hashを参照しています. このようにbranchを介さずに直接commit-hashを参照している HEAD のことを特に DETACHED HEAD と呼びます.
DETACHED HEADのときは, branchがない状態と理解しても大丈夫です.
Column: DEATCHED HEADはいつ使うのか?
特定のコミット時点でのコード全体をreview, testをする際にDETACHED HEADを使います.
DETACHED HEAD状態から新たに開発の必要性が出た場合は,
1
% git switch -c <new-branch-name>
でbranchを新たに作成してから開発する必要があります. これを未実行のまま, 開発を進めcommitを作成しても一度ほかのbranchへswitchした瞬間にそれまでの作業記録がなくなってしまいます. (逆に, 他のbranchに移る前にbranchを新たに作成すれば作業変更記録がlogとして残ります)
What is HEAD~?
公式ドキュメントの定義を確認します:
A suffix ~ to a revision parameter means the first parent of that commit object.
A suffix ~<n> to a revision parameter means the commit object that is
the <n>th generation ancestor of the named commit object, following only the first parents.
単純化すると, Gitの文脈における ~N は, 指定したcommitから数えて同じbranchのN-th step前のcommit-hashを指し示すシンボルです.
gitGraph
commit id:"A"
commit id:"B"
branch develop
commit id:"C"
commit id:"D"
checkout main
merge develop id:"E"
commit id: "F[HEAD]"
上記の例の場合,
HEAD~: commit EHEAD~2: commit BHEAD~~: commit BE~: commit BE~2: commit A
を指し示します.
Column: HEAD~ is different from ORIG_HEAD
ORIG_HEADはHEADの直前の状態を示すシンボルgit reflogコマンドで確認できるHEAD@{1}に相当
What is HEAD^?
公式ドキュメントの定義を確認します:
A suffix ^ to a revision parameter means the first parent of that commit object.
^<n> means the <n>th parent (i.e. <rev>^ is equivalent to <rev>^1).
As a special rule, <rev>^0 means the commit itself
HEADに対して, N番目のparent commitを指し示すのが HEAD^Nとなります.
基本的には余り使わないほうが良いと思います.
gitGraph
commit id:"A"
commit id:"B"
branch develop
commit id:"C"
commit id:"D"
checkout main
merge develop id:"E"
commit id: "F[HEAD]"
上記の例の場合,
HEAD^: commit EHEAD^2: 存在しない(fatal: invalid reference: HEAD^2)HEAD^^: commit B(=親の親なので)E^: commit BE^^: commit AE^2: commit D(2人の親はmerge source branch)E^2~: commit CE^2~3: commit A
を指し示します.
How to Use HEAD~ and HEAD^ in Git?
Checking the diff between HEAD and the last commit by using git difftool
Editor経由で直前のcommitとの(またはN-th前の)差分をgit difftoolとの組合せで
簡単に確認することができます.
1
2
3
4
# 直前のcommitとの差分を確認したい場合
% git difftool HEAD~
# 特定のファイルについて, 直前のcommitとの差分を確認したい場合
% git difftool HEAD~ <file-name>
merge commitの直後に, mergeによる変更前のconflictを含む差分は以下のコマンドで確認することが出来ます
1
% git difftool HEAD~ HEAD^2
References
- Git 公式ドキュメント > HEAD, ORIG_HEAD, MERGE_HEAD etc
- Git 公式ドキュメント > DETACHED HEAD
- stackoverflow > What’s the difference between HEAD^ and HEAD~ in Git?
(注意:GitHub Accountが必要となります)