Git入門:バージョン管理のきほん

困ったときの逃げ道(よくある事故と復旧)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • Git で本当に消えるものと、消えないものの境目
  • よくある事故を症状から引ける逆引き表
  • 状況が分からなくなったときに、まず打つべきコマンド

まず、たいていは戻せる

このコースの最後は、事故からの復旧です。

先に結論を言います。一度でも commit していれば、Git で失われるものはほとんどありません。 ブランチを消しても、reset で履歴を巻き戻しても、コミットそのものはリポジトリの中に残っています。見えなくなっているだけです。

Git はコミットを消しません。消えるのは、コミットを指し示す名前(ブランチ名など)だけです。名前が外れたコミットも、git reflog に記録が残っている限り、そこから拾い直せます。この記録は既定で 90 日ほど保持されます。

一方で、本当に戻せないものもあります。

戻せるもの戻せないもの
commit 済みの変更一度も commit していない編集内容
消したブランチ追跡されていない新規ファイルの削除
reset で巻き戻した履歴git clean で消した未追跡ファイル
amend で書き換えた前のコミットstash を消したあとの一部のケース

この表の右側を見ると、対策が見えてきます。危ないと思ったら、まず commit する。 メッセージが雑でも構いません。あとから 直前のコミットを直す(commit --amend) で整えられます。commit していないものだけが、Git の保護の外にあります。

慣れない操作を試す前に、ディレクトリごとコピーしておくのも有効です。.git を含めて丸ごと複製すれば、何をしても元に戻せます。手荒に見えますが、確実です。

症状から引く逆引き表

事故が起きたときは、原因を分析するより先に、症状から手順を引くほうが速いです。

症状まず打つコマンド読む節
編集を間違えた(commit 前)git restore <ファイル>手元だけで済む事故
ファイルを間違えて add したgit restore --staged <ファイル>手元だけで済む事故
直前の commit のメッセージを間違えたgit commit --amend手元だけで済む事故
commit したが、内容が間違っていたgit reset --soft HEAD~1手元だけで済む事故
push 済みの commit を取り消したいgit revert <ハッシュ>push したあとの事故
main で作業してしまったgit switch -c <新ブランチ>push したあとの事故
ブランチを消してしまったgit reflog から復元見失った作業を取り戻す
pull したら知らない変更が入ったgit reflog から戻す見失った作業を取り戻す
コンフリクトで訳が分からないgit merge --abort詰まったら、いったんやめる
そもそも今どうなっているか不明git statusgit log --oneline詰まったら、いったんやめる

以下、節ごとに順に見ていきます。

手元だけで済む事故

まだ push していない段階の事故は、影響が自分の中だけで閉じています。落ち着いて戻せば誰にも迷惑がかかりません。

commit 前の編集をなかったことにする

まだ commit していない変更は、git restore で捨てられます。

ターミナル

$ git restore src/app.js

add したものを取り消して、変更自体は残したいときは --staged を付けます。

ターミナル

$ git restore --staged src/app.js

全部まとめて捨てるなら次のとおりです。

ターミナル

$ git restore .

この操作だけは戻せません。commit していない編集は Git のどこにも記録されていないためです。実行前に一度 git diff で中身を見てください。詳しくは 変更を取り消す(restore) にあります。

直前の commit のメッセージを直す

typo に気づいたときは、コミットし直す必要はありません。

ターミナル

$ git commit --amend -m "ログインフォームのバリデーションを追加"

ファイルを入れ忘れていたときは、add してから同じコマンドを打ちます。

ターミナル

$ git add src/validate.js $ git commit --amend --no-edit

--no-edit はメッセージを変えずに中身だけ差し替えるという意味です。

--amend はコミットを作り直す操作です。push 済みのコミットにやると、リモートと食い違って push が拒否されます。push する前だけの手段だと覚えてください。

commit したが内容が間違っていた

まだ push していないなら、reset で戻せます。

ターミナル

$ git reset --soft HEAD~1

HEAD は今いるコミットを指す名前で、HEAD~1 はその 1 つ前です。--soft は、コミットだけを取り消して変更内容は add された状態で残すという意味です。ファイルの中身はそのままなので、直してから commit し直せます。

変更ごと完全に消したいときだけ --hard を使います。

ターミナル

$ git reset --hard HEAD~1

これは commit していない編集も一緒に消えます。3 つのモードの違いは コミットを取り消す(reset の 3 モード) で詳しく扱っています。迷ったら --soft を使ってください。

push したあとの事故

ここからは他の人の手元にも影響が出ます。履歴を書き換える手段は使わず、足す方向で直します。

push 済みの commit を取り消したい

ここが分かれ目です。すでに push したコミットには reset を使いません。他の人が持っている履歴と食い違い、強制 push が必要になります。

代わりに revert を使います。

ターミナル

$ git revert a1b2c3d

