Mercurialでアレを元に戻す108の方法
2011年 08月 22日
yoppi
前回[@kana1さん](http://twitter.com/kana1)による[「gitでアレをもとに戻す108の方法」](/tech/git-undo-999)が大反響で世間はやはりgit使いが多いのかと再認識しました。
私も普段はgitを使っていますが、お仕事ではMercurialを仕事で使っているのでのっかって書き連ねてみましょう。
### 問題1: ライブラリの新機能を試すためにあれこれ適当なコードを書いていくつかコミットした。でももういらない
さて初っぱなから行き詰まりそうです。基本的にMercurialは「コミットを積み重ねたものを後から編集する」ことに弱いのです。
MQを使って解決してみましょう。
$ hg update -r {revision}
$ hg qimport -r {revision+1}:tip
$ hg qpop –all
$ hg qseries | while read patch
> do
> hg qdelete $patch
> done
面倒ですね。
stripを使うともうちょっと楽にできます。
$ hg update -r {revision}
$ hg strip {revision+1}
もちろんこの記事を読んでいる読者のみなさんは、メインブランチで作業する剛毅な人
はいないはずなので、topicブランチを削除するだけで大丈夫なはずです
$ hg update {main-branch}
$ hg qimport -r “branch(‘{topic-branch}’)”
$ hg qpop –all
$ hg qseries | while read patch
> do
> hg qdelete $patch
> done
もしくはstripを使います。
$ hg update {main-branch}
$ hg strip {topic-branch-start-revision}
Mercurialはブランチを削除することができないので、対象のブランチのコミットをMQに
入れて削除するか、stripで一気に消してしまうかのどちらかしかありません。
このあたりのことは[「Mercurialのローカルブランチのブランチ名を変更したり削除する」](/tech/rename-and-delete-localbranch-in-mercurial)で詳しく書いています。
### 問題2: トピックブランチをマージしたけど実はまだ不完全だった。マージをやり直したい
コミットする前であれば、
$ hg update -C
コミットしてしまったならば、
$ hg strip {merge-commit-revision}
stripしてしまいましょう。
ここでbackoutを使わないのは、あくまでmergeそのものをなかったことにしたいからです。
Mercurialで理解しにくい概念にMQがありますが、mergeのbackoutも複雑でややこしいものです。
詳しは、[Finding and fixing mistakes](http://hgbook.red-bean.com/read/finding-and-fixing-mistakes.html)を参照してください。
### 問題3: リリース後に発覚したバグ。30日前の自分がコミットが原因だった。なかったことにしたい。
backoutして正しく修正したものをコミットしましょう。
$ hg backout -r badcafe
これもgit同様にリポジトリから抹消するものではありません。
間違った修正を打ち消すコミットを一度行うものです。
打ち消したものは自動的にコミットされますが、マージが発生した場合、それは自動でコミットしてくれません。
往々にしてコンフリクトすることがあると思いますが、落ち着いて対応しましょう。
### 問題4: 新しいコミットしようと間違えて不要なものまで含めてコミットしてしまった
Mercurialは編集しているものはすべてコミット対象となってしまうので、適当にいじっていて、
不要な変更までコミットしてしまうことは珍しくありません。
あわてずトランザクションをrollbackしましょう。
$ hg rollback
そして再度コミットしたい変更のあるものだけを指定します。
$ hg commit hoge.txt foo.txt
### 問題5: いろいろ作業していたら作業ディレクトリが混沌としてきた。一度綺麗な状態にしたい
$ hg update -C
もしくは、
$ hg shelve –all
して横にのけておくのもいいですね。あとからあのコードやっぱり必要だった、みたいなことはよくありますから。
shelveはMercurial1.9だと動作しなくなっているので、1.8のMercurialを使うか、[パッチ](https://yoppi@bitbucket.org/astiob/hgshelve)が適用されたものをしようしましょう。
### 問題6: 作業ディレクトリにゴミファイルがたまってきた。一度綺麗な状態にしたい
[PurgeExtension](http://mercurial.selenic.com/wiki/PurgeExtension)を使いましょう。
bundleされているので.hgrcに記述するだけで使えるようになります。
まずは-pを指定して削除対象を確認してから、慎重に削除しましょう。
$ hg purge -p
…
$ hg purge
で、Mercurial管理下にないものを削除できます。
### 問題7: 新しいファイルをhg addした。しかしまだそのタイミングではなかった。元に戻したい
$ hg revert {file}
もしくは
$ hg rm -f {file}
または、
$ hg forget {file}
です。rmの場合-f(–force)しても削除されることはないので大丈夫です。
余談ですが、forgetはhg help addするとポインタとしてforgetのことを教えてくれるのですが、
日本語にローカライズされたhelpだとrevertしかでてこなく、見つけるのが難しいです([@troter](http://twitter.com/troter)に教えてもらいました)。
### 問題8: hg stripした。けどstripをなかったことにしたい
stripを覚えるとしばらくstrip房になり、なんでもかんでもstripし始めます。
ほら、いわんこっちゃない、stripを間違ってしまいました。
大丈夫です。落ち着きましょう。
stripは便利な反面、コミットの歴史から無くしてしまう諸刃の刃的なものですので、安全策として.hg/strip-backup/以下に{changeset}-backup.hgというファイル名でバイナリ形式で残してくれています。
復旧するには、unbundleコマンドを使います。
$ hg unbundle .hg/strip-backup/{changeset}-backup.hg
### 問題9: rm {file}した。でもまだその時期ではなかった。元に戻したい
$ hg revert {file}
もしくは
$ hg update -C
です。
### 問題10: ファイルのあちこちを編集した。でも最後にコミットした状態に戻したい
$ hg revert {file}
### 問題11: ファイルの10箇所くらい変更した。でも特定のものだけ最後にコミットした状態に戻したい
これは応用編になりますね。
一度recordで残しておきたいものをコミットしてしまって、残りをupdate -Cして元に戻したあと、
commitしたトランザクションをrollbackしましょう。
$ hg record {file}
…
$ hg update -C
$ hg rollback
またはshelveを使ったほうが直感的ですね。残しておきたいものをshelveしておいて、update -Cして戻し、unshelveします。
$ hg shelve {file}
…
$ hg update -C
$ hg unshelve
### 問題12: 調子にのってhg qimport をしていたら途中で混乱してきた。qimport開始前の状態に戻したい
qimportを使ってパッチを作成して歴史を整形することはままあります。
しかしやみくもにパッチを作ってしまって結局もとに戻したい場合はfinishしましょう。
$ hg qrefresh -a
### 問題13: 先程コミットした内容は2つに分割すべきだった。コミット前の状態に戻したい
コミットしたトランザクションをrollbackして、必要であればrecord等を使ってコミットし直しましょう。
$ hg rollback
### 問題14: リリースブランチの更新作業を行おうとしたがまだ作業の途中。一度綺麗な状態にしたいが、後で作業を再開したい
shelveとunshelveを使いましょう。
$ hg shelve –all
$ …
$ hg unshelve
### 問題15: リリース後に間違ったリビジョンを指定してtagを打ってしまって、しかもpushしてしまった。この黒歴史を抹消したい
これは大変です。.hgtagsを直接編集してtagを打ち直してもいいですが、そもそも黒歴史はなかったことにしたい場合があります。
大丈夫です。
リリース作業直後だったので誰もpullしておらずあなただけがpushした状態です。
では、リポジトリサーバを一時停止しましょう。
おもむろにセントラルリポジトリで、その間違ったコミットをstripします。
$ hg strip -r abadbabe
さて、自分のローカルの間違ってtagを打ったコミットもstripします。
いいですね。これで黒歴史は抹消されました。
リポジトリサーバを稼働させましょう。何もなかったように時は再び流れ始めます。
リポジトリサーバを自前で用意しておらず、例えばbitbucketを使用していたとしても大丈夫です。管理画面からstripできるようになっています。
### 問題16: かなり昔からパスワードを含んだファイルをコミットしており、公開リポジトリにも push 済みだった。この黒歴史を抹消したい
[1024](/tech/author/1024/)さんから記事をいただきました。
答えは直ちに該当のパスワードを変更する、です。
Mercurialの歴史はとても堅牢にできています。
どうしても消したければ公開リポジトリを strip し、もう一度それ以降の歴史を積
み重ね直すという大技もありますが、該当リポジトリのcloneに大混乱を発生させる
こと必至です。
逆に言えば、一度公開リポジトリに push しさえすれば、
安易に破壊されることがないということでもあります。
いずれにせよ、Mercurialは自身のリポジトリは管理できますが、
パスワードを見た人全員の記憶からパスワードを削除するなどということは
Mercurialに限らずどんなツールでも無理ですから、無駄な行為は慎みましょう。
### 問題16: 問題108
@kana1さんの記事のネタをそのまま拝借して書いてみましたが、これ以上思いつかなかったのでここで筆をおきたいと思います。
Mecurialで何か「元に戻したい」ことがあれば追記していきます。