LoginSignup
207
139

「これはHEAD^^」 「これはHEAD^2」 「これはHEAD~2」「HEAD@{2}、reflog用」「全部いっしょじゃないですか」「違う!!もっとよく見ろ!!」

Last updated at Posted at 2024-06-03

画像略

TL;DR(Too Long; Didn't Read)

  • ~nは単純なコミットの親をたどる(ブランチの分岐がある場合は現在のブランチのみで辿れるコミット)
  • ^nはマージコミット向けで^2は「そのコミットの2番目の親(取り込んだブランチの前回のコミット)」
    • だからHEAD^n(n > 2)は存在しない
      • 2024/06/04追記: OctopusなMergeだと3つ以上のブランチからマージできるので^nも存在する......があまり見かけることはない
    • HEAD^^は「HEAD^の親」、HEAD^2は「HEADのもう一人の親」みたいな......。タラちゃんがHEADだと波平がHEAD^^でマスオがHEAD^2です(わかりづら)
  • @{n}git reflogで見ることができるコミット以外の操作も含めた履歴の順番

背景

私「JetBrains IDEのUndo Commit最高~~~!!!」
コマンド忘れた「VSCode使ってる私」

ChatGPT「Undo Commitは git reset --soft HEADやで」
私「じゃあいっぺんにコミットやりなおすんはどうすんべ」

ChatGPT「ならgit reset --soft HEAD~nやで」
私「ん、HEAD^2も見たことある、違いは何」
ChatGPT「HEAD~2は親の親で、HEAD^2は2番目の親やで」

......ha?

全部一緒じゃないですか~~!!

最初見たときまじで何いってんだって感じなのですが、Gitのドキュメントにきちんと書いてあったのでまとめます

調べたときの足跡(長くなるので折りたたんでます)

まずは--helpを見る

git resetのヘルプを見ればなにか書いてあるなと考えとりあえず脳死でヘルプを呼び出します

GIT-RESET(1)                                          Git Manual                                         GIT-RESET(1)

NAME
       git-reset - Reset current HEAD to the specified state

SYNOPSIS
       git reset [-q] [<tree-ish>] [--] <pathspec>...
       git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
       git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
       git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

このあとに説明や具体例が続いていますが、このSYNOPSISというのがgit resetの書き方のパターンを示しています。
詳しい見方は省略しますが、いま知りたいところというのはどうも[<commit>]についての記述を探すのがよさげです。

<commit>の記述を見てみる

とはいってもここにのっているわけではなく、目を皿にしながら探すとHEAD^HEAD~3をつかうExampleを見つけることができます

HEAD^を使う例
       Undo a commit and redo

               $ git commit ...
               $ git reset --soft HEAD^      (1)
               $ edit                        (2)
               $ git commit -a -c ORIG_HEAD  (3)

           1. This is most often done when you remembered what you just committed is incomplete, or you misspelled
           your commit message, or both. Leaves working tree as it was before "reset".
           2. Make corrections to working tree files.
           3. "reset" copies the old head to .git/ORIG_HEAD; redo the commit by starting with its log message. If you
           do not need to edit the message further, you can give -C option instead.

           See also the --amend option to git-commit(1).
HEAD~3を使う例
       Undo commits permanently

               $ git commit ...
               $ git reset --hard HEAD~3   (1)

           1. The last three commits (HEAD, HEAD^, and HEAD~2) were bad and you do not want to ever see them again.
           Do not do this if you have already given these commits to somebody else. (See the "RECOVERING FROM
           UPSTREAM REBASE" section in git-rebase(1) for the implications of doing so.)

こうみると~による指定は単純にブランチのコミット履歴をたどっていくのがわかります
ただHEAD^nの記述はないんですよね......。

Web検索するか

ChatGPTに聞こうにも自分の中の語彙が悪いせいで要領の得ない回答しか得られないので、検索エンジンに適当に打ち込みます
「git commit syntax」
「git reset revision commit」とか......
(今思えばGitのドキュメントいけよって話ではありますが......)

というわけでたどり着いたオアシスがこちら

https://www.git-scm.com/book/en/v2/Git-Tools-Revision-Selection


今回例に出すコミットログを出しておきます

*       honda   f4cdf8a  (HEAD -> master) third commit on master (after merged)
*       honda   ea7f03a  Merge branch 'sub'
|\
| *     honda   0acaac2  (sub) first commit on sub
* |     honda   4584b5e  second commit on master
|/
*       honda   374bed4  first commit on master
*       root    bcb26a8  first commit on master       

使うコマンド:git show

引数のコミットを参照して情報を提示するコマンドです。これでHEADのあとになんたらかんたらつける正体をさぐります。
なおエディタに毎回遷移するのが嫌なので--no-pagerを付けています。

HEAD

❯ git --no-pager show HEAD
commit f4cdf8abb1547a63a216a717dbf800b3ef45801e (HEAD -> master)
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:11:48 2024 +0900

    third commit on master (after merged)

まぁ当然一番最新のコミットになりますね

~2

❯ git --no-pager show HEAD~2
commit 4584b5ed60d3d654f920c59e65c84791d0a6753a
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:13 2024 +0900

    second commit on master

今ブランチはmasterなので、masterブランチでたどることができる2つ目のコミットが選択されます

^2

❯ git --no-pager show HEAD^2
fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

HEADだとエラーになってしまう。ここで一つ前のマージコミットのea7f03aから試してみると、

❯ git --no-pager show ea7f03a^2
commit 0acaac2feef73a118f5a1c23fd39f9825715d0b5 (sub)
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:33 2024 +0900

    first commit on sub

となりsubブランチのマージコミットの前のコミットが出てくる。

ちなみにea7f03a^は、

❯ git --no-pager show ea7f03a^
commit 4584b5ed60d3d654f920c59e65c84791d0a6753a
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:13 2024 +0900

    second commit on master

となり、これはマージコミットの前の取り込み下(masterブランチ)の前回のコミットが出てきた。

HEAD^^

(なんか煽ってるようにみえるけど気の所為です)

❯ git --no-pager show HEAD^^
commit 4584b5ed60d3d654f920c59e65c84791d0a6753a
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:13 2024 +0900

    second commit on master

なんかHEAD~2と同じ結果みたいですね

Git Book解説

というわけで、コマンドで実行してみるとたしかに違うものを指すことがわかるわけです。
が、じゃあなんでこうなんねんというのがしりたいことでしょう。
https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection
Ancestry References(祖先の参照)」という章に書かれています。

If you place a ^ (caret) at the end of a reference, Git resolves it to mean the parent of that commit.

^(Caret)をコミットの参照につけるとGitはそのコミットの親として解釈する」と書かれてますね。

そして^2に関しても、

You can also specify a number after the ^ to identify which parent you want; for example, d921970^2 means “the second parent of d921970.”

d921970^2は『d921970の2番目の親』を意味してるよ」
とあり、

This syntax is useful only for merge commits, which have more than one parent — the first parent of a merge commit is from the branch you were on when you merged (frequently master), while the second parent of a merge commit is from the branch that was merged

「これは親が複数いるマージコミットのみの書き方で、1番目の親はマージしたときのもともとのブランチで、2番目の親はマージで取り込んだ方のブランチ」
という説明があります。

まとめ

とまぁこんな感じで実際のコマンド結果とドキュメントでまなべたよ~という共有でした。

  • ^,~は参照の親をたどる記号で、そのあとに数字をつけるとそれぞれ違う挙動をする
    • ~は今のブランチで探索可能なn代前のコミット
    • ^はマージコミット向けで、^は取り込み元のブランチ、^2は取り込んだブランチの親コミットをたどる

なのでこれを使うと例に出したHEADから参照することでもsubブランチのコミットを取ることができ、

❯ git --no-pager show HEAD~^2
commit 0acaac2feef73a118f5a1c23fd39f9825715d0b5 (sub)
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:33 2024 +0900

    first commit on sub

や、

❯ git --no-pager show HEAD^^2
commit 0acaac2feef73a118f5a1c23fd39f9825715d0b5 (sub)
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:33 2024 +0900

    first commit on sub

などのように「HEADの親の2番目の親」という指定で取ることができます

余談@{2}

❯ git --no-pager show HEAD@{2}
commit 4584b5ed60d3d654f920c59e65c84791d0a6753a
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:13 2024 +0900

    second commit on master

一見するとHEAD~2と変わらないが、ここでHEAD~3HEAD@{3}も確かめてみよう。

❯ git --no-pager show HEAD@{3}
commit 0acaac2feef73a118f5a1c23fd39f9825715d0b5 (sub)
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:10:33 2024 +0900

    first commit on sub
❯ git --no-pager show HEAD~3
commit 374bed48f242fded2a2261d7cb65cac4054a1fab
Author: honda <honda@example.com>
Date:   Mon Jun 3 23:09:29 2024 +0900

    first commit on master

なにかちがうぞ......?

はい、まぁこんなこと基本はしないので軽く紹介だけすると、branchのSwitchやrebaseなどを含めたコミットログでは出てこない操作もふくめたreflogです。

今回用意したRepoのreflogはgit reflogで表示でき、

❯ git --no-pager reflog
f4cdf8a (HEAD -> master) HEAD@{0}: commit: third commit on master (after merged)
ea7f03a HEAD@{1}: merge sub: Merge made by the 'ort' strategy.
4584b5e HEAD@{2}: checkout: moving from sub to master
0acaac2 (sub) HEAD@{3}: commit: first commit on sub
374bed4 HEAD@{4}: checkout: moving from master to sub
4584b5e HEAD@{5}: commit: second commit on master
374bed4 HEAD@{6}: checkout: moving from sub to master
374bed4 HEAD@{7}: checkout: moving from master to sub
374bed4 HEAD@{8}: commit: first commit on master
bcb26a8 HEAD@{9}: commit (initial): first commit on master

のようになっています。 checkoutとかが残っているのがわかりますね

このreflog情報は例えばrebaseの取り消しなどで使ったり、Gitの履歴復元の最終手段的な立ち位置だと私は思っています。

さいごに

というわけでGitのコミット参照の仕方を実際のコマンドと一緒に解説しました。これが分かるとcherrypickやreset --softなんかで役に立つのではないでしょうか(しらんけど)
これでターミナルしか使えない環境に放り出されてもある程度立ち向かえるのでは無いでしょうか、JetBrains IDE使える環境にいろよ
それでは良きGitライフを。

おまけ: ペイントでLogと参照の関係の画像です(字が汚いのはトラックパッドのせいにしてください)
スクリーンショット 2024-06-04 002127.png

(2024/06/04追記修正しました)^n(n > 2)について

^3とか存在せんやろwと高を括っていました、反省。
コメントに記載されている記事の用に、複数のブランチからマージできるので、そうしたマージコミットの場合であれば^3^4は参照できます。

このCommitを見ると、Parentに4つコミットがあることが確認できます。こんなことできるんだ......

image.png

ご指摘ありがとうございました!

補足(2024/06/05)

はてブ転載先のコメントに気になったのがあったので補足です
この記事にまとめていたものの情報がちゃんとターミナルで確認できるみたいですね
man gitrevisionsで下の方に行くと......

       <rev>^[<n>], e.g. HEAD^, v1.5.1^0
           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 and is used when <rev> is the object name of a tag object
           that refers to a commit object.

       <rev>~[<n>], e.g. HEAD~, master~3
           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. I.e.  <rev>~3 is
           equivalent to <rev>^^^ which is equivalent to <rev>^1^1^1. See
           below for an illustration of the usage of this form.

......gitrevisionsのマニュアルページを呼び出すのは認知ハードルが高いな〜と思いました。まる。

あと<commit>^0は自分自身をさすみたいですね。

207
139
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
207
139