Git Reset と Reflog

GitBeginner

はじめに

Git タイムトラベラーの皆さん、ようこそ!今日は、リポジトリの履歴をかつてないほど自由に制御できる、Git の 2 つの強力な機能 git resetgit reflog について探求します。これらのツールは、Git というタイムマシンの高度な制御装置のようなもので、プロジェクトの異なる状態間を移動したり、一度「失われた」はずの作業を復元したりすることを可能にします。

git reset コマンドは、変更の取り消し、ファイルのステージング解除、さらにはコミット履歴の書き換えまで行える多機能なツールです。しかし、大きな力には大きな責任が伴います。git reset は初心者にとって少し威圧的に感じられるかもしれません。そこで登場するのが git reflog です。これはセーフティネットのような役割を果たし、ブランチの先端などの参照(refs)に加えられたすべての変更を記録しているため、たとえ過激なリセットを行った後でも元の状態に戻すことができます。

この実験では、以下の内容を学習します:

  1. Soft Reset(ソフトリセット):作業ディレクトリやステージングエリアを変更せずに HEAD を移動する
  2. Mixed Reset(ミックスリセット):作業ディレクトリの変更は保持したまま、ステージングを解除する
  3. Hard Reset(ハードリセット):すべての変更を完全に破棄する
  4. Reflog を使用した「破壊的」な操作からの復元
  5. 時間指定リセット:特定の時点の状態にリポジトリを戻す

この実験を終える頃には、これらの強力な Git 機能を安全かつ効果的に使いこなすための確かな知識が身についているはずです。必要に応じていつでも元の場所に戻れるという安心感を持って、自信を持ってリポジトリの履歴を操作できるようになるでしょう。

それでは、git resetreflog のマスターを目指して始めていきましょう!

ワークスペースのセットアップ

リセットやリフログを試す前に、実験台となるいくつかのコミットを含むワークスペースを準備しましょう。新しいディレクトリを作成し、Git リポジトリを初期化して、複数のコミットを持つファイルを追加します。

ターミナルを開き、以下のコマンドを入力してください:

cd ~/project
mkdir git-reset-lab
cd git-reset-lab
git init

次に、いくつかのファイルを作成し、一連のコミットを行います。以下のコマンドをターミナルにコピー&ペーストしてください:

echo "## Git Reset and Reflog Lab" > README.md
git add README.md
git commit -m "Initial commit"

echo "function add(a, b) { return a + b; }" > math.js
git add math.js
git commit -m "Add addition function"

echo "function subtract(a, b) { return a - b; }" >> math.js
git add math.js
git commit -m "Add subtraction function"

echo "function multiply(a, b) { return a * b; }" >> math.js
git add math.js
git commit -m "Add multiplication function"

今行った操作の内容は以下の通りです:

  1. README ファイルを作成し、最初のコミット(Initial commit)を行いました。
  2. 加算関数を含む JavaScript ファイルを作成し、コミットしました。
  3. 同じファイルに減算関数を追加し、コミットしました。
  4. 最後に、乗算関数を追加してコミットしました。

これで、実験に使用するための履歴を持つリポジトリが完成しました!

Soft Reset:HEAD の移動

最初に探索するリセットの種類は「Soft(ソフト)」リセットです。ソフトリセットは、HEAD(および現在のブランチ)を別のコミットに移動させますが、ステージングエリア(インデックス)や作業ディレクトリの内容は変更しません。これは、いくつかのコミットを「取り消したい」けれど、その変更内容は保持して新しいコミットとしてまとめ直したい場合に便利です。

ソフトリセットを試してみましょう:

git reset --soft HEAD~2

このコマンドは、HEAD を 2 つ前のコミット("Add subtraction function" の前のコミット)に戻します。~2 は「現在の HEAD から 2 つ前のコミット」を意味します。~N を使うことで N 個前のコミットに遡ることができます。

ここで git status を実行すると、"Add subtraction function" と "Add multiplication function" で行われた変更が、まとめてステージングされた状態になっていることがわかります。作業ディレクトリ内のファイルは変更されていません。これら 2 つのコミットに含まれていたすべての作業が、1 つの新しいコミットとして記録される準備が整った状態です。

これは、直近のいくつかのコミットを 1 つにまとめたい(「スクワッシュ」したい)場合に非常に役立ちます。数コミット分ソフトリセットで戻り、それらの変更をすべて含んだ新しいコミットを 1 つ作成するだけです。

では、これらの変更を新しいメッセージで再度コミットしましょう:

git commit -m "Add subtraction and multiplication functions"

注意点として、ソフトリセットは変更を破棄しないため一般的には安全ですが、履歴を書き換えることになります。もし元のコミットをすでにリモートにプッシュしている場合、リモートブランチを更新するために強制プッシュ(force push)が必要になり、共同作業者に混乱を招く可能性があります。共有されている履歴を書き換える前には、必ずチームと相談してください。

Mixed Reset:ステージングの解除

次に紹介するのは「Mixed(ミックス)」リセットです。これは、フラグを指定せずに git reset を実行した場合のデフォルトの動作です。ミックスリセットは HEAD を移動させ、それに合わせてステージングエリアを更新しますが、作業ディレクトリには一切手を加えません。

まず、変更を加えてステージングしてみましょう:

echo "function divide(a, b) { return a / b; }" >> math.js
git add math.js

ここで、気が変わってこの変更をまだステージングしたくないと思ったとします。その場合、ミックスリセットを使用します:

git reset HEAD

