Git リセット git reset コマンド

git reset コマンドの基本的な使い方(--soft、--mixed、--hard の各オプション)やコミットの指定方法(HEAD~ や HEAD^)、reflog や ORIG_HEAD を使って誤った操作を元に戻す方法などについて。

関連ページ

作成日:2022年8月26日

Git リセットの基本的な考え方

Git は変更履歴を「コミット」と呼ばれる単位で管理します。これは、プロジェクトのある時点での状態をスナップショットとして保存する方法です。

git reset は、このコミットと関連する変更を操作するためのコマンドです。

git reset コマンドには3つの主な使い方があります。

  • Soft Reset: コミットを取り消して変更はインデックス(及びワーキングツリー)に残る方法です。新しいコミットに含める変更を調整する際などに使用します。
  • Mixed Reset: デフォルトのリセットモードで、コミットは取り消されて変更はワーキングツリーに残ります。変更を再ステージングして新しいコミットに含めることができます。
  • Hard Reset: コミットと変更を取り消す方法です。過去のコミットにプロジェクトを戻す際に使用します。変更が完全に削除されます。

Git のリセットは、プロジェクトの履歴を操作する便利な方法ですが、注意深く使用する必要があります。誤って使うとデータを失うことがあるので注意が必要です。

また、他の人と共有しているリポジトリやリモートリポジトリと連携している場合は、慎重に操作することが重要です。※ 基本的にローカルな変更を取り消して元に戻す場合に限って使用します。

以下は具体的なコマンド例です(詳細は後述)。

Soft Reset

以下を実行すると最新のコミットを取り消して1つ前のコミットに戻りますが、変更はステージングエリアに残ります。

% git reset --soft HEAD~

Mixed Reset

以下を実行すると最新のコミットを取り消して1つ前のコミットに戻ります。変更はワークツリーに戻されますが、ステージングは解除されます。

% git reset HEAD~

Hard Reset

以下を実行すると最新のコミットを取り消して1つ前のコミットに戻るコマンドです。変更もすべて削除されます。

% git reset --hard HEAD~

HEAD~ は最新のコミットの親、つまり1世代前のコミットを指します(コミットの指定)。

コミットを取り消す(git reset)

git reset コマンドを使用すると、特定の時点までファイルを巻き戻す(変更を取り消す)ことができます。

[注意]git reset コマンドは、リモートリポジトリに push していない場合にのみ使用します。

一度でもスナップショットをリモートリポジトリに push した場合は、git reset コマンドを使用してはいけません。ローカルな変更を取り消して元に戻す場合に限って使用します。

リモートリポジトリで公開されているコミットに対して行うと、不整合が発生してしまいます。

※ push などを行って他の開発者に公開されているコミットを取り消すことは絶対にしてはいけません。

git reset コマンドは HEAD を移動させることで、ブランチを特定の時点に巻き戻します。

HEAD は現在のブランチを指すポインタです。

そしてブランチはそのブランチの最新のコミットを指すポインタです。

つまり、HEAD は現在のブランチの最新のコミットを指し示すポインタでもあり、HEAD とは現在のブランチが指している最新のコミット(のスナップショット)と捉えることができます。

例えば、最初のコミットを実行すると HEAD が main を指し、main がコミットを指します。

最初のコミットを実行 HEAD HEAD(現在のブランチを指すポインタ) main ブランチ(最新のコミットを指すポインタ) 2931804 コミット

2回目のコミットを実行すると HEAD は main を指し、main が2回目のコミットを指します。

2回目のコミットを実行 HEAD main 2931804 6595577

git commit(コミット)を繰り返すたびに、HEAD とブランチは新しいコミットに自動的に移動します。

git reset コマンドを使うと、HEAD が指すブランチを移動させることができます。

3回目のコミットを実行 HEAD main 2931804 6595577 b1acd1b

git reset コマンドを実行すると、ブランチが指すコミットが変わるので作業のやり直しを行えます。

