gitでアレを元に戻す108の方法


2011年 08月 21日

以前gitで一度行った変更をなかったことにする方法4つを紹介しましたが、
日常的に git を使用していると他にも様々な
「なかったことにしたい」「元に戻したい」
という状況に遭遇します。
そのひとつひとつについて対処方法を紹介していきます。


目次

問題1: ライブラリの新機能を試すためにあれこれ適当なコードを書いてみた。でももう要らない。

解答1: git reset –hard HEAD~{n}

詳細は gitで一度行った変更をなかったことにする方法4つ を参照してください。

別解1: git branch -d

$ git branch experimental
$ git checkout experimental

$ $EDITOR
$ git commit -am 'foo'
$ $EDITOR
$ git commit -am 'bar'
$ $EDITOR
$ git commit -am 'baz'

$ git checkout master
$ git branch -d experimental

そもそも実験的なことをするのであれば、
一度実験用のブランチ(例えば experimental)を作ってそこで作業し、
要らなくなったらそのブランチを削除(git branch -d)すれば済みます。

問題2: トピックブランチをマージしたけど実はまだ不完全だった。マージをやり直したい。

解答2: git reset –hard ORIG_HEAD

詳細は gitで一度行った変更をなかったことにする方法4つ を参照してください。

問題3: リリース後に発覚したバグ。原因は30日前に自分が行ったコミットだった。なかったことにしたい。

解答3: git revert $commit_id

詳細は gitで一度行った変更をなかったことにする方法4つ を参照してください。

問題4: 新しいコミットしようとして間違えてgit commit –amendで書き換えてしまった。元に戻したい。

解答4: git reset HEAD@{1}

詳細は gitで一度行った変更をなかったことにする方法4つ を参照してください。

問題5: 色々作業していたら作業ディレクトリの内容が混沌としてきた。一度綺麗な状態にしたい。

回答5: git checkout HEAD — .

別解5: git reset –hard HEAD

問題6: 作業ディレクトリにゴミファイルが溜まってきた。一度綺麗な状態にしたい。

回答6: git clean -n

git の管理下にないファイルは
git clean
でまとめて削除することができます。

しかし「git の管理下にない」=「後から元に戻すことはできない」なので
git clean-f を指定しない限りファイルを削除しません。
-n では削除対象のファイル名を表示するだけです。
実際の削除は慎重に確認をしたうえで行いましょう。

問題7: 新しいファイルを git add した。しかしまだそのタイミングではなかった。元に戻したい。

回答7: git reset HEAD — $file

別解7: git rm –cached $file

新しいファイルを git add したのであればこれでも同じ効果。
ただし、このコマンドの意味するところは「git リポジトリから $file を削除する」なので注意しましょう。

問題8: git add -p $file した。でも git add しなかったことにしたい。

回答8: git reset HEAD — $file

問題9: rm $file をした。でもまだその時期ではなかった。元に戻したい。

回答9: git checkout HEAD — $file

問題10: ファイルをあちこち編集した。でも最後にコミットした状態に戻したい。

回答10: git checkout HEAD — $file

問題11: ファイルの10箇所くらいを変更した。でも特定のものだけ最後にコミットした状態に戻したい。

回答11: git checkout -p HEAD — .

個々の変更箇所について元に戻すかそのままにしておくか選択することができます。

問題12: 調子に乗って git rebase -i をしていたら途中で混乱してきた。rebase 開始前の状態に戻したい。

回答12: git rebase –abort

問題13: git rebase が完了してしまった。でも rebase 実施前の状態に戻したい。

回答13: git reset –hard ORIG_HEAD

ただし git rebase 直後に git commitgit merge などのコマンドを実行していない時に限ります。

別解13: git reflog と git reset –hard

それも既にやってしまったという場合:
git reflog
で各種操作が行われた時点のコミットが一覧できます。
適切なコミット(例えば abadcafe) を探して
git reset --hard abadcafe とすれば元に戻せます。

問題14: git reset –hard HEAD~4 とするつもりが HEAD~5 で実行してしまった。元に戻したい。

回答14: git reset –hard HEAD@{1}

問題4や問題13と同様です。

問題15: 先程コミットした内容は2つに分割すべきだった。コミット前の状態に戻したい。

回答15: git reset HEAD~1

実行後は適宜 git addgit commit をしましょう。

問題16: リリースブランチの更新作業を行おうとしたがまだ作業の途中。一度綺麗な状態にしたいが、後で作業を再開したい。

回答16: git stash save と git stash pop

リリースブランチの更新作業が終わった後、
元のブランチで git stash pop すれば中断した作業を再開することができます。

問題17: かなり昔からパスワードを含んだファイルをコミットしており、公開リポジトリにも push 済みだった。この黒歴史を抹消したい。

例えば以下のような状況だったとします:

$ git checkout master

$ echo 'id=who' >conf
$ echo 'password=secret' >>conf
$ git add conf
$ git commit -m 'Add conf'

$ echo 'width=1024' >>conf
$ git commit -am 'Update conf 1'

$ echo 'height=768' >>conf
$ git commit -am 'Update conf 2'

$ echo 'color=256' >>conf
$ git commit -am 'Update conf 3'

$ git push origin master

「本来 conf には生のパスワードを書いてはいけなかった」という状況です。
このとき、慌てて

$ sed -e '/^password=/d' conf >,conf
$ mv ,conf conf
$ git commit -am 'Remove the secret password'
$ git push origin master

などとやっても、変更履歴を辿れば結局はパスワードを参照できてしまいます。