これにより、変更のステージングは解除されますが、作業ディレクトリ内には変更が保持されます。今 git status を実行すると、math.js が変更されているものの、ステージングはされていない状態であることが確認できます。

ミックスリセットは、変更をステージングした後に「やっぱりまだコミットする準備ができていない」と判断したときに便利です。ステージングする前にもう一度内容を確認したり、さらに修正を加えたりしたい場合に適しています。

ソフトリセットとは異なり、ミックスリセットはステージングエリアを書き換えます。しかし、作業内容が破棄されるわけではなく、すべて作業ディレクトリに残っているため、その意味では依然として安全な操作です。

Hard Reset:変更の破棄

3 つ目の、そして最も強力で注意が必要なリセットが「Hard(ハード)」リセットです。ハードリセットは HEAD を移動させ、ステージングエリアを更新し、さらに作業ディレクトリもそれに合わせて更新します。つまり、リセット先のコミット以降に行われたすべての変更を完全に破棄します。

ハードリセットを試してみましょう:

git add math.js
git commit -m "Add division function"
git status
git reset --hard HEAD~1

ここでは、除算関数をステージングしてコミットした後、前のコミットに対してハードリセットを実行しました。これにより、直前のコミットが「取り消され」、その変更内容も破棄されました。

math.js を確認すると、除算関数が消えているのがわかります。まるで最初から書かなかったかのようです。

ハードリセットは強力ですが危険です。ある作業を完全に捨てて、以前の状態からやり直したいときには非常に便利です。しかし、変更を永久に失う可能性があるため、実行する際は細心の注意を払ってください。

ハードリセットを行う前には、リセット先のコミットが正しいかどうかを必ず再確認してください。確信が持てない場合は、ソフトリセットやミックスリセットを使用するか、実験用に新しいブランチを作成してから行うのが安全です。

Reflog を使用した失われたコミットの復元

さて、もし先ほどの除算関数を破棄するつもりはなかったと気づいたらどうすればいいでしょうか?ここで git reflog が救世主となります。リフログ(reflog)は、ローカルリポジトリで HEAD が移動したすべての履歴を記録したログです。これは「履歴の履歴」のようなもので、リセットのような履歴を書き換えるコマンドの実行記録さえも保持しています。

リフログを見てみましょう:

git reflog

リセットを含む、最近行ったすべてのアクションのリストが表示されるはずです。各エントリには HEAD@{n} という識別子が付いています。

失われたコミットを復元するには、ハードリセットを実行する前の状態にリセットします:

git reset --hard HEAD@{1}

これは、最後のアクション(今回の場合はハードリセット)を行う直前の HEAD の状態にリセットすることを意味します。

ここで math.js を確認してみてください。除算関数が戻っているはずです!

cat math.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function divide(a, b) { return a / b; }

リフログは強力なセーフティネットであり、Git でのほとんどのミスから回復することを可能にします。ただし、リフログはあなたのローカルマシンにのみ存在し、一時的なものである(通常 30 日から 90 日間保持される)ことに注意してください。定期的なバックアップや、リモートリポジトリへのプッシュの代わりになるものではありません。

時間指定リセット

Git では、特定の時点の状態にリポジトリをリセットすることも可能です。これは、戻したい状態がいつ頃だったか大まかに覚えている場合に便利です。

時間指定リセットを試してみましょう:

git reset --hard master@{"1 hour ago"}

これにより、リポジトリは 1 時間前の状態にリセットされます。"yesterday"(昨日)、"2 days ago"(2 日前)、"3 minutes ago"(3 分前)など、さまざまな時間表現を使用できます。

時間指定リセットは、特定のコミットを指定するよりも精度が低くなる可能性があるため、注意して使用してください。実行後は必ずリポジトリの状態を確認し、意図した通りの状態になっているか確かめましょう。

もし期待通りの結果にならなかった場合でも、リフログを使えばいつでも時間指定リセットを取り消すことができることを覚えておいてください。

まとめ

おめでとうございます、Git タイムロードの皆さん!Git の中で最も強力で、かつ慎重に扱うべきコマンドのいくつかをマスターしました。今回学んだ重要な概念を振り返ってみましょう:

  1. Soft Reset(ソフトリセット): ステージングエリアや作業ディレクトリを変更せずに HEAD を移動します。コミットをまとめる(スクワッシュ)のに便利です。
  2. Mixed Reset(ミックスリセット): HEAD を移動しステージングエリアを更新しますが、作業ディレクトリには触れません。ステージングを解除するのに最適です。
  3. Hard Reset(ハードリセット): HEAD、ステージングエリア、作業ディレクトリのすべてを更新します。強力ですが、変更を破棄する可能性があるため注意が必要です。
  4. Reflog(リフログ): HEAD へのすべての変更を記録するセーフティネットで、ほとんどの Git 操作ミスから復元できます。
  5. 時間指定リセット: 特定の時点の状態にリポジトリを戻すことができます。

「大きな力には大きな責任が伴う」という言葉を忘れないでください。これらのコマンドはリポジトリの履歴を驚くほど自由に制御させてくれますが、不注意に使用すると危険です。リセット、特にハードリセットを実行する前には必ず再確認を行い、万が一のときにはリフログが味方になってくれることを思い出してください。

Git の習得を続ける中で、これらのコマンドを安全な環境で練習し、自信を持って使えるようにしていきましょう。正しく使えば、Git のワークフローを大幅に強化する強力な武器になります。

それでは、クリーンで意味のある Git 履歴を目指して、ハッピー・リセッティング!