git reset HEAD~ (または git reset 6595577)を実行 HEAD main HEAD main 2931804 6595577 b1acd1b コミット履歴からコミットが消える

実際には、指定するオプションにより HEAD の位置のみをリセットしたり、インデックスへ登録された内容や作業ディレクトリでの変更をリセットすることができます。

Git のさまざまなツール - リセットコマンド詳説

以下のサイトにもわかりやすい解説があります。

Git インデックスと reset --soft / (--mixed) / --hard を理解する

git reset コマンド

git reset コマンドはコミットした内容を取り消す(戻す)コマンドです。

以下のようにコミットを指定すると、現在のブランチを指定したコミットに戻すことができます。

git reset オプション コミット 

コミットにはハッシュ値や HEAD、@ などを指定することができます(デフォルトは HEAD)。

コミットを省略した場合は、HEAD を指定したことになり、HEAD は最新のコミットを指すので、HEAD の位置は変わりません。

コミットの指定ではブランチや HEAD に ~^ を付けてコミットを相対的に指定することができます。例えば、HEAD~ は最新の1つ前のコミットを表します(コミットの指定

また、git reset には3種類の主要なオプションがあり、オプションにより動作が変わってきます。

3つのオプションのいずれも HEAD の位置を指定した位置に戻す(コミット履歴を過去の状態に戻す)動作は共通ですが、インデックスとワーキングツリーに対する動作が異なります。

オプションを省略した場合は --mixed オプションを指定したのと同じです。

オプション 説明
--soft

--soft オプションを使ってリセットすると、ただ単に HEAD の位置が移動します。

つまり、コミット履歴を過去の状態に戻す(HEAD の位置を移動する)ことはしますが、変更内容は現在のワーキングツリーとインデックスに残ります。

これにより、変更をインデックスに戻すことができます。コミットを取り消して、新しいコミットメッセージを付けて再コミットしたり、変更を修正して再度コミットしたりすることができます。

--mixed

--mixed オプションはデフォルトのオプションです(オプションを指定しない場合と同じ動作です)。

このモードでは、過去のコミットに戻る(HEAD の位置を移動する)と同時に、インデックスも過去のコミットの状態に戻されます。

これにより、選択したコミットまでの変更内容がインデックスから削除され、ワーキングツリーには変更内容が残ります。

つまり、変更内容はインデックスからワーキングツリーに戻されます。これにより、変更を一部取り消したり、変更内容を再度選択してステージングし、新しいコミットとして保存することができます。

--hard

--hard オプションを使用すると、過去のコミットに戻る(HEAD の位置を移動する)だけでなく、選択したコミットまでの変更内容がすべて取り消され、ワーキングツリーとインデックスが指定したコミットの状態に戻されます。

このオプションを使用すると変更が完全に削除されるため、慎重に扱う必要があります。

これらのオプションを理解して使い分けることで、過去のコミットを操作する際に柔軟に対応できるようになります。

git reset -h で git reset コマンドのヘルプを表示できます。

% git reset -h
usage: git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]
  or: git reset [-q] [<tree-ish>] [--] <pathspec>...
  or: git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]
  or: git reset --patch [<tree-ish>] [--] [<pathspec>...]

  -q, --quiet           be quiet, only report errors
  --no-refresh          skip refreshing the index after reset
  --mixed               reset HEAD and index
  --soft                reset only HEAD
  --hard                reset HEAD, index and working tree
  --merge               reset HEAD, index and working tree
  --keep                reset HEAD but keep local changes
  --recurse-submodules[=<reset>]
                        control recursive updating of submodules
  -p, --patch           select hunks interactively
  -N, --intent-to-add   record only the fact that removed paths will be added later
  --pathspec-from-file <file>
                        read pathspec from file
  --pathspec-file-nul   with --pathspec-from-file, pathspec elements are separated with NUL character
git reset の取り消し

誤ってリセットしてしまった場合は、以下を実行してリセット前の状態に戻すことができます。

git reset でリセットしたものを、git reset で戻します(リセットします)。

