画像略
TL;DR(Too Long; Didn't Read)
-
~n
は単純なコミットの親をたどる(ブランチの分岐がある場合は現在のブランチのみで辿れるコミット) -
^n
はマージコミット向けで^2
は「そのコミットの2番目の親(取り込んだブランチの前回のコミット)」-
だからHEAD^n
(n > 2)は存在しない- 2024/06/04追記: OctopusなMergeだと3つ以上のブランチからマージできるので
^n
も存在する......があまり見かけることはない
- 2024/06/04追記: OctopusなMergeだと3つ以上のブランチからマージできるので
-
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を見つけることができます
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).
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~3
とHEAD@{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/09/22追記)
(2024/06/04追記修正しました)^n
(n > 2)について
^3
とか存在せんやろwと高を括っていました、反省。
コメントに記載されている記事の用に、複数のブランチからマージできるので、そうしたマージコミットの場合であれば^3
や^4
は参照できます。
このCommitを見ると、Parentに4つコミットがあることが確認できます。こんなことできるんだ......
ご指摘ありがとうございました!
補足(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
は自分自身をさすみたいですね。