経緯
git revert をよく理解せずに使ったら、トラブりました。
ver_1.2
ブランチに入れるべき修正を間違って ver_1.3
ブランチにコミットしちゃったとき。
ver_1.3
へのコミットを revert して ver_1.2
に cherry-pick で当該コミットを入れたらどうなるか。
しばらくは大丈夫。たぶん期待通りの状態になります。
でも後日、 ver_1.3
を ver_1.2
に追従したら、 ver_1.2
で cherry-pick して入れた修正はなくなってしまいます。
どうしてこんなことに?
revert は間違った操作を undo (なかったことに)するのとは違います。
[B]が最新の状態で、[A]のファイルをどこからコピーしてきて上書きコミットするイメージです。
例えるならホワイト(修正液)で塗りつぶす感じです。gitには「ホワイトで塗りつぶす作業」が追加コミットされます。
- [A]を[B]に変える修正をコミットしたとします。
- [B]を revert すると[B]を[A']に変える修正をコミットしたことになります。
ここで[A']はファイルの内容自体は[A]と全く同じですが、過去に一度[B]であった過去を背負った別物です。
また**[B]は将来[A']に上書きされることを運命づけられた**コミットになってしまいます。
一方 cherry-pick は別のコミットをその運命込みで取り込む操作です。
別ブランチで[B]を cherry-pick して取り込んでも、[A']のコミットが入ったブランチをマージすると [A']のコミットが最新になるわけです。
解決策
revert の revert
問題が発覚したあとなら、 [A']のコミットをさらに revert して [B'] にしたものを入れれば良いです。
[B]のファイルをどこからコピーしてきて上書きコミットしても同じですが、操作ミスが入る余地が減るので可能なら revert コマンドでやったほうがいいですね。
revert しない
今回の事例の場合、そもそも ver_1.3
から revert する意味があったのか?
私のケースでは、「操作間違えちゃった、取り消し!」みたいな軽い気持ちでやってしまいました。
いずれ追従する予定なら、そのまま[B]が入ったままでもよかったんです。
revert が undo じゃないと知ってれば、安易に revert するよりそのままにしておく選択肢もありました。
(もちろん、プロジェクトと修正内容によっては、先のバージョンに入れると不都合があるので revert が必要なケースもあるかもしれません。)
cherry-pick じゃなかったら?
[B]を cherry-pick じゃなくてファイルをコピーしてきて新規コミットしたら・・・
おそらく追従時にコンフリクトしますね。気付けるだけマシですが。
コミット自体なかったことにできないのか
よく知らずに使うと、 revert よりはるかに危険なので、あまりおすすめはしませんが、 rebase という操作でできるらしいです。
Git-のブランチ機能-リベース
なぜ危険かと言うと、共同作業中のリポジトリだとうっかり他の人の必要なコミットまでなかったことにしてしまい、しかも過去の歴史が改ざんされてるので気づきにくい。問題が発覚したときには当事者がいなかったりして、原因を特定するのにも苦労する、なんてこともあります。
ローカルリポジトリなら reset で
まだ push しておらず、あくまでローカルリポジトリにのみコミットした状態なら、以下のようにしてコミットする前の状態に戻すことができます。
git reset --soft HEAD^
--soft
オプションはコミットした差分を維持する指定です
--hard
に変えるとコミットした差分は消えて修正作業前の状態になります
また HEAD^
は一つ前のコミットまで戻る指定で、 HEAD^^
, HEAD^^^
のように ^
を重ねた分だけ前のコミットに戻すことができます。
参考
https://qiita.com/forest1/items/f7c821565a7a7d64d60f
reset と push
前項で まだ push しておらず と条件を付けましたが、既に push されてる場合でも -f オプションで強制的にリモートリポジトリに反映することは可能です。これは実質的に rebase と同じ操作であり、同じく危険な行為ですのでおすすめしません。
まとめ
revert は undo ではなく、逆修正による上書きコミットです。
なので別ブランチで cherry-pick していても、マージすると revert 後の状態が最新になります。
ローカルのコミットをやり直すなら reset コマンドがいいです。
以上、私もそこまで深く git を理解してるわけじゃありませんので、間違いやよりよい解決方法などあればご意見いただければ幸いです。