Git操作をしていると、HEAD@{n}や@@{n}といった表記を見かけることがあります。例えば次のようなものです。
ここでは、HEAD@{n}や@@{n}や、HEAD@{n}や@@{n}を理解する上で欠かせないgit reflog
について解説しています。
git reflogとは何か?
HEAD@{n}や@@{n}を理解するにはまず、git reflog
コマンドを理解する必要があります。
gitにはコミット履歴を参照するコマンドにgit log
があります。gitコマンドの中でも最もよく使うコマンドの一つといっても過言ではありません。
git logはコミットの履歴を参照するものです。Gitにはこれとは別に、コミット(git commit)も含め、レポジトリ内で行われた全てのコマンドの履歴も保存しています。
コマンドとは、git commit, git checkout, git marge, git pull, git fetch, git resetなど、あらゆるGitコマンドのことです。
このGit内のコマンド操作の全ての履歴を参照するコマンドがgit reflog
です。
git reflogの実例
git reflogを実行すると次のような履歴が表示されます。
$ git reflog
c597283 (HEAD -> as) HEAD@{0}: checkout: moving from master to as
d21e8bf (master) HEAD@{1}: checkout: moving from as to master
c597283 (HEAD -> as) HEAD@{2}: reset: moving to @
c597283 (HEAD -> as) HEAD@{3}: checkout: moving from image_tag to as
129ba79 (image_tag) HEAD@{4}: checkout: moving from fix to image_tag
129ba79 (image_tag) HEAD@{5}: commit: [A]home.index ホバーの画像切り替え
1c3e97d HEAD@{6}: reset: moving to 1c3e97d
c597283 (HEAD -> as) HEAD@{7}: checkout: moving from as to fix
c597283 (HEAD -> as) HEAD@{8}: checkout: moving from fix to as
c47cf4a HEAD@{9}: reset: moving to @
c47cf4a HEAD@{10}: reset: moving to @^
1c3e97d HEAD@{11}: reset: moving to 1c3e97d
c597283 (HEAD -> as) HEAD@{12}: checkout: moving from as to fix
c597283 (HEAD -> as) HEAD@{13}: commit: [A]variant for image
dba364e HEAD@{14}: commit: [U]users詳細 画像のダウンロード機能追加
git log --oneline
の実行結果と似ているようですが次の点が大きく異なります。
ここで表示された内容の中に、HEAD@{0}、HEAD@{1}、HEAD@{2}といった表示があることにも注意してください。
git reflogで表示された内容の見方
git reflogの表示内容
git reflogを使うと以下のような文字列が表示されます。
c597283 (HEAD -> as) HEAD@{0}: checkout: moving from master to as
これは次のようになっています。
①コミット番号 ②(参照名) ③HEAD@{n}: ④Gitコマンド: ⑤処理内容
5つの項目の内容が表示されています。
①コミット番号
コミット番号は各Git操作の内容をハッシュ値をつけて参照できるようにしたものです。
本来は「c5972837bc730ae64303995820e9f855ce279810」のように非常に長いものですが、「c597283」のように7桁ほどで表示されます。
コミット履歴が非常に長い巨大なプロジェクトなどハッシュ値が7桁だと重複が発生する場合は、8桁、9桁のように重複の発生しない桁数だけ表示されます。
②(参照名)
参照名とは (HEAD -> as) のようにカッコ()で囲まれている部分のことです。
ローカルブランチとリモート追跡ブランチのそれぞれがどこにあるかを示しています。
例えばgit log --oneline
を実行すると以下のようにコミット履歴が表示されます。(–onelineオプションはコミット履歴を一行ずつ表示するオプションです。)
$ git log --oneline
0adba46 (HEAD -> main, origin/main) [F]edit class name
b80abcb (test) [A]test Components UserDetail
参照名(ref name)とは、コミットのハッシュ値の後ろに表示されるカッコで囲まれた部分のことです。
(HEAD -> main, origin/main)
(test)
参照名は、各ローカルブランチがどのコミット履歴を指しているか、ローカルと紐づけた各リモートレポジトリのコミット履歴がどこを指しているか、現在のブランチはどのコミットにいるかを示しています。
実際の開発でも多用する非常に重要な情報です。
主要な参照名の意味(HEAD、main、origin/main)
以下の参照名には大きく分けて「HEAD->」「main」「origin/main」「test」の4つがあります。
$ git log --oneline
0adba46 (HEAD -> main, origin/main) [F]edit class name
b80abcb (test) [A]test Components UserDetail
HEAD->
「HEAD->」は現在のコミット履歴を参照しているブランチがどこかを表しています。HEAD -> main
は、現在のブランチが「main」にいることを示しています。
origin/main
「origin/main」はリモートに同期したリモートレポジトリ名originのmainブランチの最新のコミットの場所を指しています。(「リモートレポジトリ名/ブランチ名」と対応しています)
「リモートに同期した」というのが分かりにくいかもしれないので、追加で解説します。
リモートレポジトリはGithub上に存在します。リモートレポジトリのコミット履歴をローカルレポジトリに取り込んだときに、「リモートレポジトリ名/ブランチ名」のコミット履歴が更新されます。
このため、自分以外の他の人が該当するGithub上のリモートレポジトリのコミット履歴を前に進めた場合、ローカルの「リモートレポジトリ名/ブランチ名」が指す最新のコミット番号は、Github上のリモートレポジトリとずれます。
もし、ローカルレポジトリに同期しているリモートレポジトリの状態を最新に更新したい場合は「git fetch」を実行してください。
test
2つ目のコミット履歴の参照名は「test」になっています。
これはローカルレポジトリの「test」ブランチは、このコミット履歴にいますよということです。
testブランチがmainブランチのコミット履歴を取り込めば、「test」は「main」や「origin/main」と同じ場所に来ます。
実際にtestブランチに移動して、mainブランチの内容を取り込むと以下のようになります。
#testブランチに移動
$ git checkout test
Switched to branch 'test'
#mainブランチのコミット履歴を取り込む
$ git rebase main
Successfully rebased and updated refs/heads/test.
#コミット履歴を表示
$ git log --oneline
0adba46 (HEAD -> test, origin/main, main) [F]edit class name
b80abcb [A]test Components UserDetail
(HEAD -> test, origin/main, main)
と表示され、「test」が「main」や「origin/main」と同じ場所に来ていることがわかります。
なお、現在はtestブランチにいるので、「HEAD->」は「test」を指しています。
③HEAD@{n}
git reflogで表示される内容の3つ目でいよいよHEAD@{n}
という表示が出てきます。
これは、全てのGitコマンドの操作履歴において、最新の履歴を「0」として、そこから何番目の履歴かを表しています。
$ git reflog
c597283 (HEAD -> as) HEAD@{0}: checkout: moving from master to as
d21e8bf (master) HEAD@{1}: checkout: moving from as to master
c597283 (HEAD -> as) HEAD@{2}: reset: moving to @
c597283 (HEAD -> as) HEAD@{3}: checkout: moving from image_tag to as
HEAD@{0}, HEAD{1}, HEAD{2}というように、一行下がるごとに{ } の中の数値が1づつ増えていきます。
④Gitコマンド
4つ目の要素は実行されたGitコマンドの情報です。
$ git reflog
c597283 (HEAD -> as) HEAD@{0}: checkout: moving from master to as
d21e8bf (master) HEAD@{1}: checkout: moving from as to master
c597283 (HEAD -> as) HEAD@{2}: reset: moving to @
c597283 (HEAD -> as) HEAD@{3}: checkout: moving from image_tag to as
129ba79 (image_tag) HEAD@{4}: checkout: moving from fix to image_tag
129ba79 (image_tag) HEAD@{5}: commit: [A]home.index ホバーの画像切り替え
上記の場合、一番最近に実行されたコマンドが「git checkout」、もう一つ前に実行されたのも「git checkout」その前は「git reset」であることがわかります。
「git commit」が実行されたのは5つ前のGitコマンドであることがわかります。
⑤処理内容
最後に表示されるのが処理内容です。
checkoutの例
例えば以下の場合、「git chekout」で「moving from master to as」されたことを示しています。
c597283 (HEAD -> as) HEAD@{0}: checkout: moving from master to as
すなわち、masterブランチからasブランチへ移動したということです。
コマンドでいうとmasterブランチにおいて「git checkout as」を実行したということがわかります。
resetの例
git resetの場合は以下のように表示されます。
c597283 (HEAD -> as) HEAD@{2}: reset: moving to @
処理内容は「moving to @」です。これは、git resetで最新のコミットに戻った(コミット後の余計な変更を捨てた)ということを示してます。
実際に実行されたコマンドは「git reset –hard @」です。
なおここでの「@」は「HEAD」のことです。(@とHEADの詳細については下記をご参考ください)
【Git】HEAD~~, HEAD^^, アットマーク@~(チルダ), @^(キャレット)とは何か?
HEAD@{n}や@@{n}の違い
HEAD@{n}は 全てのGitコマンドの操作履歴において、最新の履歴を「0」として、そこから何番目の履歴かを表しています。
では、@@{n}は何かというと、HEAD@{n}と同じです。
「@@{n}」=「HEAD@{n}」です。
HEADは4語あり毎回入力するのは大変です。このため、Gitでは「HEAD」の別名(エイリアス)として「@」が用意されています。
「HEAD@{n}」のHEADの部分を@に置き換えると「@@{n}」になります。
git resetで間違って消したコミットを復活・復元させる方法
git reflogと@@{n}(またはHEAD@{n})を使うと、git resetで間違って消したコミットを復活させることができます。
方法はとても簡単で、戻りたいGitのコミット履歴を指定してgit reset --hard
を実行するだけです。
git reset --hard @@{n}
↑↓ 同じ
git reset --hard HEAD@{n}
通常git log
で表示されるコミット履歴は、git reset
をして過去のコミットに戻ると、それ以降は表示されなくなり、消えてしまったかのように見えます。
ところが、git reflogの中には、git resetした履歴もその前のコマンドの履歴も全て残っているので復活できるというわけです。
git resetの復活方法実例
実際に、git resetで間違ってコミットを戻りすぎてしまった場合に、コミット履歴を復活する方法を紹介します。
初期状態のgit log
次のようなコミット履歴で処理を行います。
$ git log --oneline
c597283 (HEAD -> as) [A]variant for image
dba364e [U]users詳細 画像のダウンロード機能追加
c5f72e1 [U]ActiveStorage 複数選択&個別削除
9cbd2d7 [U]User画像の複数選択
1c3e97d [A]tweet ActiveStrage test
c47cf4a [WIP]user attach image
73aaaac [A]ActiveStrage migrated
間違って@^^^を実行
1つ前のコミット(@^)に戻りたい場合に、間違って3つ前のコミット(@^^^)まで戻ってしまった場合は以下のようになります。
$ git reset --hard @^^^
HEAD is now at 9cbd2d7 [U]User画像の複数選択
git reset –hardが実行され、間違って戻りすぎてしまいました。
git logで確認すると次のようになり、元々あった3つのログが消えてしまっています。
$ git log --oneline
9cbd2d7 (HEAD -> as) [U]User画像の複数選択
1c3e97d [A]tweet ActiveStrage test
c47cf4a [WIP]user attach image
73aaaac [A]ActiveStrage migrated
この状態から、消してしまったコミット「dba364e [U]users詳細 画像のダウンロード機能追加」まで戻ってみます。
git reflogでコマンド実行履歴を確認
まずは落ち着いてgit reflogでコマンドの実行履歴を確認します。
$ git reflog
9cbd2d7 (HEAD -> as) HEAD@{0}: reset: moving to @^^^
c597283 HEAD@{1}: checkout: moving from master to as
d21e8bf (master) HEAD@{2}: checkout: moving from as to master
c597283 HEAD@{3}: reset: moving to @
c597283 HEAD@{4}: checkout: moving from image_tag to as
129ba79 (image_tag) HEAD@{5}: checkout: moving from fix to image_tag
129ba79 (image_tag) HEAD@{6}: commit: [A]home.index ホバーの画像切り替え
1c3e97d HEAD@{7}: reset: moving to 1c3e97d
c597283 HEAD@{8}: checkout: moving from as to fix
c597283 HEAD@{9}: checkout: moving from fix to as
c47cf4a HEAD@{10}: reset: moving to @
c47cf4a HEAD@{11}: reset: moving to @^
1c3e97d HEAD@{12}: reset: moving to 1c3e97d
c597283 HEAD@{13}: checkout: moving from as to fix
c597283 HEAD@{14}: commit: [A]variant for image
dba364e HEAD@{15}: commit: [U]users詳細 画像のダウンロード機能追加
Gitコマンドの履歴を遡ると、HEAD@{15}に戻りたいコミット「[U]users詳細 画像のダウンロード機能追加」があることがわかります。
git resetでコミットを復活させる
復活したいコミットのコミット番号とHEAD@{n}がわかったので、実際に復活させます。
$ git reset --hard @@{15}
HEAD is now at dba364e [U]users詳細 画像のダウンロード機能追加
本当に欲しかったコミットにHEADが移動しました。
なおコマンドはgit reset --hard HEAD@{15}
でも同じです。
git logでコミット履歴を確認します。
$ git log --oneline
dba364e (HEAD -> as) [U]users詳細 画像のダウンロード機能追加
c5f72e1 [U]ActiveStorage 複数選択&個別削除
9cbd2d7 [U]User画像の複数選択
1c3e97d [A]tweet ActiveStrage test
c47cf4a [WIP]user attach image
73aaaac [A]ActiveStrage migrated
戻りたかったコミット「dba364e [U]users詳細 画像のダウンロード機能追加」が復活していることがわかります。
以上で復活は完了です。
(参考)git reflogの状態
この状態でgit reflogを確認すると、以下のようにgit resetで@@{15}に戻った処理が最新の履歴になっていることがわかります。
$ git reflog
dba364e (HEAD -> as) HEAD@{0}: reset: moving to @@{15}
9cbd2d7 HEAD@{1}: reset: moving to @^^^
c597283 HEAD@{2}: checkout: moving from master to as
d21e8bf (master) HEAD@{3}: checkout: moving from as to master
c597283 HEAD@{4}: reset: moving to @