株式会社ネットワールドのエンジニアがお届けする技術情報ブログです。
各製品のエキスパートたちが旬なトピックをご紹介します。

GitとCI/CDに関する知識ゼロのSEが、GitLabで取り消し (リバート) をGUIから試す話 (2023/07/04 更新)

2023/07/04 画面ショットをGitLab Enterprise Edition 16.2.0-pre のGUIに差し替えました。

皆様こんにちは。SEの小池と申します。

GitLabでは、ブランチに含まれるソースコードの変更をマージ前にチェックする方法 及び それを提供する機能をマージリクエスト (略してMR) と呼んでいます。GitHubだとプルリクエストと呼ばれているアレです。
このマージリクエストを活用することで、コードレビューを効率化し、品質を向上させることが可能になります。

しかし時には、マージリクエストでマージ先を間違えてしまったりバグが含まれるコミットをそのままマージしてしまったり...ということがあるかと存じます。
今日はそんな間違えてマージしたマージリクエストやコミットをGUIから取り消し (リバート) する方法を試してまいります。

本記事の対象の方

  • GitLabでマージリクエストをGUIから取り消し (リバート) したい方
  • GitLabでマージリクエストのマージ先を間違えたので何とかしたい方
  • GitLabでマージリクエストに要らないコミットが入った状態でマージしたので戻したい方

今回のブログのゴール

このブログのゴールはこちらです。

今回のゴール
  • 特定のコミットをGUIから取り消し (リバート) する。
  • 特定のマージ済みのマージリクエストをGUIから取り消し (リバート) する。

このブログをお読みいただくにあたっての事前ご連絡事項

  • 本記事ではあくまでGUIからマージリクエスト 及び コミットを取り消し (リバート) する方法のみ記載しております。gitコマンドでは本ブログに記載している要件を実現する方法が他にも複数あるケースがありますが、それについては本記事では言及致しません。
  • 本記事はSaaS版 (GitLab.com) の GitLab Enterprise Edition 16.2.0-pre (Ultimate) における仕様をベースに記載しております。それ以外のエディションやバージョンではこの記事に記載の通りではない可能性がございます。
  • 本記事ではGitのリバート (Revert) 、リセット (Reset) 、リベース (Rebase) 、チェリーピック (Cherry-pick) に関する詳細な説明は記載しておりません。これについては恐れ入りますがGitの公式リファレンス (こちら) をご参照ください。
  • 本記事のGitLabのGUIは日本語にローカライズした状態で掲載しております。それ以外の言語をご利用の方は適宜読み替えてください。

GitLabにおけるリバート (Revert) とは?

GitLabにおけるリバート (Revert) は、特定のマージリクエスト もしくは 特定のコミットを取り消す操作を指します。
特に、GitLabのGUIからリバート (Revert) を使うと、新しいブランチを自動作成し、そこへ対象のコミットを取り消す変更内容を含んだコミットを作成してくれます。
また、この際任意でマージリクエストまで自動生成してくれます。

図を用いて説明致します。
例えば以下のような履歴のリポジトリがあるとします。


commit Bによる変更にバグが含まれていたと仮定します。


mainブランチにマージしてしまったcommit Bの変更内容を取り消す必要があります。
下の図でいうと、現在のmainブランチの最新はVer1.2ですが、これをVer1.1時点に戻す必要があります。