% git reset --hard ORIG_HEAD

ORIG_HEAD は Git リポジトリ内で使用される特別な参照の一つで、一般的にリポジトリ内で行われた一連のリセットやマージなどの操作の中で、直前に行われた操作前のコミットを参照します。

具体的にはリセットやマージ、リベースなどの操作を行うと、それによって変更される前のコミットが一時的に ORIG_HEAD に保存されます。

主に ORIG_HEAD はリセットやマージなどの変更を取り消す場合に使用します。

例えば、誤ってコミットをリセットしたり、マージをやり直したりする際に、以前の状態を復元するために ORIG_HEAD を利用できます。

但し、ORIG_HEAD は一時的なリファレンスであるため、新しい操作が行われると上書きされ、以前の ORIG_HEAD の内容は失われます。

関連項目

--soft オプション

--soft オプションを指定した場合、HEAD が指すブランチの移動だけが行われ、インデックス(ステージングエリア)や作業ディレクトリ(ワーキングツリー)は操作されません。

つまり、コミット履歴を過去の状態に戻す(HEAD の位置を移動する)ことはしますが、変更内容は現在のワーキングツリーとインデックスに残ります。

これにより、変更をインデックスに戻すことができます。コミットを取り消して、新しいコミットメッセージを付けて再コミットしたり、変更を修正して再度コミットしたりすることができます。

git reset コマンドの動作を確認するため、プロジェクトを作成して Git で初期化し、ファイルを3つ作成して、それぞれコミットします。

git ls-files コマンドはインデックスの状態を表示するコマンドです。オプションを指定しない場合はその時点でインデックスに属しているファイルを表示します(通常の操作で使用することはありません)。

git init reset_sample
Initialized empty Git repository in /.../reset_sample/.git/
% cd reset_sample

// foo.txt を作成してコミット
% echo foo > foo.txt
% git add foo.txt
% git commit -m "1st commit"  //1番目のコミット
[main (root-commit) dffc18c] 1st commit
 1 file changed, 1 insertion(+)
 create mode 100644 foo.txt
% git ls-files // インデックスに現在属しているファイルを表示
foo.txt

// bar.txt を作成してコミット
% echo bar > bar.txt
% git add bar.txt
% git commit -m "2nd commit"  //2番目のコミット
[main 7f7f24c] 2nd commit
 1 file changed, 1 insertion(+)
 create mode 100644 bar.txt
% git ls-files // インデックスに現在属しているファイルを表示
bar.txt
foo.txt

// baz.txt を作成してコミット
% echo baz > baz.txt
% git add baz.txt
% git commit -m "3rd commit"  //3番目のコミット
[main aaf42e0] 3rd commit
 1 file changed, 1 insertion(+)
 create mode 100644 baz.txt
% git ls-files // インデックスに現在属しているファイルを表示
bar.txt
baz.txt
foo.txt

% ls // ワーキングツリーのファイルを表示
bar.txt baz.txt foo.txt

% git status
On branch main
nothing to commit, working tree clean

上記の操作により HEAD は main を指し、main が3番目のコミットを指しています。

以下の図は状態を表すイメージです(状態を正確に表してはいるわけではありません)。

リセット前

Git リポジトリ 1番目のコミット 2番目のコミット 3番目のコミット HEAD main dffc18c 7f7f24c aaf42e0 インデックス ワーキングツリー git commit foo.txt git add foo.txt git commit foo.txt bar.txt git add foo.txt bar.txt git commit foo.txt bar.txt baz.txt git add foo.txt bar.txt baz.txt

現在、HEAD は main を指し、main はコミット aaf42e0 を指しています。

% git log --oneline
aaf42e0 (HEAD -> main) 3rd commit    //3番目のコミット
7f7f24c 2nd commit    //2番目のコミット
dffc18c 1st commit    //1番目のコミット

git reset --soft

以下の git reset --soft コマンドを実行すると、main が指すコミットを2つ前のコミットに移動します。