これは指定したコミットの変更を打ち消す新しいコミットを積む操作です。履歴は書き換わらず、後ろに 1 つ足されます。他の人の手元とも矛盾しません。

エディタが開いてメッセージの確認を求められるので、そのまま保存して閉じます。あとは普通に push するだけです。

ターミナル

$ git push

考え方の詳細は 公開済みの履歴を安全に戻す(revert) にまとめています。

「まだ誰も pull していないはずだから reset でいい」という判断は避けてください。確認が取れないなら revert です。履歴が汚れることより、他人の作業が壊れることのほうが高くつきます。

main で作業してしまった

ブランチを切り忘れて main のまま commit してしまった。よくある事故です。

まだ push していなければ、その場でブランチを作れば解決します。

ターミナル

$ git switch -c feature/login-form

switch -c は、いまいるコミットから新しいブランチを作ってそこへ移動する操作です。コミットはそのまま新しいブランチに属します。

次に main を元の位置へ戻します。

ターミナル

$ git switch main $ git reset --hard origin/main

origin/main は、最後に取得した時点でのリモート側の main を指す名前です。これで main はリモートと同じ状態に戻り、作業内容は feature/login-form に残ります。

まだ commit すらしていない段階で気づいたなら、git switch -c するだけで編集内容ごと新しいブランチへ移ります。

見失った作業を取り戻す

消えたように見えるものは、たいてい名前が外れただけです。git reflog で拾い直します。

ブランチを消した、pull で変になった

ここで git reflog が出てきます。reflog は HEAD がどこを指していたかの移動記録です。

ターミナル

$ git reflog

出力は次のような形です。

プレーンテキスト

a1b2c3d HEAD@{0}: checkout: moving from feature/login-form to main 9f8e7d6 HEAD@{1}: commit: バリデーションを追加 1234abc HEAD@{2}: commit: ログインフォームを追加

左のハッシュが、その時点でのコミットです。消したブランチの最後のコミットもここに残っています。見つけたら、そこからブランチを作り直します。

ターミナル

$ git switch -c feature/login-form 9f8e7d6

pull したら見覚えのない変更が入った、というときも同じです。pull の前の位置を reflog から探し、そこへ戻します。

ターミナル

$ git reset --hard HEAD@{2}

reflog の読み方と、もっと踏み込んだ復旧は 消したはずの作業を取り戻す(reflog) にあります。このコースで最も頼りになる 1 本です。

詰まったら、いったんやめる

途中で分からなくなったら、進めずに戻すのが正解です。

コンフリクトで詰んだ

マージの最中に、どこを直せばいいのか分からなくなることがあります。そのときは無理に進めず、いったん全部やめます。

ターミナル

$ git merge --abort

マージを始める前の状態に完全に戻ります。rebase 中なら次のコマンドです。

ターミナル

$ git rebase --abort

戻したうえで落ち着いて、コンフリクトしているファイルを 1 つずつ確認します。手順は コンフリクトを解決する にあります。そもそも起きにくくする習慣は、1 つ前のレッスンで扱いました。

状況が分からなくなったとき

事故のときにいちばんやってはいけないのは、思い出せないコマンドを次々に試すことです。1 回目の事故は小さくても、そこから復旧を焦ると本当に取り返しがつかなくなります。

分からなくなったら、まず状態を見る 3 つのコマンドを打ってください。

ターミナル

$ git status $ git log --oneline -10 $ git reflog -20

git status は、いまどのブランチにいるか、何が add されているか、マージの途中かどうかを全部教えてくれます。マージ中なら、次に何をすればいいかまで文章で書いてあります。

git log --oneline で履歴の並びを確かめ、git reflog で自分が何をしてきたかを確かめる。この 3 つが読めれば、上の表のどの行に当てはまるかが分かります。

復旧作業に入る前に、作業ディレクトリを丸ごとコピーしておくことを勧めます。復旧の途中でさらに間違えても、コピーから何度でもやり直せます。

コースのおわりに

30 本を通して、ファイルの記録から、履歴の操作、ブランチとマージ、GitHub でのチーム開発までを扱いました。

Git の難しさは、コマンドの数ではありません。手元のファイル、記録待ちの領域、コミットの履歴、リモートという 4 つの場所の間で、いま何がどこにあるのかが見えないことです。git statusgit loggit reflog は、その中を覗くための 3 つの窓です。この 3 つを打つ癖が付いていれば、知らないコマンドが出てきても状況は必ず読めます。

覚えていないコマンドは調べれば出てきます。落ち着いて状態を確かめること、そして危ないと思ったらまず commit すること。この 2 つだけ持ち帰ってください。

この章のポイント
  • commit 済みの変更はほぼ失われない。commit していない編集だけが Git の保護の外にある
  • 事故が起きたら、症状から逆引き表を引く。原因の分析はあとでよい
  • push 前なら reset、push 後なら revert。この分岐だけは間違えない
  • 分からなくなったら git status git log --oneline git reflog の 3 つで状態を確かめる