Oh Shit, Git!?!

Gitって難しい。簡単にぐちゃぐちゃの状態になっちゃうし、失敗を直す方法を知ろうとしたところでまじくそ不可能。Gitのドキュメンテーションって卵とニワトリの問題みたいなところがあって、ハマりから抜け出すために知ってないといけない事柄の名前をあらかじめ知っていないと、どうやって問題を解決したらいいのか検索することすらできないんだよね。

だからここに、私が遭遇したことのあるよろしくない状況から、最終的にどうやって抜け出したかをフツーの日本語で書いていこうと思う。

くっそー、超絶やらかした。お願い、Gitには魔法のタイムマシンがあるって言って?

git reflog
# こうすると、Gitでやったことがすべてのブランチに渡って全部見えるよ!
# どのブランチにも HEAD@{index} ってインデックスがあるはずだから
# やらかす前のやつを見つけて
git reset HEAD@{index}
# ほら、まるで魔法のタイムマシン

これを使えば、間違えて消しちゃったものを取り戻したり、試しにやってみたらrepoを壊しちゃったやつを取り除いたり、ダメだったマージから復旧したり、あるいは単にちゃんと動いた頃の状態へ戻したりすることができる。私も reflogめっちゃ使う。これを載せるべきだって言ってくれた超超超超超大勢の人たちみんなにとっても欠かせない最重要Tipだよね!

くっそー、コミットしたばっかだけど、あと1個だけ入れなきゃいけない微修正あったの気づいちゃった!

# 変更を入れてから
git add . # あるいは個々のファイルをadd
git commit --amend --no-edit
# これで最後のコミットに変更が含まれたよ!
# 警告: もうpublicになってるコミットをamendするのは厳禁

私の場合よくあるのは、コミットしたあとテストやlinterを回してみたら... うわ最悪。イコール記号の後にスペースを入れてなかったわ、とか。新しいコミットを作ってから rebase -i して一つのコミットにまとめてもいいんだけど、このほうが100万倍速いよね。

注意!: publicだったり共有されてたりするブランチに対して、既にpushされてるコミットをamendするのは絶対やめておこう。コミットをamendするのはローカルコピーだけにしておくこと、じゃないとひどい目にあうから。

くっそー、さっきのコミットメッセージ変えたいんだけど!

git commit --amend
# 指示に沿ってコミットメッセージを変更しよう

体裁よくアホなコミットメッセージをよろしくね。

くっそー、新しいブランチにコミットするはずだったやつ、間違えてMasterにコミットしちゃったよ!

# 今の状態のmasterから新しいブランチを作成
git branch some-new-branch-name
# masterブランチから最後のコミットを消去
git reset HEAD~ --hard
git checkout some-new-branch-name
# あなたのコミットはこのブランチにあるはず :)

注: publicだったり共有されてたりするブランチへ既にpushされてるコミットの場合にはこの方法はうまくいかないし、もし先に他のことを試してた場合は HEAD~ の代わりに git reset HEAD@{戻すコミットの数} をやらないといけないかもしれない。無限のつらみだね。それと、超超超他大勢の人たちがこの手順を短くできるように、私の知らなかったイケてる方法を提案してくれた。みんなありがとうね!

くっそー、コミットするブランチ間違えたし!

# 最後のコミットは取り消しつつ、変更内容はあとから使えるようにしよう
git reset HEAD~ --soft
git stash
# 正しいブランチに移動
git checkout [正しいブランチ名]
git stash pop
git add . # あるいは個々のファイルをadd
git commit -m "ここにメッセージを書いてね";
# これで正しいブランチに変更内容が反映されたよ

この状況だと cherry-pick を使うっていう人も多かった。だから自分に合うほうを選んでね。

git checkout [正しいブランチ名]
# masterブランチから最後のコミットをピックして
git cherry-pick master
# masterからは変更内容を消そう
git checkout master
git reset HEAD~ --hard

くっそー、diff実行したはずがなんにも出てこないんだけど!?

ファイルを変更したはずなのに diff が空っぽのときは、多分ステージングに add してるから以下のフラグを使わないといけない。

git diff --staged

なんでこんなところにファイルが ¯\_(ツ)_/¯ (これは機能であってバグじゃないって分かってるけど、まじくそ混乱のもとだし初めての人にはさっぱり分かんないよね!)

くっそー、5つ前のコミットを元に戻さないといけないって!

# undoしたいコミットを見つけよう
git log
# 矢印キーで履歴内を上下して、コミットを見つけたらハッシュを保存して
git revert [保存したハッシュ]
# Gitはそのコミットをundoした状態の新しいコミットを作ってくれるから
# 指示に従ってコミットメッセージを編集するか、あるいは単に保存してコミットしよう

どうやら古いファイルを見つけ出して中身を既存のファイルへコピペする必要はないみたい! バグをコミットしちゃったんだとしても、revert 一発でコミットをundoできるよ。

それに、コミット全体じゃなくて一つのファイルだけを対象にrevertすることだってできる! だけどもちろん、真のgitのお作法を念頭に置くなら、それって全然違うクソコマンドのセットだよね...

くっそー、ファイルへの変更を元に戻さなくちゃ!

# ファイルが変更される前のコミットのハッシュを見つけよう
git log
# 矢印キーで履歴内を上下して、コミットを見つけたらハッシュを保存して
git checkout [saved hash] -- path/to/file
# インデックスには古いバージョンのファイルがあるはず
git commit -m "やった! コピペして元に戻す必要ないじゃん"

これがやっと分かったのは、でかかった。でかかった。で・か・か・っ・た。 けど真面目な話、checkout -- がundoするのにベストの方法って、一体全体どういうこと? (Linus Torvaldsへ抗議のポーズで)

もーーまじこいつ無理。諦めたわ。

cd ..
sudo rm -r fucking-git-repo-dir
git clone https://some.github.url/fucking-git-repo-dir.git
cd fucking-git-repo-dir

Eric V、これありがとね。仮にこのジョークを真に受けて sudo しちゃったとしても、みんな文句は彼に言うんだよ。

真面目の話、ブランチがめちゃくちゃになって、repoの状態をリモートと同じように "git的に正しい状態" に戻さなくいけなくなったら、以下を試してみて。でもこれは破壊的な変更で、一度やると元に戻せないので要注意!

# originの最新の状態を取ってくる
git fetch origin
git checkout master
git reset --hard origin/master
# 追跡されていないファイルやディレクトリを削除
git clean -d --force
# 腐った全ブランチに対し checkout/reset/clean を繰り返そう

おことわり: このサイトは網羅的なリファレンスを作ろうとして用意したわけじゃない。それに、同じことをもっと純粋な理論に則ってやる方法だってあるはず。でも私は、試行錯誤を繰り返しては悪態をつきまくり、テーブルをひっくり返したりながらやっとこの方法に辿り着いて、軽いノリでみんなと共有してみよっかなってイカれた考えに至ったわけ。要は参考にするもしないも、あなたの自由ってこと!

このサイトをボランティアで新しい言語に翻訳してくれたみんな、ありがとう! みんな最高! Moritz Stückler (de) · Daniil Golubev (ru) · Łukasz Wójcik (pl) · fedemcmac (it) · Michel (fr) · Andriy Sultanov (ua) · Meiko Hori (ja) · Alex Tzimas (gr) · Franco Fantini (es) · Catalina Focsa (ro) · Davi Alexandre (pt_BR) , 以下の追加の支援により: Iain Murray · Frank Taillandier · David Fyffe · Lucas Larson

もし、あなたの言語への翻訳の追加を手伝ってくれるなら GitHub からPRよろしく!