HEAD~2 は HEAD の2つ前のコミット(dffc18c)を表します(コミットの指定)。

% git reset --soft HEAD~2  // git reset --soft dffc18c と同じこと

コマンドを実行してもレスポンスはありません(何も表示されません)。

ログを確認すると、main が2つ前のコミット(1番目のコミット dffc18c)を指し、2番目と3番目のコミット(7f7f24c と aaf42e0)はコミット履歴から消えています。

% git log --oneline
dffc18c (HEAD -> main) 1st commit  // main が1番目のコミット dffc18c を指す

--soft オプションは、HEAD の位置を移動しますが、変更内容は現在のワーキングツリーとインデックスに残ります。

そのため、ステータスを確認すると(2番目と3番目のコミットが取り消されたため)インデックスの bar.txt と baz.txt はコミットされていない状態(Changes to be committed)になっています。

% git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   bar.txt
        new file:   baz.txt

--soft オプションでリセット後

git reset --soft HEAD~2 により、2番目と3番目のコミットが取り消されます。

ワーキングツリーのファイルとインデックスに登録されたスナップショットは変わりませんが、bar.txt と baz.txt はコミットされていない状態です。

HEAD の位置が移動 Git リポジトリ 1番目のコミット HEAD main HEAD main dffc18c 7f7f24c aaf42e0 変更内容は現在のワーキングツリーとインデックスに残る インデックス ワーキングツリー git commit foo.txt git add foo.txt foo.txt bar.txt git add foo.txt bar.txt foo.txt bar.txt baz.txt git add foo.txt bar.txt baz.txt

ここでコミットを実行すると、bar.txt と baz.txt が1つのコミットにまとまります。

% git commit -m "add bar.txt and baz.txt"
[main 592e1c4] add bar.txt and baz.txt
 2 files changed, 2 insertions(+)
 create mode 100644 bar.txt
 create mode 100644 baz.txt

作業ディレクトリがクリーンになり、新たなコミットが作成されてコミット履歴が2件になります。

% git status
On branch main
nothing to commit, working tree clean

% git log --oneline
592e1c4 (HEAD -> main) add bar.txt and baz.txt
dffc18c 1st commit
Git リポジトリ 1番目のコミット 2番目のコミット HEAD main dffc18c 592e1c4 インデックス ワーキングツリー git commit foo.txt git add foo.txt git commit foo.txt bar.txt baz.txt git add foo.txt bar.txt baz.txt

--mixed オプション

-mixed オプションは、過去のコミットに戻る(HEAD の位置を移動する)と同時に、インデックス(ステージングエリア)も影響を受けます。

--mixed オプションを指定した場合、--soft オプションを指定した場合の動作に加え、インデックスを指定したコミットの状態と一致させる処理が実行されます。

ワーキングツリー(作業ディレクトリ)の状態は変わらないので、HEAD とインデックスの状態だけを巻き戻したい場合に利用できます。

git reset --mixed

現在、以下のように --soft オプションで作成した初期状態(3回のコミット実行後)になっています。

リセット前

Git リポジトリ 1番目のコミット 2番目のコミット 3番目のコミット HEAD main dffc18c 7f7f24c aaf42e0 インデックス ワーキングツリー git commit foo.txt git add foo.txt git commit foo.txt bar.txt git add foo.txt bar.txt git commit foo.txt bar.txt baz.txt git add foo.txt bar.txt baz.txt

以下の git reset --mixed コマンドを実行すると、main が指すコミットを2つ前のコミット(dffc18c)に移動し、インデックスも2つ前のコミットの状態に戻します。

--mixed オプションはデフォルトなのでオプションを省略すると --mixed オプションになります。

% git reset HEAD~2 // git reset --mixed HEAD~2 と同じこと

コマンドを実行してもレスポンスはありません(何も表示されません)。

ログを確認すると、 --soft オプションの場合と同様、main が2つ前のコミット(dffc18c)を指し、2番目と3番目のコミット(7f7f24c と aaf42e0)はコミット履歴から消えています。

