- Git は、ミスの状況名がわからないと解決法を検索しにくいため、よくある復旧シナリオ別のコマンドをすぐ引いて使えるように整理した資料
- リポジトリを大きく壊したり、削除・マージのミスを取り消したいときは
git reflog で作業履歴を探し、git reset HEAD@{index} で以前の状態に戻れる
- 最後のコミットへの小さな修正やメッセージ変更は
git commit --amend で素早く処理できるが、すでに公開・共有ブランチへプッシュしたコミットには使ってはいけない
- 間違ったブランチにコミットした場合は、
reset、stash、checkout、stash pop の流れや cherry-pick で移せるが、プッシュ済みかどうかで安全な方法が変わる
- リモートリポジトリの状態へ強制復旧する
reset --hard と git clean -d --force は、追跡されていないファイルまで削除しうるため、実行前の確認が必要
Git のミスを復旧しにくい理由
- Git はミスしやすく、問題を直すには必要な概念の名前をすでに知っていないと検索できないことが多い
- この資料は、Git でよく遭遇する悪い状況を平易な英語とコマンド例で整理している
- 完全なリファレンスではなく、同じ作業を処理する別の方法がある場合もある
リポジトリを過去の状態に戻す
- リポジトリを大きく壊したり、削除した内容を復活させたいときは
git reflog で作業履歴を確認する
git reflog
# すべてのブランチにまたがる Git 作業一覧を見られる
# 各項目は HEAD@{index} 形式のインデックスを持つ
git reset HEAD@{index}
reflog は次のような状況で使える
- 誤って削除した内容の復旧
- リポジトリを壊した試行の除去
- 悪いマージ後の復旧
- 実際に動いていた時点へ戻る
最後のコミットを修正する方法
- 最後のコミット直後に小さな修正を追加したいなら
git commit --amend --no-edit を使う
# 変更後
git add . # または個別ファイルを追加
git commit --amend --no-edit
- このコマンドは最後のコミットに新しい変更を含める
- 同じ作業を新しいコミットとして作成してから
rebase -i でまとめることもできるが、amend のほうがはるかに速い
- 公開・共有ブランチにすでにプッシュされたコミットには
amend を使ってはいけない
- 最後のコミットメッセージだけを変えたい場合は、次のコマンドを使う
git commit --amend
# 案内に従ってコミットメッセージを変更
間違ったブランチにコミットしてしまったとき
master に誤ってコミットしたが新しいブランチにあるべきだったなら、現在の状態から新しいブランチを作り、master の最後のコミットを削除する
git branch some-new-branch-name
git reset HEAD~ --hard
git checkout some-new-branch-name
- この方法は、すでに公開・共有ブランチへプッシュしたコミットには適していない
- 別の試行を先にしていた場合は、
HEAD~ の代わりに HEAD@{number-of-commits-back} 形式が必要になることがある
- 完全に間違ったブランチへコミットした場合は、
reset --soft、stash、checkout、stash pop の流れで移せる
git reset HEAD~ --soft
git stash
git checkout name-of-the-correct-branch
git stash pop
git add . # または個別ファイルを追加
git commit -m "your message here"
- 同じ状況で
cherry-pick を使う方法もある
git checkout name-of-the-correct-branch
git cherry-pick master
git checkout master
git reset HEAD~ --hard
変更内容と過去のコミットを取り消す
- ファイルを変更したのに
git diff が空なら、すでにステージングしている可能性があるため --staged フラグが必要
git diff --staged
- 数コミット前の変更を取り消したいなら
git log でハッシュを見つけて git revert を実行する
git log
git revert [saved hash]
git revert はそのコミットを打ち消す新しいコミットを作る
- コミット全体ではなく特定のファイルだけを戻すときは、ファイルの以前のバージョンをインデックスに載せてからコミットする流れを使う
git log
git checkout [saved hash] -- path/to/file
git commit -m "Wow, you don't have to copy-paste to undo"
リモートリポジトリの状態へ強制復旧する
- リポジトリを消して再度クローンする代わりに、リモートリポジトリの状態に合わせる Git コマンドを使える
git fetch origin
git checkout master
git reset --hard origin/master
git clean -d --force
git reset --hard origin/master はブランチをリモートの master 状態に合わせる
git clean -d --force は追跡されていないファイルとディレクトリを削除する
- 壊れたブランチごとに
checkout、reset、clean を繰り返す必要がある
- これらの作業は破壊的で復旧不能なので、実行前に注意が必要
範囲と貢献
- この資料は完全な Git リファレンスではなく、試行錯誤で見つけた実践的な復旧手順集
- 翻訳追加を手伝いたいなら GitHub に PR を送れる
1件のコメント
Hacker Newsの意見
変えたい点がいくつかある。常に
git checkoutの代わりに**git switchを使い、reset --hard**はできるだけ避けると思うたとえば「新しいブランチにあるべきコミットを誤ってmasterにしてしまった場合」は、
git branch some-new-branch-name、git switch -d HEAD~、git switch -C master、git switch some-new-branch-nameの順で対処できる「間違ったブランチにコミットした場合」も、正しいブランチへ
git switchした後、git cherry-pick master、git switch -d master~、git switch -C masterで対処すると思う「すべて諦めてリモートの状態に戻す」場合も、
git fetch origin、git restore -WS .、git clean -d --force、git switch -d origin/master、git switch -C masterのように構成すると思うGitのモデルをよく理解していても、こうしたレシピは直感的ではなく、各コマンド特有の挙動まで知っている必要がある
たとえば
git switch -c some-new-branch-nameはすでにあるが、ブランチだけを別のコミットへ移すgit move-branch master HEAD~のようなコマンドのほうが直感的だと思うgit checkoutの代わりにgit switchとgit restoreを使う方向に反対するつもりはないが、これらのコマンドはGit v2.23で導入され、そのリリースは約5年前のものだヘルプページには今でも
THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.という警告が入っているもちろんこの警告はコマンドができたほぼ当初からあり、5年も経ったなら、もう実験的と呼ばなくてもよい時期になっているように思う
それでも
git checkoutのほうが後方互換性が高く、もともとこのWebサイトが作られた時期のGitの使い方を反映しているとも見なせるhttps://github.com/git/git/blob/757161efcca150a9a96b312d9e78...
https://github.com/git/git/releases/tag/v2.23.0
https://github.com/git/git/commit/4e43b7ff1ea4b6f16b93a432b6...
git add .**を教えるのはあまりよくない特定のファイルを明示して追加するほうが、あとで「あ、やってしまった」となる余地が少ない
switchがcheckoutより優れている理由と、reset --hardを使うべきでない理由を知りたいreset --hardを避けろ」というのがどういう意味なのかわからない実際になぜ不十分なのか気になるし、
alias git-restore-file='git restore --source=HEAD --'と一緒にかなり頻繁に使っているが、うまく動いているように思うGitを学ぶときの基本ルートとして、GUIクライアントを勧め始めるべきだ
そうすればこうした問題の3分の1は解決し、さらに別の3分の1はそもそも発生しないはずだ
後でコマンドラインのほうが速いと判断したら、そのとき使えばよい。まずは人々がツリーとやり取りする方法を視覚的に見る必要がある
個人的にはfork.devが好きだが、最近のクライアントはだいたい似ている
Magitを使うと、「cherry-pickの中断」のような操作をインターフェース内で見つけられ、他の「Xを中断」操作と同じショートカットを使う
Gitのコマンドラインだけを使わなければならなかったら、どこから始めればよいのか見当がつかなかったと思う
インタラクティブリベース中に削除してはいけないコミットを消したこともあるが、リベース開始時点ごとにreflogからアクセス可能なスナップショットが作られるので、誤ったリベースを戻すのは安全だ
Magitのreflog UIはlog UIと同じなので初見でも迷わなかったが、Git CLIだったら何が起きているのかわからなかった可能性が高い
jjを引数なしで実行すると、コミットツリーの関連部分と現在位置を表示してくれる点がよい今作業中のコミット、紐づいたブランチと履歴、リポジトリ内の他のアクティブなブランチ、各コミットのメッセージと変更の有無を一目で見られるので、方向を立て直すのに素晴らしい
だがほとんど誰も聞かず、結局CLIで苦労し、ブランチを壊し、mainに遅れを取り、rebaseを怖がるようになる
UIならドラッグ&ドロップやそれに近い方法で解決できることが多いのに、一種の自虐のように見える
インターフェースがひどくても、チームの他の人たちが使っているものと同じインターフェースを使うほうが意思疎通しやすく、オンラインのトラブルシューティング資料もたいてい「普通のやり方」を前提にしているので活用しやすかった
2025年4月でGitが20周年だなんて、かなり不思議な感じがする
当時その現場にいて、SVNやCVSを置き換えるために git、darcs、bazaar のどれを学ぶべきか悩んでいたのを覚えている
Mercurialも試したかどうかは分からない
「GitHub効果」が、バージョン管理システムの分野で新規参入者の必要性を事実上消してしまったのか気になる
ある時点で、ヤクの毛刈りは十分に終わっていたのかもしれない
完全にjjへ乗り換えたし、Git互換性があるので、ほかの人が一緒に乗り換えてくれるのを待つ必要もない
GitHubのさまざまな面にますます不満を感じているので、いつか皆もそこを越えてくれればと思うが、そちらは先へ進む道がそれほど単純には見えない
SVNは直感的で、99%のケースでは十分うまく動くので、人にわざわざ「教える」必要がない
みんなで1337ハッカーごっこをやめて、Gitは大多数の人にとって過剰なツールだと認めてほしい
Gitが何をしているのかについてのメンタルモデルはかなりしっかり持っていると思うが、コマンドが少し複雑になるだけで引数を覚えていられない
コマンドは発見しやすくもないし、覚えやすくもない
テキストUIが悪いのか、それともツリーを操作する作業をテキストで説明すること自体が難しいのか分からない
複雑なツリーをテキストで操作するのは簡単ではないし、テキストUIも客観的に悪い: https://stevelosh.com/blog/2013/04/git-koans/
このスレッドでhgとMercurialの検索結果が出てきてうれしい
Mercurialが分散バージョン管理戦争に負けたことはいまだに信じられないし、より良いツールだと思っている
使えて、安定していて、実戦で検証済みで、活発にサポートされている
「自分が見たい変化になれ」に近い話なので、そのまま使えばいい
負けたというような言い方は逆効果が大きく、そうした言葉がもたらす暗黙の抑制がMercurialの採用率を低く保っている
ホスティングは少なくともSourcehutとheptapod.hostで可能だ
個人用のHeptapodインスタンスも運用しているが、GitlabフォークにMercurialの直接サポートが入った形で、普通にうまく動いている
Gitはいろいろな面で直感に反していて、結局なかなか頭に入ってこない技術の一つだった
長く使ってきたのでなければ、ほぼすべての作業でGoogleを探すかmanページを見ることになる可能性が高い
特に大きなコードベースをSVNで多数のブランチとして管理し、再びマージしようとする恐怖を経験した後だと、Gitはその部分ではばかげているほどはるかに優れていることが分かる
最初からGitに入る人には学習曲線がずっと大きいかもしれないが、人々がどこで詰まるのか、いまだに理解しづらいことがある
組み込み方式の**
filter-branch**を使うと、長い遅延とともにControl+Cを押してfilter-repoというサードパーティ製Pythonスクリプトをダウンロードしろという警告が出る自慢できることではないが、一番よく使う「やらかした」Git操作は、ローカルリポジトリを削除して再クローンし、変更内容を再適用することだ
95%はとてもよく効くし、残りはDevOps担当者に助けを求める
cp -R .git ../git-backupのようなことをしておく場合がある大きく失敗しすぎたら、
.gitディレクトリ全体を以前のコピーに置き換えればよいからだ特定の作業や作業のまとまりを正しく取り消す方法を探すより、ずっと速い
Gitについて人々にぜひ体得してほしいのは、Gitが追記専用データストアだという点だ
作業をリポジトリに入れるにはコミットすればよく、一度入った作業はGitコマンドでは削除できない
reflogもこの原理で動作している
何をしても以前のブランチ状態に戻って、データストアから作業を復元できる
しかしコミットしていなければGitは助けられないので、とにかくコミットすべきだ
常にコミットし、負担が大きすぎる、あるいはつい忘れてしまうなら、ツールを直すべきだ
一日中コードを書いているなら、一般的には少なくとも1時間に1回はコミットすべきだ
些細な話だが、
git reflogを見るたびにre-flogのように見えるだからこのコマンドをうまく覚えられないのかもしれないし、無意識に「reference log」と考えられていないようだ
代わりに、Gitに何かをもう一度安く売れと命じているような感じがする
Gitユーザーではありませんが、こういうものを見ると、
git commit自体には意味がなく、重要なのはコミットがpushされる、またはmergeされる時点だけなのだという考えが強まりますテキストファイルの保存に似ています
ファイルを保存するたびに短いメッセージを書くわけではないので、そのまま流せばよいのです
短くではなく、変更が小さいほど説明が長くなることもあります
影響の大きい2〜3行の変更に、2〜3段落を書いたこともあります
ローカルコミットは、すべてがコンパイルされ動作する時点でチェックポイントを作る方法です
そうすれば、変更をstashしたり、失敗したときに動いていた状態へ戻したりできます
pushする前にresetして適切なコミットに分ければ、リモートの履歴はきれいに保たれ、
wipのダミーにはなりませんあるいはPRをsquash mergeして、両方の利点を得ることもできます
ただし、
more fixesのような意味のないメッセージを入力する必要はありませんたいていは
(WIP) 機能名のような最初のコミットを作り、満足するまでamendコミットを繰り返したあと、最後にメッセージから(WIP)を取り除きますGitのリビジョンログも、そうした日記に似ていると思います
別のTODO文書より整理されており、すでにやったことを読みやすい形で説明するよう促してくれます
どこまで戻すか判断するときに、そのコミット時点で何を考えていたのかを知るのは役に立つことがあり、アプローチが間違っていたなら事後分析にも役立ちます
mergeやpushされていない作業が、目的の付いた個別の変更として整理されていれば、そのうち一部は良く、一部は悪い場合に、良い部分だけを残すのがずっと簡単になります
それでも技術的にコミットに意味がないわけではありません
コミットがなければ、pushしたりmergeしたりする対象もありません
非技術的にも、なぜ何かを変えるのかを平文で記録するのは非常に有用です
そうした説明をあまりに頻繁に書くことになるなら、「変更」の単位をもっと狭めるか、同時に触る作業をもっと厳選すべきです
Gitを使っていないなら、バージョン管理と変更管理を何で行っているのか気になります