miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

git revert で複数コミットを打ち消す

git にはコミットした内容を取り消す方法がいくつかありますが、いったんリリースしたコンテンツの公開期間が終了してその内容を取り下げたいような場合は、git revert でリリース時のコミットを打ち消すコミットを作るのがお作法です。

今回まさにそういう状況になったんですが、リリース時のコミットが複数回にまたがっており、それも 先のエントリ で書いたように他の対応と入り交じってコミットされてしまっています。

こういう場合にどう revert すればいいかという話です。

revert の基本的なところ

例えば 3a0e871f というコミットを打ち消したい場合は、

git revert 3a0e871f

を実行すれば、

Revert "xxx 対応"

This reverts commit 3a0e871ff60411ca89fa07c7f2b4d426fa04285d.

のようなメッセージがみたいなメッセージが入力された状態でエディタが起動するので、必要に応じてログメッセージを保存するだけで、逆マージを行うコミットが完了します。

複数のコミットを打ち消したい場合

単純なやり方

ログがこういう風になっていて、xxx 対応のコミット 3 つを打ち消したいとしましょう。

$ git log --pretty=oneline
d185733908f7ecf391639f16b3fc081c5b86bf1a 関係ないコミット 3
2a7824e19c75a626951d4ee5501d29ba63a57099 xxx 対応 その3
f83cfecf66182700ecacb2f4cd39f6430695abcc 関係ないコミット 2
eda48916d5ec314a367265b850484f93ca9d1ab5 xxx 対応 その2
3a0e871ff60411ca89fa07c7f2b4d426fa04285d 関係ないコミット 1
007ac11addb1f362675dafda96aa858ecb4851bf xxx 対応 その1
3916da28cf2f2eef429a1e595f7154dce71d2079 initial

こういう場合、

git revert 2a7824e1
git revert eda48916
git revert 007ac11a

のようにコミット時とは逆順で順番に revert してやれば、とりあえず目的は果たせます。

ただ、一回のコンテンツ取下げで 3 つコミットが残るのも面倒です。

-n オプションを使うやり方

git revert 時はコンテンツの逆マージと同時にコミットまで行うのがデフォルトの動作ですが、-n(--no-commit)オプションをつければ逆マージだけ行ってコミットは行ないません。

つまり、

git revert -n 2a7824e1
git revert -n eda48916
git revert 007ac11a
git commit

このように実行すれば、一回のコミットで 3 つのコミットを打ち消せます。

ただ、この場合デフォルトのログメッセージは

Revert "xxx 対応 その2"

This reverts commit eda48916d5ec314a367265b850484f93ca9d1ab5.

のように最後のコミットに対するメッセージになってしまう(と思ったけど、最後に -n したときのメッセージになってる・・・操作間違えたかな)ので、ちょっと不便です。

普通に revert して rebase -i で統合する方法

じゃあどうするかというと。最初にやったやり方に --no-edit オプションをつけて(エディタがいちいち起動するのが面倒なのでつけてるだけで、必須ではありません。)

git revert --no-edit 2a7824e1
git revert --no-edit eda48916
git revert --no-edit 007ac11a

で 3 つを revert した後で、

git rebase -i HEAD~3

します。

するとエディタが起動して、

pick be7ca2f Revert "xxx 対応 その3"
pick 0a7fce8 Revert "xxx 対応 その2"
pick 5fa5675 Revert "xxx 対応 その1"

# Rebase 2a7824e..5fa5675 onto 2a7824e
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

のような指示が表示されますので、先頭の 3 行を

pick be7ca2f Revert "xxx 対応 その3"
squash 0a7fce8 Revert "xxx 対応 その2"
squash 5fa5675 Revert "xxx 対応 その1"

に変えてエディタを閉じます。

すると、

# This is a combination of 3 commits.
# The first commit's message is:
Revert "xxx 対応 その3"

This reverts commit 2a7824e19c75a626951d4ee5501d29ba63a57099.

# This is the 2nd commit message:

Revert "xxx 対応 その2"

This reverts commit eda48916d5ec314a367265b850484f93ca9d1ab5.

# This is the 3rd commit message:

Revert "xxx 対応 その1"

This reverts commit 007ac11addb1f362675dafda96aa858ecb4851bf.

こんな感じで 3 つの revert を統合した形でコミットしようとするので、うまいことメッセージを編集してやれば完了です。

私は Redmine を使ってるので、今回のケースだったら、

refs #1234: 公開期間終了につき、xxxx のコンテンツ取下げ。

以下の 3 コミットを revert しました。
* commit:2a7824e19c75a626951d4ee5501d29ba63a57099 "xxx 対応 その3"
* commit:eda48916d5ec314a367265b850484f93ca9d1ab5 "xxx 対応 その2"
* commit:007ac11addb1f362675dafda96aa858ecb4851bf "xxx 対応 その1"

みたいに Redmine(textile)の記法で書きますかね。

もしかしたらコンテンツのトラッキングVCS に任せる Git の方針には反するかもしれないけど、履歴を残したいひとにはこの方法がいいのかなと。もっといいやり方があったら教えてくださいませ。