% git log --oneline
dffc18c (HEAD -> main) 1st commit

--mixed オプションは過去のコミットに戻る(HEAD の位置を移動する)と同時に、インデックスも過去のコミットの状態に戻されます。これにより、選択したコミットまでの変更内容がインデックスから削除され、ワーキングツリーには変更内容が残ります。

そのためステータスを確認すると、bar.txt と baz.txt はステージされていない状態になっています。

% git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        bar.txt
        baz.txt

nothing added to commit but untracked files present (use "git add" to track)

--mixed オプションでリセット後

git reset HEAD~2 により 2番目と3番目のコミットが取り消されます。

インデックスも2つ前のコミットの状態に戻り、bar.txt と baz.txt はステージされていない(インデックスに登録されていない)状態です。

ワーキングツリー(作業ディレクトリ)に変更はありません。

HEAD の位置が移動 Git リポジトリ 1番目のコミット HEAD main HEAD main dffc18c 7f7f24c aaf42e0 インデックスは過去のコミットの状態に戻される 変更内容はインデックスからワーキングツリーに戻される インデックス ワーキングツリー git commit foo.txt git add foo.txt foo.txt foo.txt bar.txt foo.txt foo.txt bar.txt baz.txt

--mixed オプションはステージしたものを取り消したい(不要なファイルをステージしてしまい、それを除外したい)場合などに使用できます。

ワーキングツリーはそのままなので、作業はそのまま続行でき、再度必要なファイルだけをステージすることができます。

以下では bar.txt をワーキングツリーから削除して baz.txt のみをステージに追加してコミットしています。

このように変更を一部取り消したり、変更内容を再度選択してステージングし、新しいコミットとして保存することができます。

% rm bar.txt  // bar.txt を削除

% git add baz.txt   // baz.txt をステージに追加

% git commit -m "baz.txt added"   // コミット
[main 0534783] baz.txt added
 1 file changed, 1 insertion(+)
 create mode 100644 baz.txt

% git status
On branch main
nothing to commit, working tree clean

% git log --oneline
0534783 (HEAD -> main) baz.txt added
dffc18c 1st commit
Git リポジトリ 1番目のコミット 2番目のコミット HEAD main dffc18c 0534783 インデックス ワーキングツリー git commit foo.txt git add foo.txt git commit foo.txt baz.txt git add foo.txt baz.txt

--hard オプション

--hard オプションを使用すると、選択したコミットまでの履歴、インデックスの変更、さらにはワーキングツリーの変更まで全てが取り消されます。

--hard オプションを指定した場合、--mixed オプションを指定した場合の動作に加え、ワーキングツリーを指定したコミットの状態と一致させる処理が実行されます。

言い換えると、インデックスとワーキングツリーは指定したコミットの状態と同じになるようにリセットされるので、指定したコミット以降のインデックスとワーキングツリーの変更は削除されます。

--hard は、インデックスにもワーキングツリーにも何も残らなくて良いので、コミットをまるごと消したいという場合に使用するオプションです。

また、一度もコミットしていない変更は取り戻すことができないので注意が必要です。

例えば、ステージに追加しただけでコミットしていないファイルの追加や変更、ワーキングツリーのみでのファイルの追加や変更は取り戻すことができなくなります。

[重要] ORIG_HEAD や reflog を使ってリセット前の状態に戻すことができますが、その場合でも、一度もコミットしていない変更やファイルは取り戻すことができなくなります

git reset --hard

現在、以下のように --soft オプションで作成した初期状態(3回のコミット実行後)になっています。

リセット前

Git リポジトリ 1番目のコミット 2番目のコミット 3番目のコミット HEAD main dffc18c 7f7f24c aaf42e0 インデックス ワーキングツリー git commit foo.txt git add foo.txt git commit foo.txt bar.txt git add foo.txt bar.txt git commit foo.txt bar.txt baz.txt git add foo.txt bar.txt baz.txt