回答17: git filter-branch と git push -f

どちらのコマンドもかなりの荒業なので、周囲の状況をよく確認したうえで使いましょう。

git filter-branch
を使えば指定したコミットを自動で書き換えることが可能です。
今回の例の場合、 conf ファイル中のパスワード行をなかったことにしたいので、
以下のようなコマンドを実行します:

$ git filter-branch \
> --tree-filter 'sed -e "/^password=/d" <conf >,conf; mv ,conf conf' \
> master~4..master

正しくファイルを書き換えることができたか確認し:

$ git log -p master~4..master -- conf

問題なさそうなら公開リポジトリに push した内容を上書きしましょう:

$ git push origin master -f

ただし、このケースで行っていることは、既に公開した内容を自分の都合で上書きしていることに他ならず、
黙って行うと公開リポジトリを既に clone していた人達に大混乱を招くため、
必ず周囲の状況を確認して周知を徹底したうえで行いましょう。

別解17: すぐにパスワードを変更する。

(まあ、元のパスワードが恥ずかしいものだった場合は、やはり git filter-branch したくなりますが……)

問題18: feature-xブランチをmasterブランチへマージしたら盛大にコンフリクトした。手っ取り早く修正するために、作業ディレクトリの特定のファイルを片方のブランチのものに戻したい。

(2011-09-02T18:06:54+09:00追加)

例えば以下のような状況だったとします:

$ git checkout master

$ git merge feature-x
Auto-merging SOMEFILE
CONFLICT (content): Merge conflict in SOMEFILE
Automatic merge failed; fix conflicts and then commit the result.

$ git diff | wc -l 12000

$ git diff | grep '<<<<<<<' | wc -l 8000

マージに際してコンフリクトがあった場合、
作業ディレクトリにあるファイルはコンフリクト状況を示す
<<<<<<< やら >>>>>>> やらのマーカーが埋め込まれた状態になります。
コンフリクトした箇所が10箇所くらいなら手作業でどうにかするところですが、
上記のように8000箇所くらい
(あるいはコンフリクトしたファイルが誠に遺憾ながら政治的な理由によりプレインテキストでない場合)
になるとさすがにそうも言ってられません。

手っ取り早く修正するにはファイルの内容をどちらか片方のブランチの最新版の内容で置き換えるのが一番なのですが、
どうすればよいでしょうか。

回答18: git checkout –ours $file または git checkout –theirs $file

git checkout
を使います。通常、このコマンドはブランチの切り替えに使いますが、
ファイル名を指定すると作業ディレクトリ中の対応するファイルの内容を
特定のブランチでのファイルの内容で置き換えることができます。

例えば作業ディレクトリにある SOMEFILE ファイルの内容を
master ブランチの内容で置き換えるには以下のコマンドでできます:

$ git checkout master -- SOMEFILE

実際にはブランチだけでなく任意のコミットを指定することもできるので、
以下のようにして1前のコミットでの状態を取得することもできます:

$ git checkout HEAD~1 -- SOMEFILE

実際にコンフリクトが発生した場合は
「どのブランチをどのブランチにマージしようとしていたか」
という情報が内部的に記録されているので、
直接ブランチ名を指定する代わりに以下のコマンドを使うこともできます:

$ git checkout --ours -- SOMEFILE

--ours はマージ結果を取り込むブランチ(上記の例の場合 master)を表します。
現在のブランチのものから復元するため「ours」という訳です。

$ git checkout --theirs -- SOMEFILE

--theirs はマージしようとしたブランチ(上記の例の場合 feature-x)を表します。
現在のブランチとは別のブランチのものから復元するため「theirs」という訳です。

問題19: ブランチ foo を削除しようと思ったのに間違えてブランチ bar を削除してしまった。ブランチ bar を復元したい。

(2012-07-02T14:33:15+09:00追加)

回答19: git branch bar HEAD@{$n}

大抵の場合、一度は bargit checkout しているはずです。
ならば git reflog HEAD で「 bar から別のブランチに git checkout した」ログが見つかるはずです。
そのログから $n の値を決めて git branch bar HEAD@{$n} を実行すれば復元できます。

一度も checkout してなかった場合は reflog にも記録されていないので調べようがありませんが、
この場合はそもそも bar で有益な作業をしていた訳ではないので、復元できなくとも問題ないはずです。

問題20: ブランチ foo を共有リポジトリへ push した後になって実はブランチ名は bar が正しかったと気付いた。 push してしまった foo を消したい。

(2013-11-18T18:57:45+09:00追加)

本来なら

git checkout -b bar master
...
git push origin bar

とすべきところを

git checkout -b foo master
...
git push origin foo

とやってしまいました。
ローカルのブランチは

git branch -m foo bar

でリネームできるので、これで

git push origin bar

とすれば本来のブランチ名で push はできます。
が、既に push してしまった foo についてはどうすれば無かったことにできるでしょうか。

回答20: git push origin :foo または git push –delete origin foo

問題21: feature-xブランチをmasterブランチへマージしたら盛大にコンフリクトした。コンフリクトを解決しようとあれこれ編集していたら混乱してきた。一から編集をやり直したいのでマージ直後の状態に戻したい。

(2015-05-14T18:49:58+09:00追加)

回答21: git checkout –merge

問題18の亜種ですね。

問題22 – 問題108

  • 筆者の経験では108通りもありませんでした。
  • 「元に戻したい」状況があれば適宜追記します。
  • 次回は Mercurial 編です。