ここで使えるのがリバート (Revert) です。
mainブランチのVer1.2に対してリバート (Revert) をすると、新しいブランチを作成し、バグが含まれるcommit Bの変更を打ち消す内容を含んだコミット (commit B') を自動で作成してくれます。


自動作成されたブランチをmainにマージすることで、commit Bの変更内容を取り消し、実質的にVer1.1の内容と同じ状態にすることができます。
(ただし、変更履歴という観点ではcommit Bcommit B'のマージ履歴が残るので、Ver1.1の状態と完全に同じというわけではありません。)


こういったように、任意のマージ (もしくはコミット) を取り消すために、専用のブランチとコミット (と、任意でマージリクエスト) を同時に作成してくれるのが、GitLabのGUIにおけるリバートとなります。

マージ済みマージリクエストのリバート (Revert) について

GitLabのGUIにおいて、リバート (Revert) はコミット単位 もしくは マージリクエスト (マージ済み) 単位で実施することが可能です。
マージ済みのマージリクエストをリバートする場合、そのマージリクエストに含まれていた全コミットがリバートされるような変更内容を含むコミットが自動生成されます。

下図の場合、リバートしたいマージ済みマージリクエストにはcommit A, commit B, commit C (赤枠部分) の3つのコミットが含まれています。


この状態でマージ済みマージリクエストをリバートすると、3つのコミットの変更内容をすべて打ち消す内容が含まれたコミットが新しいブランチに自動生成されます。


GitLabのGUIからマージリクエスト単位でリバートをする場合は、そのリバートの対象となるコミットの内容をよく確認して適切にリバートをする必要があります。

お試し1:特定のコミットのリバート

ここからは早速GitLabのGUIにてリバートを試していきたいと思います。
1つ目のお試しでは、シンプルに1つのコミットをリバートします。

シナリオ

まず、現状の変更履歴は以下の通りです。


この履歴でcommit Aをリバートします。
リバートに伴い、新しいブランチが作成され、そこにcommit Aを打ち消す変更commit A'がコミットされます。
自動生成されたブランチをmainブランチにマージすることで、最終的にmainブランチの状態をVer1.0と同等の状態に戻します。


リバート手順

まず、対象のプロジェクトで [コード] > [リポジトリグラフ] から現在の状況を確認します。
今回のシナリオでは、以下の図の「commit A (README.mdの更新。)」のコミットをリバートします。

リバートするコミットの点 (●) のあたりにカーソルをあてると、コミットしたユーザーや日時などが表示されますので、その状態でクリックします。

このコミットの詳細が別タブで表示されます。
画面中央にはこのコミットの変更内容が表示されます。今回の場合、README.mdに一行追加していることがわかります。

変更内容を確認し終わったら、リバートを開始します。
画面右上の [オプション] > [リバート] をクリックします。

リバートに際し、新ブランチは強制的に生成されます。
下の画面が表示されるので、新ブランチのマージ先ブランチの指定、MRを作成するか否かを指定します。
今回のシナリオではこれらはデフォルトのまま [リバート] をクリックします。

前の画面でマージリクエストのチェックを入れた場合は、マージリクエスト作成画面へ遷移します。
下図の通り、この画面ではリバート用のブランチ名、リバートするコミットIDなどが確認できます。
任意に設定し、[作成マージリクエスト] をクリックします。

マージリクエストが作成され、詳細画面が表示されます。
[変更] タブを開くと、このリバートにより生じる変更を確認することができます。

[概要] タブから [マージ] をクリックすると、リバートのために作成されたブランチがmainブランチにマージされます。

[コード] > [リポジトリ] から実際のファイルに意図した通りの変更が反映されたかを確認します。

なお、今回のシナリオ終了時点のリポジトリのグラフはこんな感じになります。
シナリオに記載した図が縦になっただけですね…あ、いや、想定通りなのでいいんですが。

以上が特定のコミットのリバートの試行でした。

お試し2:複数のコミットを含むマージ済みマージリクエストのリバート

2つ目のお試しでは、複数のコミットを含むマージリクエストを丸ごとリバートします。

シナリオ

まず、現状の変更履歴は以下の通りです。


このリポジトリで、mainブランチにcommit Acommit Bをマージしたマージリクエストをリバートします。リバートに伴い、新しいブランチが作成され、そこにcommit Acommit Bを打ち消す変更がコミットされます。
自動生成されたブランチをmainブランチにマージすることで、最終的にmainブランチの状態をVer1.0と同等の状態に戻します。


リバート手順

対象のプロジェクトで [コード] > [リポジトリグラフ] から現在の状況を確認します。
このシナリオでは、以下の図の「Merge branch 'feature-A' into 'main'」のマージをリバートします。

リバートするマージの点 (●) のあたりにカーソルをあてると、マージしたユーザーや日時などが表示されますので、その状態でクリックします。

マージリクエストをマージした際のコミットの詳細が別タブで表示されます。
画面中央にはこのコミットの変更内容が表示されます。今回の場合、commit Acommit Bは両方README.mdを編集しています。
なお、この画面からはマージリクエスト内の変更をまとめて確認できますが、commit Acommit Bのそれぞれの変更を個別に確認できません。もしマージリクエスト内の各コミットの変更内容を確認したい場合は左側のメニューから、[マージリクエスト] > [マージ済み] タブから対象のマージリクエスト名をクリックしてください。

変更内容を確認し終わったら、リバートを開始します。
画面右上の [オプション] > [リバート] をクリックします。

リバートに際し、新ブランチは強制的に生成されます。
下の画面が表示されるので、新ブランチのマージ先ブランチの指定、MRを作成するか否かを指定します。
今回のシナリオではこれらはデフォルトのまま [リバート] をクリックします。

前の画面でマージリクエストのチェックを入れた場合は、マージリクエスト作成画面へ遷移します。
下図の通り、この画面ではリバート用のブランチ名、リバートするマージリクエストのIDなどが確認できます。
任意に設定し、[作成マージリクエスト] をクリックします。

マージリクエストが作成され、詳細画面が表示されます。
[コミット] タブを開いてみます。もともとリバートしたいマージリクエストにはコミットが2個含まれていましたが、ここではコミットが1個になっています。
この様にマージリクエストのリバートでは、そのマージリクエストに含まれるすべてのコミットの変更をまとめて1個のコミットで取り消すことになります。

[変更] タブを開くと、このリバートにより生じる変更を確認することができます。

[概要] タブから [マージ] をクリックすると、リバートのために作成されたブランチがmainブランチにマージされます。

[コード] > [リポジトリ] から実際のファイルに意図した通りの変更が反映されたかを確認します。

なお、今回のシナリオ終了時点のリポジトリのグラフはこんな感じになります。
前述したシナリオに記載した図が縦になった感じでございます。

以上が複数のコミットを含むマージ済みマージリクエストのリバートの試行でした。

お試し3:複数のコミットを含むマージ済みマージリクエストで特定のコミットだけリバート

ここでは複数のコミットを含むマージ済みマージリクエストにおいて、そこに含まれるコミットを1個だけリバートします。

シナリオ

現状の変更履歴は以下の通りです。


このリポジトリで、mainブランチにマージリクエストでマージしたcommit Acommit Bのうち、commit Bだけをリバートします。
リバートに伴い、新しいブランチが作成され、そこにcommit Bだけを打ち消す変更がコミットされます。
自動生成されたブランチをmainブランチにマージすることで、最終的にmainブランチの状態をVer1.0にcommit Aだけが反映された状態にします。

なお、このシナリオにおいては上記以外に、マージリクエスト全体をリバートさせてからcommit Aだけをチェリーピックで反映させる方法もありますが、この方法についてはまた別の機会にご紹介いたします。

リバート手順

最初に対象のプロジェクトで [コード] > [リポジトリグラフ] から現在の状況を確認します。
このシナリオでは、以下の図の「commit B (fileB.md新規追加)」のコミットだけをリバートします。

リバートしたいコミットの点 (●) のあたりにカーソルをあてると、コミットしたユーザーや日時などが表示されますので、その状態でクリックします。

コミットの詳細が別タブで表示されます。
画面中央にはこのコミットの変更内容が表示されます。今回の場合、commit BはファイルfileB.mdを新規追加したことがわかります。

変更内容を確認し終わったら、リバートを開始します。
画面右上の [オプション] > [リバート] をクリックします。

リバートに際し、新ブランチは強制的に生成されます。
下の画面が表示されるので、新ブランチのマージ先ブランチの指定、MRを作成するか否かを指定します。
今回のシナリオではこれらはデフォルトのまま [リバート] をクリックします。

前の画面でマージリクエストのチェックを入れた場合は、マージリクエスト作成画面へ遷移します。
下図の通り、この画面ではリバート用のブランチ名、リバートするコミットのIDなどが確認できます。
任意に設定し、[作成マージリクエスト] をクリックします。

マージリクエストが作成され、詳細画面が表示されます。
[コミット] タブを開いてみます。もともとリバートしたいマージリクエストにはコミットが2個含まれていましたが、そのうちcommit Bのみがリバートされることがわかります。

[変更] タブを開くと、このリバートにより生じる変更を確認することができます。

[概要] タブから [マージ] をクリックすると、リバートのために作成されたブランチがmainブランチにマージされます。

[コード] > [リポジトリ] から実際のファイルに意図した通りの変更が反映されたかを確認します。

今回のシナリオ終了時点のリポジトリのグラフはこんな感じになります。

以上が複数のコミットを含むマージ済みマージリクエストで、特定のコミットだけリバートする場合の試行方法でした。

リバートに関する注意事項と回避策

注意事項:コミットは同じブランチに複数回マージしても、2回目以降は変更が反映されない

一般的にGitにおけるリバート (Revert) は「特定のコミットによる変更内容を取り消すためのコミットを自動作成する」ことが可能です。
つまり、特定のコミットをリバートしたとしても、取り消したい対象のコミットをマージした履歴はそのまま残ります。
この仕様はGitLabのリバート (Revert) でも同様です。


そして通常のGitの仕様として、同一のブランチにて同じコミットIDのコミットは複数回マージしたとしても、2回目以降はその変更内容が反映されない点にご注意ください。
厳密には、過去にマージした履歴があるコミットIDは、そのコミットを再度マージさせても変更内容が反映されません。


この仕様はGitLabでも同様です。
下図はマージリクエストに含まれるコミットの一覧画面です。このマージリクエストにはコミットが2個含まれますが、このうち赤枠内にあるcommit B過去にマージしたことがあるので、今回マージしても変更内容が反映されないコミットです。
普通に変更が反映されるコミットと違って、「マージ済みのコミット 」という欄に表示されているので、この欄に入っているコミットが過去にマージしたことがあるコミットIDを指し示しているものと思われます。

この件はGitにおけるリバート (Revert) あるあるのようで、「リバート マージできない」等のキーワードでインターネット上に様々な事例が存在しますので、ご興味がおありの方は検索していただければと存じます。

Gitにおいてこの件の対応方法としては、そもそもリバート (Revert) ではなくリベース (Rebase) で対応する、リバート (Revert) 後に必要なコミットだけチェリーピック (Cherry-pick) する、そもそもリバート (Revert) ではなく リセット (Reset) で対応する等の方法が挙げられますが、GitLabのGUIではリバート (Revert) 後に必要なコミットだけチェリーピック (Cherry-pick) することで、この "マージしても変更が反映されない問題" を回避できます。
リバートで作成されたマージリクエストに、必要なコミットをチェリーピックする方法は次章でご紹介いたします。

回避策:任意のコミットを特定のマージリクエストにチェリーピックする

GitLabのGUIでは、チェリーピックを用いることで前述の "マージしても変更が反映されない問題" を回避することができます。
具体的には以下の図のような感じで回避します。

以下はGitLabのマージリクエストの画面です。
赤枠が マージしても変更が反映されない問題が発生するコミット で、緑枠が commit Bと同じ変更内容だけど、チェリーピックで作ったコミットなのでマージ時に変更内容が反映されるコミット です。
赤枠と緑枠のcommit Bはどちらも同じ変更内容ですが、コミットIDが異なることがわかります。
また、緑枠内のチェリーピックで作成したコミットの方は「マージ済みのコミット 」一覧ではなく、通常のコミットの欄に表示されていることがわかります。

最後にちょこっとだけ解説です。
チェリーピックは元のコミットの変更内容をそのまま用いますが、コミットIDが変わります。
Gitはコミットの履歴をコミットIDで管理しているので、コミットIDが異なるのであれば変更内容が一緒であったとしても別のコミットであると判断されます。この仕様により、チェリーピックを使うことで前に紹介した「マージをしたことがあるコミットは、同じブランチに再度コミット・マージしても変更内容が反映されない問題」を回避することができます。
ネットの海を眺めていると、Gitは「同じコミットIDのコミットを再度マージしても2回目以降は変更が反映されない」という仕様をご存じないまま「なんかよくわからんがリバートは危険なものだ!」と早合点なさっている方もちらほら見受けられたので、ここで簡単に説明致しました次第です。

最後に

この度はGitもCI/CDもよくわかっていないど素人SEによるGitLab検証ブログをお読みいただき、誠にありがとうございます。
このブログの目標は以下のとおりでしたが、皆さまはいかがでしたでしょうか。


今回のゴール
  • 特定のコミットをGUIから取り消し (リバート) する。
  • 特定のマージ済みのマージリクエストをGUIから取り消し (リバート) する。

筆者はこのブログを書き始めるまで "リバート" という言葉すら知らない状況だったので、正直この記事は書くのに骨が折れました…。
この記事がGitLabを触り始めた方、および、Gitのリバート仕様で躓いていらっしゃる方の一助となれば幸いにございます。


GitLabに関するお問い合わせは、以下のフォームからお願い致します。
GitLab製品 お問い合わせ

今までのGitLabの検証ブログはこちらです。
GitLab カテゴリーの記事一覧 - ネットワールド らぼ

GitLab操作デモ動画 (基本編) を作ってみました。(音声の録音は自宅でiPhoneのボイスメモ使うという超低クオリティですが…。)
つたない内容ではありますが、ご興味がおありでしたら是非ご視聴いただければと存じます。

www.youtube.com