以下の git reset --hard コマンドを実行すると、main が指すコミットを2つ前のコミット(dffc18c)に移動し、インデックスとワーキングツリーも2つ前のコミットの状態に戻します。

% git reset --hard HEAD~2  // git reset --hard dffc18c と同じこと
HEAD is now at dffc18c 1st commit

上記を実行すると、最新及びその1つ前のコミットを破棄します。

main と HEAD が2つ前のコミット(dffc18c)を指すようになり、インデックスとワーキングツリーがコミット dffc18c を記録したときの状態になります。

% git log --oneline // コミット履歴
dffc18c (HEAD -> main) 1st commit

% git status  // 作業ディレクトリはクリーン
On branch main
nothing to commit, working tree clean

% git ls-files  // インデックスに登録されているファイル
foo.txt

--hard オプションでリセット後

選択したコミット以降の変更は完全に失われ、選択したコミットに戻ることができます。

HEAD の位置が移動 HEAD main 7f7f24c aaf42e0 Git リポジトリ 1番目のコミット HEAD main dffc18c インデックス ワーキングツリー 選択したコミットまでの変更内容がすべて取り消され、 ワーキングツリーとインデックスが指定したコミットの状態に戻される git commit foo.txt git add foo.txt

直前のコミットやマージを取り消す

直前のコミットやマージを取り消すには以下を実行します。但し、直前のコミットやマージ以降の(まだコミットされていない)変更は失われます。

% git reset --hard HEAD~  // git reset --hard HEAD^ でも同じこと

コミット後の変更を全部取り消す

コミット後に行った変更(ワーキングツリーでの変更やインデックスへの追加)を全て取り消すには以下を実行します。但し、取り消した変更を取り戻すことはできません(全て失われます)。

% git reset --hard HEAD

コミットの指定(HEAD~ や HEAD^)

Git でコミットを指定する際、ハッシュ値で指定する以外に HEAD やブランチに ~(チルダ)や ^(キャレット)を使って相対的に指定することができます。

指定 意味
~ 1世代前の親(~1 と同じ)。~~ は2世代前
^ 1番目の親(^1 と同じ)。^^ は1番目の親の1番目の親(2世代前の親と同じ)
~n n 世代前の親(n が1の場合は省略可)
^n n 番目の親(n が1の場合は省略可)。^n による指定は親が複数存在する場合に親を選択するという状況で使用します。

また、~~~2~~~~3 のように表すこともできます。

HEAD は現在のブランチの最新のコミットを指すので、HEAD~ は最新のコミットの親(1世代前のコミット)を指し、HEAD~~HEAD~2 は2世代前の親(コミット)を指します。

また、HEAD^^ は1番目の親の1番目の親を指すので、結果として2世代前の親を指す HEAD~2HEAD~~ と同じですが、HEAD^2 は2番目の親を指すので、HEAD^^HEAD~2 とは異なります。

以下は、コミットを HEAD やブランチを使って指定する例です。

% git log --oneline --graph
*   fb8269c (HEAD -> main) Merge branch 'topic'
|\
| * 017e938 (topic) additional text for bar.txt
| * 55f5fdc bar.txt added at experiment branch
* | 74f048c goodbye added to foo.txt
|/
* 4dad07d extra text added to foo.txt
* e0cb10e text added to foo.txt
* 8b91c87 first commit

例えば、コミットの 55f5fdcHEAD^2~main^2~topic~topic^ と指定できます。

// 以下全て同じこと
% git show HEAD^2~ --oneline
% git show main^2~ --oneline
% git show topic~ --oneline
% git show topic^ --oneline
HEAD main 8b91c87 HEAD^^^^ HEAD~~~~ HEAD~4 main~4 topic~4 e0cb10e HEAD^^^ HEAD~~~ HEAD~3 main~3 topic~3 4dad07d HEAD^^ HEAD~~ HEAD~2 main~2 topic~2 55f5fdc HEAD^2~ main^2~ topic~ topic^ 74f048c HEAD^ or HEAD~ main^ or main~ fb8269c HEAD main 017e938 topic HEAD^2 topic

