reset・revert・reflogの実習
目標
resetの3つのモード、amend、revert、reflogを、それぞれ独立したリポジトリで実行して結果を比べ、状況別に何を使うべきかをチートシートにまとめます。出力物は/root/gitx2/の下に置きます。
なぜ重要なのか
取り消しの事故の大半は、コマンドを知らないからではなく、そのコマンドがどこまで上書きするのかを知らずに実行して起きます。--softは失うものがなく、デフォルトの--mixedはステージング状態だけを失い、--hardはコミットされていないすべての変更を失います。危険度は、コマンド名ではなく、「このコマンドがコミットされていない変更を上書きするか」で判断する必要があります。そして、2つ目の軸があります。コミットがすでに他の人のリポジトリにあるなら、そのコミットをなくすあらゆる方法は、他人の履歴を壊します。そのときは、履歴を書き換える代わりに付け足すことが答えです。このラボは、その違いを、言葉ではなく、コミットの数とファイルの有無で確認させてくれます。
ステップ
グローバルなgitの作成者情報がありません。リポジトリを作るたびに、ローカルのuser.email / user.nameを設定しないと、コミットできません。
/root/gitx2/mkrepo.shを作成してchmod +xします。最初の引数($1)で受け取ったパスに(なければ作成して)リポジトリを初期化し、ブランチはmain、コミットは5つ作ります。n個目のコミットはfn.txt(f1.txt … f5.txt)を追加し、タイトルはc1…c5です。採点ツールは、一時ディレクトリでこのスクリプトを実行して確認するので、パスをハードコードせず、必ず"$1"を使ってください。/root/gitx2/amendにスクリプトでリポジトリを作成し、最後のコミットのタイトルだけをc5 fixedに変更します。コミットの数は5つのままである必要があり、reflogにamendの記録が残る必要があり、変更前のハッシュと現在のハッシュは異なる必要があります。/root/gitx2/softにリポジトリを作成し、コミットを1つ取り消します。結果: コミットは4つ、f5.txtがインデックスにステージングされた状態、ワーキングツリーにもf5.txtが存在。/root/gitx2/mixedにリポジトリを作成し、コミットを1つ取り消します。結果: コミットは4つ、f5.txtはステージングされておらず、ワーキングツリーには残っている必要があります(git status --porcelainで??またはM)。/root/gitx2/hardにリポジトリを作成し、コミットを1つ取り消します。結果: コミットは4つ、f5.txtはなくなり、f4.txtは残っており、ワーキングツリーが完全にきれいである必要があります。/root/gitx2/revertにリポジトリを作成し、c3コミットの変更だけを打ち消します。結果: コミットは6つで、タイトルがRevertで始まるコミットが存在し、元のc3コミットも履歴にそのまま残っていて、f3.txtは消え、f1/f2/f4/f5.txtは残っている必要があります。/root/gitx2/reflogにリポジトリを作成します。元に戻す前に、現在のHEADのハッシュを/root/gitx2/before.txtに保存します(ファイルにはハッシュの文字列だけが入っている必要があります)。そのあと、コミットを2つ強制的に飛ばし、参照ログを見て、元の位置に復旧します。結果: HEADが記録しておいたハッシュと同じで、コミットは5つ、reflogにresetの記録が残っている必要があります。/root/gitx2/undo.mdにチートシートを書きます。本文に--soft、--mixed、--hard、revert、reflogがすべて登場する必要があり、次の3行が必要です。pushed=: すでにプッシュして他の人が受け取ったコミットを取り消すときに使うコマンド(1単語)staging_only=: ステージングだけを解除して、ファイルは残すresetのオプションworktree_wiped=: コミットされていない変更まで飛ばすresetのオプション
参考
- スクリプトの骨組み:
mkdir -p "$1"→git -C "$1" init -q -b main→git -C "$1" config user.email ...→ ループでファイルを作成・git add・git commit -m "c$i"。 - ハッシュの保存:
git rev-parse HEAD > /root/gitx2/before.txtで行うと、ハッシュだけが入ります。git logの出力をそのまま入れると、形式チェックに引っかかります。 - 失ったコミットの探し方:
git reflogでHEAD@{1}のような表記を使うか、左に見えるハッシュをそのまま使います。 - よくある間違い1: ステップ3–5を、同じリポジトリで続けて行うことです。各ステップは、別のディレクトリで、きれいな状態から始める必要があります。
- よくある間違い2: ステップ6で
git resetを使うことです。コミット数が減ると失敗します。revertは、過去を消さず、逆のコミットをもう1つ積みます。 git revertはエディターを開くことがあります。--no-editを付けると、そのまま進みます。
リポジトリ作成スクリプトを作る
/root/gitx2/mkrepo.shを作成してchmod +xします。最初の引数($1)で受け取ったパスに(なければ作成して)リポジトリを初期化し、ブランチはmain、コミットは5つ作ります。n個目のコミットはfn.txt(f1.txt … f5.txt)を追加し、タイトルはc1 … c5です。採点ツールは、一時ディレクトリでこのスクリプトを実行して確認するので、パスをハードコードせず、必ず"$1"を使ってください。
/root/gitx2/mkrepo.shは、最初の引数で受け取ったパスに、新しくリポジトリを作成します。パスがまだない場合もあるため、先に作成する必要があり、グローバルな作成者情報がないので、リポジトリごとにローカルの作成者情報を設定しないと、コミットできません。chmod +xを忘れないでください。
最後のコミットメッセージを直す
/root/gitx2/amendにスクリプトでリポジトリを作成し、最後のコミットのタイトルだけをc5 fixedに変更します。コミットの数は5つのままである必要があり、reflogにamendの記録が残る必要があり、変更前のハッシュと現在のハッシュは異なる必要があります。
/root/gitx2/amendにリポジトリを量産し、最後のコミットのタイトルだけを変更します。amendはコミット数を増やしませんが、新しいコミットオブジェクトを作るため、ハッシュが変わり、以前のハッシュはreflogに残ります。
コミットだけを解除してステージングは残す
/root/gitx2/softにリポジトリを作成し、コミットを1つ取り消します。結果: コミットは4つ、f5.txtがインデックスにステージングされた状態、ワーキングツリーにもf5.txtが存在。
/root/gitx2/softで、コミットを1つ取り消しますが、変更内容はインデックスにそのまま残る必要があります。3つのモードのうち、失うものが何もないモードです。
インデックスまで戻す
/root/gitx2/mixedにリポジトリを作成し、コミットを1つ取り消します。結果: コミットは4つ、f5.txtはステージングされておらず、ワーキングツリーには残っている必要があります(git status --porcelainで??または M)。
/root/gitx2/mixedで、コミットを取り消しますが、ステージングは解除され、ファイルはワーキングツリーに残る必要があります。オプションを付けなかったときのデフォルトの動作が、まさにこれです。
ワーキングツリーまで戻す
/root/gitx2/hardにリポジトリを作成し、コミットを1つ取り消します。結果: コミットは4つ、f5.txtはなくなり、f4.txtは残っており、ワーキングツリーが完全にきれいである必要があります。
/root/gitx2/hardで、コミットとともにファイルも消える必要があります。コミットされていない変更を上書きする唯一のモードなので、実行前に必ずもう一度考える必要があります。
履歴を残しながら元に戻す
/root/gitx2/revertにリポジトリを作成し、c3コミットの変更だけを打ち消します。結果: コミットは6つで、タイトルがRevertで始まるコミットが存在し、元のc3コミットも履歴にそのまま残っていて、f3.txtは消え、f1/f2/f4/f5.txtは残っている必要があります。
/root/gitx2/revertで、途中のコミット1つの変更だけを打ち消します。履歴が減ってはいけず、むしろコミットが1つ増える必要があります。すでに他の人に渡ったコミットには、この方法だけが安全です。
失ったコミットを取り戻す
/root/gitx2/reflogにリポジトリを作成します。元に戻す前に、現在のHEADのハッシュを/root/gitx2/before.txtに保存します(ファイルにはハッシュの文字列だけが入っている必要があります)。そのあと、コミットを2つ強制的に飛ばし、参照ログを見て、元の位置に復旧します。結果: HEADが記録しておいたハッシュと同じで、コミットは5つ、reflogにresetの記録が残っている必要があります。
/root/gitx2/reflogで、元に戻す前のハッシュを先にファイルに書いておき、わざとコミットを飛ばしたあと、参照ログを見てその位置に戻ります。before.txtには、ハッシュの文字列だけが入っている必要があります。
状況別のチートシートをまとめる
/root/gitx2/undo.mdにチートシートを書きます。本文に--soft、--mixed、--hard、revert、reflogがすべて登場する必要があり、次の3行が必要です。
pushed=: すでにプッシュして他の人が受け取ったコミットを取り消すときに使うコマンド(1単語)staging_only=: ステージングだけを解除して、ファイルは残すresetのオプションworktree_wiped=: コミットされていない変更まで飛ばすresetのオプション
/root/gitx2/undo.mdに、3つのモードとrevert、reflogをすべて言及し、pushed= / staging_only= / worktree_wiped=の3行で、状況別の正解を書きます。すでにプッシュしたコミットには、履歴を書き換えるコマンドを使いません。