📋

git revert: PRのマージを取り消し、元の状態に戻す方法

2024/02/09に公開

こんにちはmocchantaiです🥳

この記事では、プルリクエスト(以下PR)のマージを取り消し、マージ前の状態に戻したい方向けのガイドを提供します。また、マージを戻した後の手順や、revertrevertがどういう意味を持つのかについても解説します。

ステップ別ガイド

ステップ1: PRのマージを取り消す

状況

  • developstagingproductionの3つのブランチで管理しています。
  • stagingからproductionへのPRをマージしましたが、アップデート完了後に致命的なバグが発覚しました。
  • 今回のPRのマージを取り消し、アップデート前の状態に戻したいです。

手順

  1. GitHubのPRの画面からrevertボタンを押して、revert用のPRを作成します。
  2. Revert用のPRをproductionブランチにマージします。

これにより、マージした変更を取り消す新しいコミットがproductionブランチに追加されます。

ステップ2: PRを再度マージする準備

状況

  • git revertを使用してPRのマージを取り消すことができました。
  • 致命的なバグを修正した後に、再度PRをマージしたいです。

手順

  1. Revertしたブランチをdevelopブランチにmergeします。
  2. Revert用のPRをさらにrevertして復活させます。
  3. 致命的なバグを修正します。
  4. 修正され、復活したPRをdevelopstagingstagingproductionにマージしていきます。

解説

GitのCommitとは

Gitはcommit単位で差分を管理しています。各commitはコードの状態のスナップショットであり、変更履歴を追跡します。

それぞれのcommitにはcommit_idがついており、コードの変更部分が同じでもcommit_idが異なれば別のcommitとして扱われることになります。

ターミナルでgit log をするとそのブランチに含まれているcommitを最新順で確認できます。

Git Revertの原理

git revert コマンドは、特定のコミットによる変更を無効にする新しいコミットを作成します。このコマンドは、エラーを含む変更を持つコミットや、単に取り消したい変更を持つコミットを"取り消す"ために使用されます。revert は履歴を改変するのではなく、変更を打ち消す新しいコミットを追加することで、履歴をそのままにしておきながら、コードの状態を以前の状態に戻します。

新しいコミットにはユニークなコミットIDが割り当てられます。例を見てみましょう:

元のコミット:commit_id: 37d1357fd657fbd133dd167198cb1f19b5e54e2b

function addFeature() {
+   // 実装コード1
+   // 実装コード2
+   // 実装コード3
}

git revert 後のコミット:commit_id: 56f4daed2e8ae086f78d8b64927d1902130fc34a

function addFeature() {
-   // 実装コード1
-   // 実装コード2
-   // 実装コード3
}

PRをRevertするとどうなるか

PRをリバートすると、そのPRに含まれる1つまたは複数のコミットが打ち消されます。PRに含まれる各コミットに対しては、個別のリバートコミットが作成されるわけではありません。代わりに、PRの結果として生じたコードの状態全体を逆転させる単一のリバートコミットが作成されます。これにより、PRによって導入されたすべての変更が打ち消されます。

たとえば、3つの開発コミットが以下のような変更を含んでいるとしましょう
(※コミットIDは簡略化のため1文字で表しています。)

commit_id: A

+ function featureOne() {
+   // 新機能1の実装
+ }

commit_id: B

+ function featureTwo() {
+   // 新機能2の実装
+ }

commit_id: C

+ function featureThree() {
+   // 新機能3の実装
+ }

これらのコミットが含まれるPRをマージした時に1つのcommitでまとめられます。

commit_id: X

+ function featureOne() {
+   // 新機能1の実装
+ }
+ function featureTwo() {
+   // 新機能2の実装
+ }
+ function featureThree() {
+   // 新機能3の実装
+ }

これらのコミットが含まれるPRをリバートすると、以下のように1つのリバートコミットでこれらの変更が取り消されます:

これはステップ1-1と1-2に該当します。

commit_id: Y

- function featureOne() {
-   // 新機能1の実装
- }
- function featureTwo() {
-   // 新機能2の実装
- }
- function featureThree() {
-   // 新機能3の実装
- }

Revert済みのブランチからPRを作ろうとするとGithubが差分を認識してくれない

RevertしたPRを再度マージするとは、マージした後にrevertしたブランチからdevelop, staging, productionに向けてCreate Pull Request をしようとしてもThere isn’t anything to compare. と言うメッセージが表示されてPRを作成できない状態です。

この原因は上記の例でcommit_id: Yでrevertした後にcommit_id: Xのcommitを再度マージしようとしているからです。

前述の通り、gitは同じcommit_idのcommitを同一のものとして扱うのですでにマージ済みのcommit_id: Xのcommitを再びマージすることはできないのです。

Revertしたブランチをdevelopブランチにmergeする理由

developブランチにrevertしたcommitを反映させるためです。productionブランチでrevertをしたので、現状commit_id: Yはproductionブランチにしか存在しません。

これをdevelopブランチにも反映させることで、commit_id: Yのrevertをdevelopブランチで行うことができるようになります。

これがステップ2-1に該当します。

RevertをRevertするってどういうこと?

やりたいこととしては、revertして取り消されてしまった新機能1~3を再びマージしたいと言うことです。

Revertした変更を再び適用するためには、revertのrevertというプロセスを行います。これにより、新しいコミットIDで元の変更が再導入されます。

commit_id: Yをrevertしてできた新しいcommit

commit_id: Z

+ function featureOne() {
+   // 新機能1の実装
+ }
+ function featureTwo() {
+   // 新機能2の実装
+ }
+ function featureThree() {
+   // 新機能3の実装
+ }

これがステップ2-2に該当します。

バグを修正

revertのrevertをdevelopブランチで行ったことで、新機能1~3のコードがdevelopブランチに現れました。

この時点でバグを修正しましょう。

commit_id: D

function featureThree() {
    // 新機能3の実装
-   //バグコードを削除
+   //正しいコードを追加
}

これがステップ2-3です。

修正後のPRをマージする

最後にdevelop→staging、staging→productionにCreate Pull Request してPRを作成し、マージしましょう。

新しい新機能1~3とバグ修正がまとめてマージされるはずです。これは新しいcommit_idがついているからです。

commit_id: E

+ function featureOne() {
+   // 新機能1の実装
+ }
+ function featureTwo() {
+   // 新機能2の実装
+ }
+ function featureThree() {
+   // 新機能3の実装
-   //バグコードを削除
+   //正しいコードを追加
+ }

これがステップ2-4に該当します。
以上で完了しました。

まとめ

今回はPRをrevertして再度revertする手順の紹介と解説をしました。
少しでも理解の助けになれば嬉しいです!

ソーシャルデータバンク テックブログ

Discussion