@(HEAD のエイリアス)

大文字4文字の HEAD のエイリアスとして @ が用意されています。

以下はいずれも同じことです。

% git show HEAD^^ --oneline
% git show @^^ --oneline
% git show @~2 --oneline

間違えた操作を元に戻す

誤ってコミットを取り消したり、ブランチを削除したなど操作を間違えた場合、reflog で操作履歴を確認し、git reset でその前の状態にリセットすることで元に戻すことができます。

以下が間違えた操作を元に戻す手順です。

  1. git reflog で操作履歴を確認
  2. 戻りたい位置を指定して git reset で戻す

reflog

reflog(リファレンスログ)は、リポジトリ内で行われたリファレンスの変更履歴を記録する仕組みです。リファレンスが指すコミットが変更されるたびに、その変更履歴が reflog に記録されます。

reflog はローカルリポジトリで行ったあらゆることが時系列に沿って記録されている履歴だと考えることができます。Git は作業をしている間、HEAD を変更する度に、HEAD がどこを指しているかを裏側で記録しています。 コミットをしたり、ブランチを変更したりする度に、reflog は更新されます。

誤ってコミットを取り消してしまったり、誤ってブランチを削除してしまった場合など、間違えた操作を元に戻す際に利用できます。

git reflog (reflog の表示)

reflog を表示するには、git reflog コマンドを実行します。

% git reflog  // reflog を表示
373e60a (HEAD -> main) HEAD@{0}: commit: hello.txt added
0fc2599 (topic) HEAD@{1}: merge topic: Fast-forward
4ca6804 HEAD@{2}: checkout: moving from topic to main
0fc2599 (topic) HEAD@{3}: commit: topic.txt updated
b03daf1 HEAD@{4}: commit: topic.txt added
4ca6804 HEAD@{5}: checkout: moving from main to topic
4ca6804 HEAD@{6}: commit: content update
9c0ce61 HEAD@{7}: commit (initial): initial commit

git reflog コマンドの出力は複数のカラムから成り、一般的には、以下のようなフォーマットです。

コミットID (リファレンス名) HEAD@{リビジョン番号}: 操作 : コミットメッセージ
カラム 説明
コミットID 変更が行われたコミットのID(ハッシュ値)
リファレンス名 特定のリファレンス(ブランチ)がどのコミットを指しているか。例えば、(HEAD -> main)は現在のブランチが main にいることを示しています。
リビジョン番号 HEAD からのリビジョン番号(直近の変更が0から始まる)。HEAD@{n} は最新の履歴を「0」として、そこから何番目の履歴かを表しています
操作 実行されたコマンドや行われた操作(コミット、マージ、ブランチの作成など)。
処理内容 行われた処理内容やコミットメッセージ

git log -g

git log コマンドに -g オプションを指定して実行すると、reflog を通常のログ出力と同じ形式で出力して更に詳細な情報が表示されます。

% git log -g -3  // reflog を通常のログ出力と同じ形式で出力
commit 373e60ae3f798b56a3f5f70ecf642fb5519c63c9 (HEAD -> main)
Reflog: HEAD@{0} (foo <foo@example.com>)
Reflog message: commit: hello.txt added
Author: foo <foo@example.com>
Date:   Fri Aug 25 09:38:45 2023 +0900

    hello.txt added

commit 0fc259932b361d127159edf13da51a193a76dcce (topic)
Reflog: HEAD@{1} (foo <foo@example.com>)
Reflog message: merge topic: Fast-forward
Author: foo <foo@example.com>
Date:   Fri Aug 25 09:35:15 2023 +0900

    topic.txt updated

commit 4ca680483645598503c5b9d332e0c940972e03e2
Reflog: HEAD@{2} (foo <foo@example.com>)
Reflog message: checkout: moving from topic to main
Author: foo <foo@example.com>
Date:   Fri Aug 25 09:30:54 2023 +0900

    content update

