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

以前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 rebase –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-xAuto-merging SOMEFILECONFLICT (content): Merge conflict in SOMEFILEAutomatic merge failed; fix conflicts and then commit the result.$ git diff | wc -l12000$ git diff | grep '<<<<<<<' | wc -l8000

マージに際してコンフリクトがあった場合、作業ディレクトリにあるファイルはコンフリクト状況を示す<<<<<<< やら >>>>>>> やらのマーカーが埋め込まれた状態になります。コンフリクトした箇所が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 – 問題108

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

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中