git reflog と以下の git log コマンドは同じ出力になります。

% git log -g --abbrev-commit --pretty=onelin
% git log -g --oneline

操作を元に戻す

間違った操作を行ってしまった場合、その操作の前の状態に戻すためには、reflog で取り消したい操作の1つ前の識別子(ハッシュ値または HEAD@{n})を見つけます。

そして git reset コマンドに reflog で見つけた識別子を指定して間違った操作を行う前の状態に戻します。

例えば、以下のようなコミット履歴があり、誤って git reset --hard で 4ca6804 にリセットした場合、

% git log --oneline
373e60a (HEAD -> main) hello.txt added
0fc2599 (topic) topic.txt updated
b03daf1 topic.txt added
4ca6804 content update
9c0ce61 initial commit

% git reset --hard 4ca6804  // リセット
HEAD is now at 4ca6804 content update

% git log --oneline  // 4ca6804 より後の変更は失われます
4ca6804 (HEAD -> main) content update
9c0ce61 initial commit

リセット操作の前の状態に戻すには、まず、git reflog を実行して操作履歴を確認します。

% git reflog  // 操作履歴を確認
4ca6804 (HEAD -> main) HEAD@{0}: reset: moving to 4ca6804  // 誤って実行したリセット操作
373e60a HEAD@{1}: commit: hello.txt added // 1つ前に戻せば、リセット前の状態に戻る
0fc2599 (topic) HEAD@{2}: merge topic: Fast-forward
4ca6804 (HEAD -> main) HEAD@{3}: checkout: moving from topic to main
0fc2599 (topic) HEAD@{4}: commit: topic.txt updated
b03daf1 HEAD@{5}: commit: topic.txt added
4ca6804 (HEAD -> main) HEAD@{6}: checkout: moving from main to topic
4ca6804 (HEAD -> main) HEAD@{7}: commit: content update
9c0ce61 HEAD@{8}: commit (initial): initial commit

誤って実行したリセット操作は 4ca6804 (HEAD -> main) HEAD@{0} なので、その1つ前の 373e60a HEAD@{1} に戻せば、誤ったリセット操作の前の状態に戻すことができます。

373e60a HEAD@{1} に戻すには、git reset --hard にコミットのハッシュ値 373e60a または HEAD@{1} を指定します。

% git reset --hard HEAD@{1}  // または  git reset --hard 373e60a
HEAD is now at 373e60a hello.txt added

% git log --oneline  // 戻っている
373e60a (HEAD -> main) hello.txt added
0fc2599 (topic) topic.txt updated
b03daf1 topic.txt added
4ca6804 content update
9c0ce61 initial commit
reflog_sample %

また、この場合は、直前のリセット操作を取り消すので、git reset --hard ORIG_HEAD でも元に戻すことができます。

reflog と ORIG_HEAD

git reset --hard ORIG_HEAD と git reflog で操作履歴を調べてから git reset --hard で操作を取り消す方法は、基本的に同じ目的を達成するための異なるアプローチですが、いくつか違いがあります。

git reset --hard ORIG_HEAD

ORIG_HEAD は、最後に行った git reset や git merge などの操作前のコミットを指します。これにより、直前の操作を元に戻すことができます。

例えば、マージやリセットなどを行った直後に git reset --hard ORIG_HEAD を実行すると、直前の操作を取り消して元の状態に戻ります。

git reflog を使った方法

git reflog はリポジトリ内のすべての操作履歴を保持するログで、git reflog コマンドを使用して操作履歴を調べ、特定の操作に対してリセットや復元を行うことができます。

違いの主なポイント

git reset --hard ORIG_HEAD は、直前の操作を簡単に取り消す方法ですが、直前の操作以外の操作を取り消す際には使えません(直前の操作に対してのみ有効で、それが直前のリセットやマージなどである場合に限られます)。

一方で、git reflog を使用すると、過去の任意の操作を取り消すことができます(リポジトリの全体的な操作履歴に対して有効です)。