【GitLab】git revert コマンドで差分を打ち消す具体的な手順
はじめに
バージョン管理システムは、ソフトウェア開発において不可欠なツールです。特に分散型バージョン管理システムであるGitは、その柔軟性と堅牢性から世界中で広く利用されています。そして、Gitリポジトリを管理するためのプラットフォームとして、GitHub、GitLab、Bitbucketなどがあります。本記事では、GitLabをプラットフォームとして利用している環境を想定し、Gitの強力なコマンドの一つであるgit revert
に焦点を当てて、その使い方を詳細に解説します。
開発の現場では、予期せぬバグの混入、仕様変更による過去のコミット内容の無効化、あるいは単なる手違いなどにより、過去に行った変更を取り消したいという状況が頻繁に発生します。このような「取り消し」の操作は、バージョン管理システムを扱う上で避けて通れない重要なスキルです。
Gitにはいくつかの「取り消し」の方法がありますが、それぞれに特性があり、特に複数人で共同開発を行う共有リポジトリ(GitLabなどで管理されているリポジトリ)においては、どの方法を選択するかが非常に重要になります。本記事では、Gitにおける主要な取り消し方法を比較しつつ、特に共有リポジトリでの利用に推奨されるgit revert
コマンドについて、その基本概念から具体的な手順、様々なシナリオでの応用、そして注意点やトラブルシューティングまで、約5000語をかけて詳細に解説します。
GitLabでチーム開発を行っている方、またはGitの取り消し操作について深く理解したい方は、ぜひ最後までお読みください。
Gitの基本的な「取り消し」方法の比較
Gitには、様々な粒度や目的で変更を取り消すためのコマンドがいくつか存在します。主要なものとして、git reset
、git revert
、そしてファイル単位の変更を取り消すgit checkout
(または新しいコマンドであるgit restore
)があります。これらの違いを理解することは、状況に応じて最適なコマンドを選択するために不可欠です。
1. git reset
git reset
は、指定したコミットの状態までブランチのHEADポインタと必要に応じてインデックス、ワーキングツリーを移動させるコマンドです。これは、過去に時間を巻き戻すような操作であり、履歴を書き換える特性があります。
-
主なオプション:
git reset --soft <commit>
: HEADを指定したコミットに移動させますが、インデックスとワーキングツリーは変更しません。つまり、指定したコミット以降のすべての変更がステージングされた状態になります。git reset --mixed <commit>
(デフォルト): HEADとインデックスを指定したコミットに移動させます。ワーキングツリーは変更しません。指定したコミット以降の変更は、インデックスからは解除されますが、ワーキングツリーには残ります。git reset --hard <commit>
: HEAD、インデックス、ワーキングツリーの全てを指定したコミットの状態に戻します。指定したコミット以降の変更は、ワーキングツリーからも完全に失われます。このオプションは非常に強力で、注意が必要です。
-
git reset
の利用シーン:- まだリモートリポジトリにプッシュしていないローカルのコミットを取り消したい場合。
- 直前のコミットに手違いがあった場合など、個人的な作業ブランチで履歴を綺麗にしたい場合。
-
git reset
の注意点:git reset --hard
はワーキングツリーの変更を失うため、実行前に確認が必要です。- 最も重要な注意点は、すでにリモートリポジトリ(GitLabなど)にプッシュ済みのコミットに対して
git reset
を使用すると、他の開発者の履歴と食い違いが発生し、コンフリクトや問題を引き起こす可能性が高いということです。特に共有ブランチ(例:main
,develop
など)に対してプッシュ済みのコミットをgit reset
で取り消し、そのブランチにgit push --force
などで強制的にプッシュし直すことは、チーム開発においては極めて危険な行為であり、避けるべきです。他の開発者がその古い履歴に基づいて作業している可能性があるからです。
2. git revert
git revert
は、指定したコミットの変更内容を打ち消すための新しいコミットを作成するコマンドです。git reset
のように履歴を書き換えるのではなく、履歴に新たな情報を追加することで過去の変更を無効にします。
-
git revert
の利用シーン:- すでにリモートリポジトリ(GitLab)にプッシュ済みのコミットを取り消したい場合。
- 共有ブランチにマージされてしまった変更を取り消したい場合。
- 履歴を残したまま特定の変更を無効にしたい場合。
-
git revert
のメリット:- 履歴を書き換えないため、他の開発者の作業に影響を与えにくい。
- 取り消し操作自体がコミットとして記録されるため、追跡可能である。
- 共有リポジトリでの安全な取り消し方法として広く推奨される。
-
git revert
のデメリット:- 打ち消したいコミットが多い場合、その数だけrevertコミットが増え、履歴が冗長になる可能性がある。
- 打ち消したいコミット以降に多くの変更が加わっている場合、コンフリクトが発生しやすい。
3. git checkout
/ git restore
これらのコマンドは、コミットそのものを取り消すのではなく、特定のファイルやディレクトリの変更を作業ツリーやインデックスから取り消すために使用されます。
git checkout <file>
またはgit restore <file>
: ワーキングツリー内の指定したファイルの変更を、直近のコミットまたはインデックスの状態に戻します。git checkout -- <file>
またはgit restore --staged <file>
: ステージングエリア(インデックス)内の指定したファイルの変更を、直近のコミットの状態に戻します。
これらのコマンドはコミットを取り消すものではないため、本記事の主題からは外れますが、Gitにおける「取り消し」操作の一部として理解しておくと役立ちます。
なぜGitLab環境でgit revert
を選ぶべきか
GitLabのような共有リポジトリ管理プラットフォームを利用したチーム開発においては、他の開発者との連携が重要です。git reset --hard
のように履歴を書き換える操作は、他の開発者がすでにその古い履歴(書き換え前の履歴)に基づいて作業を進めている場合、混乱やコンフリクトの原因となります。特に、git pull
やgit fetch
によってローカルリポジトリがリモートの履歴を取り込んだ後に、リモートの履歴が書き換えられると、ローカルの履歴との間に互換性のない差異が生じ、解決が困難になることがあります(例: “non-fast-forward”エラーなど)。
一方、git revert
は新しいコミットを追加するだけなので、既存の履歴には一切手を加えません。これにより、他の開発者は自分のローカルリポジトリを更新する際に、単に新しいrevertコミットを取り込むだけで済み、履歴の整合性が保たれます。
したがって、特にすでにリモートリポジトリにプッシュした変更を取り消したい場合は、git revert
を使用するのが最も安全で推奨される方法です。本記事では、GitLab環境での開発を想定し、このgit revert
コマンドの使い方を徹底的に解説します。
git revert
コマンドの基本
git revert
コマンドは、特定のコミットによって導入された変更を打ち消す新しいコミットを作成するために使用されます。
コマンドの構文
基本的な構文は以下の通りです。
bash
git revert <commit-hash>
ここで<commit-hash>
は、打ち消したいコミットのハッシュ値です。ハッシュ値は、git log
コマンドやGitLabのコミット履歴画面で確認できます。
git revert
の動作
git revert <commit-hash>
を実行すると、Gitは以下の処理を行います。
- 指定された
<commit-hash>
の差分(変更内容)を特定します。 - その差分と完全に逆の差分を持つファイル変更をワーキングツリーに適用しようとします。
- 変更の適用中にコンフリクトが発生しないか確認します。コンフリクトが発生した場合は、ユーザーに解決を求めます。
- コンフリクトがなければ、またはコンフリクトが解決されれば、その打ち消し変更を含む新しいコミットを作成するための準備をします(インデックスにステージングします)。
- デフォルトでは、コミットメッセージの編集画面が開きます。標準的なメッセージは、「Revert “<元のコミットのタイトル>”」の形式で自動生成されます。元のコミットのハッシュ値も含まれます。
- メッセージを編集・保存すると、新しいコミット(revertコミット)が作成され、現在のブランチのHEADとして追加されます。
このように、git revert
は履歴を「打ち消す」のではなく、履歴に「打ち消したという事実」を新しいコミットとして追加します。
作成されるコミットメッセージ
git revert
を実行すると、デフォルトで以下のようなコミットメッセージが生成されます。
“`
Revert “<元のコミットのタイトル>”
This reverts commit <元のコミットのハッシュ値>.
“`
例えば、「Add user authentication」というタイトルのコミット(ハッシュ値 abcdef1
)をrevertした場合、デフォルトのメッセージは以下のようになります。
“`
Revert “Add user authentication”
This reverts commit abcdef1.
“`
このデフォルトメッセージは、どのコミットを打ち消したのかが明確にわかるため、通常はそのまま使用します。必要に応じて、revertした理由などを追記することも可能です。メッセージを編集・保存することで、revertコミットが作成されます。
git revert
の主なオプション
git revert
コマンドにはいくつかの便利なオプションがあります。
-n
または--no-commit
: 通常、git revert
は打ち消し変更を新しいコミットとしてすぐに作成しますが、このオプションを付けると、変更をワーキングツリーに適用し、インデックスにステージングするだけで、コミットは行いません。これにより、複数のコミットのrevertを一度にまとめたり、revertによって適用された変更をさらに手動で調整したりすることが可能になります。コミットは後でgit commit
コマンドを別途実行して行います。-e
または--edit
: デフォルトでは、revertコミットのメッセージ編集画面が表示されますが、このオプションは明示的にメッセージ編集を要求します。通常はデフォルトの動作なのであまり意識する必要はありませんが、-n
オプションなど他のオプションと組み合わせて使用されることがあります。-m <parent-number>
または--mainline <parent-number>
: マージコミットをrevertする場合に使用します。マージコミットは複数の親を持ちますが、このオプションでどの親の視点から変更を打ち消すかを指定します。通常、マージコミットの親は、マージ先のブランチのHEAD(親1)と、マージ元のブランチのHEAD(親2)です。例えば、featureブランチをmainブランチにマージしたマージコミットをrevertし、featureブランチで行われた変更(親2側)を打ち消したい場合は、-m 1
を指定します。マージコミットのrevertは少し複雑なので、後述のシナリオで詳しく解説します。
これらのオプションを組み合わせることで、より柔軟なrevert操作が可能になります。
具体的なgit revert
のシナリオと手順(GitLab環境を意識して)
ここからは、実際の開発で遭遇しうる様々なシナリオにおいて、git revert
コマンドをどのように使用するか、具体的な手順を追って説明します。GitLabをリモートリポジトリとして使用していることを前提とし、ローカルでの操作からGitLabへの反映までを含めて解説します。
シナリオ1: 直前のコミットをrevertする
これは最もシンプルなケースです。ローカルで作業していて、直前のコミットに間違いがあったが、すでにそのコミットをリモート(GitLab)にプッシュしてしまった、あるいはプッシュする予定のブランチで、履歴を書き換えたくない場合に利用します。
手順:
- 対象ブランチにいることを確認:
bash
git branch
現在のブランチが表示されます。必要であればgit checkout <対象ブランチ名>
で移動します。 - revert対象のコミットを確認 (任意):
直前のコミットはHEAD
またはHEAD^
で参照できますが、念のためgit log
で確認できます。
bash
git log --oneline -1 # 直近1件のコミットを表示
# または
git log --oneline # 複数表示して確認
表示される一番上のコミットが直前のコミットです。 git revert HEAD
またはgit revert <直前コミットのハッシュ>
を実行:
直前のコミットをrevertするには、HEAD
を指定するのが簡単です。
bash
git revert HEAD
または、git log
で確認した直前のコミットのハッシュ値(例:abcdef1
)を指定します。
bash
git revert abcdef1
このコマンドを実行すると、デフォルトではエディタが開き、revertコミットのメッセージが表示されます。- コミットメッセージを編集・保存:
生成されたメッセージは通常Revert "<元のコミットのタイトル>"
となっています。特別な理由がなければそのまま保存(Vimなら:wq
、NanoならCtrl+X
,Y
,Enter
など)します。 - revertコミットが作成されたことを確認:
bash
git log --oneline -2 # 直近2件のコミットを表示
直前のコミットの上に、新しく「Revert …」というメッセージのコミットが追加されているのが確認できます。これがrevertコミットです。元のコミットは履歴に残ったままです。 - 変更をリモートリポジトリ (GitLab) にプッシュ:
ローカルでrevertコミットが作成されたら、通常通りリモートリポジトリにプッシュします。
bash
git push origin <対象ブランチ名> - GitLabでの確認:
GitLabのWeb UIを開き、該当プロジェクトの Repository > Commits 画面に移動します。プッシュしたブランチを選択すると、新しく作成された「Revert …」というコミットがコミット履歴の先頭に追加されているのが確認できます。このrevertコミットによって、その直前のコミットで行われた変更が打ち消されている状態になります。
もしこのブランチをマージリクエスト(MR)として作成している場合、MRの Changes タブや Commits タブでもrevertコミットとその変更内容(元のコミットの逆の変更)を確認できます。
このシナリオでは、履歴を書き換えることなく、安全に直前の変更を取り消すことができました。
シナリオ2: 過去の特定のコミットをrevertする
直前ではなく、過去のある特定のコミットの変更だけを打ち消したい場合があります。例えば、いくつかの機能追加やバグ修正のコミットを行った後で、その中の特定のコミットに含まれる変更だけが問題を引き起こしていることが判明した場合などです。
手順:
- 対象ブランチにいることを確認:
bash
git checkout <対象ブランチ名> - revert対象のコミットのハッシュを特定:
git log
コマンドやGitLabのコミット履歴画面を使って、revertしたい過去のコミットのハッシュ値を特定します。
bash
git log --oneline --graph --all # 視覚的に履歴を確認
または、GitLabのWeb UIで、該当プロジェクトの Repository > Commits にアクセスし、目的のコミットを探します。コミットリストには、各コミットのタイトルと短いハッシュ値が表示されています。詳細を確認するには、コミットのタイトルをクリックします。コミットの詳細画面で、フルハッシュ値を確認できます。目的のコミットのハッシュ値(例:abcdef1234567890...
)をコピーしておきます。 git revert <過去のコミットハッシュ>
を実行:
特定したハッシュ値(例:abcdef1
)を指定してrevertコマンドを実行します。
bash
git revert abcdef1-
コンフリクトの発生と解決:
過去のコミットをrevertする場合、そのコミット以降で同じファイルや同じ箇所のコードが変更されていると、コンフリクトが発生する可能性が高くなります。コンフリクトが発生すると、Gitはrevert処理を中断し、コンフリクトしているファイルを作業ツリーに残します。ターミナルにはコンフリクトが発生した旨が表示されます。- コンフリクト解決の手順:
git status
を実行して、コンフリクトしているファイルを確認します。- コンフリクトしているファイルをエディタで開きます。ファイル内には、コンフリクトマーカー (
<<<<<<<
,=======
,>>>>>>>
) が挿入されています。 - これらのマーカーを参考に、どのコードを残し、どのコードを破棄するかを手動で編集します。通常は、revertしたいコミットの変更(
HEAD
側)を打ち消す変更を、現在のブランチの状態(<<<<<<< HEAD
から=======
の間)に反映させることになります。Gitが自動で適用しようとしたrevertの変更は=======
から>>>>>>> <revert対象のハッシュ>
の間に表示されていますが、これはあくまで参考情報であり、最終的なファイル内容は手動で編集して決定します。 - コンフリクトマーカーを全て取り除き、ファイルの内容を正しい状態に編集します。
- 編集が完了したら、変更したファイルをステージングします。
bash
git add <コンフリクトを解決したファイル名>
コンフリクトしているファイルが複数ある場合は、全てについてこの作業を繰り返します。 - 全てのコンフリクトが解決され、ファイルがステージングされたら、revertコミットを完了させます。Gitはrevert処理の途中であることを認識しているため、
git commit
を引数なしで実行すると、revertコミットを作成するためのメッセージ編集画面が開きます。
bash
git commit - コミットメッセージ(通常は「Revert …」というメッセージ)を確認・編集し、保存します。
コンフリクトが発生せずrevertが成功した場合は、ステップ4はスキップされ、ステップ5に進みます。
5. revertコミットが作成されたことを確認:
bash
git log --oneline
新しいrevertコミットがコミット履歴に追加されていることを確認します。
6. 変更をリモートリポジトリ (GitLab) にプッシュ:
ローカルでrevertコミットが作成されたら、通常通りリモートリポジトリにプッシュします。
bash
git push origin <対象ブランチ名>
7. GitLabでの確認:
GitLabのWeb UIで、該当プロジェクトの Repository > Commits 画面に移動し、新しく作成されたrevertコミットを確認します。そのコミットによって、特定した過去のコミットの変更が打ち消されていることを確認できます。マージリクエストに関連するブランチであれば、MR画面でも確認できます。 - コンフリクト解決の手順:
過去のコミットをrevertする場合、後続のコミットとの間でコンフリクトが発生する可能性が高いため、コンフリクト解決の手順をしっかりと理解しておくことが重要です。
シナリオ3: 複数のコミットをまとめてrevertする
連続した複数のコミットの変更をまとめて打ち消したい場合や、複数の離れたコミットの変更をまとめてrevertコミットとして記録したい場合があります。
方法1: 個別にrevertコミットを作成する
revertしたいコミットごとに git revert <commit-hash>
を順番に実行します。例えば、コミットA、B、C(Cが最新)をrevertしたい場合、C → B → A の順にrevertするのが直感的ですが、Gitはどの順番でもrevertできます。ただし、過去に遡ってrevertするほど、後続のコミットとの間でコンフリクトが発生しやすくなります。
“`bash
最新のコミットCをrevert
git revert
次にコミットBをrevert
git revert
次にコミットAをrevert
git revert
“`
この方法では、revertしたいコミットの数だけrevertコミットが作成されます。それぞれのrevertコミットでコンフリクトが発生する可能性があり、個別に解決が必要です。GitLabへのプッシュは、全てのrevertコミットを作成した後、まとめて行います。
方法2: -n
(no-commit) オプションを使ってまとめてコミットする
revertしたい複数のコミットの変更を、一つのrevertコミットとしてまとめたい場合に有効です。
手順:
- 対象ブランチにいることを確認:
bash
git checkout <対象ブランチ名> - revert対象のコミットのハッシュを特定:
git log
または GitLabで、revertしたい全てのコミットのハッシュを特定します(例:hash-A
,hash-B
,hash-C
)。 -n
オプションを使ってrevertを適用:
revertしたいコミットに対して、-n
オプションを付けて順番にrevertコマンドを実行します。どの順番で実行しても最終的なワーキングツリーの状態は同じになりますが、コンフリクトの発生タイミングが異なる場合があります。一般的には、古いコミットから新しいコミットの順にrevertを適用していく方が、後でコンフリクトをまとめて解決しやすいかもしれません。
bash
git revert <hash-A> -n # コミットは作成されない
# コンフリクトが発生した場合は、一旦解決してから次のrevertに進む
git revert <hash-B> -n # コミットは作成されない
# コンフリクトが発生した場合は、一旦解決してから次のrevertに進む
git revert <hash-C> -n # コミットは作成されない
# コンフリクトが発生した場合は、ここでまとめて解決
-n
オプションを付けてrevertを実行するたびに、Gitは指定されたコミットの逆の変更をワーキングツリーに適用しようとします。コンフリクトが発生した場合は、その時点でrevert処理が中断し、手動での解決が必要になります。解決後、git add
でステージングしてから、次のgit revert -n
コマンドを実行します。
全てのrevert対象に対して-n
オプションでのrevertを終えた後、ワーキングツリーには全てのrevert変更がマージされた状態になっています(コンフリクトは全て解決済み)。これらの変更はインデックスにステージングされています。- まとめてコミット:
全てのrevert変更がワーキングツリーに反映され、インデックスにステージングされたら、git commit
コマンドでまとめて一つのコミットを作成します。
bash
git commit
コミットメッセージは自動生成されないため、手動で適切に記述する必要があります。例えば、「Revert changes introduced by commits A, B, and C」のように、どのコミットの変更を打ち消したのかが分かるように記述します。 - revertコミットが作成されたことを確認:
bash
git log --oneline
新しく作成された一つのrevertコミットが追加されていることを確認します。 - 変更をリモートリポジトリ (GitLab) にプッシュ:
bash
git push origin <対象ブランチ名> - GitLabでの確認:
GitLabのWeb UIで、作成された一つのrevertコミットを確認します。
この方法では、複数のコミットの変更をまとめて打ち消す一つのコミットを作成できるため、履歴がシンプルになります。ただし、複数のコミットの変更をまとめて扱うため、コンフリクト解決がより複雑になる可能性があります。
シナリオ4: マージコミットをrevertする
マージコミットは、複数の開発履歴(ブランチ)を統合した特別なコミットです。マージ後に問題が発覚した場合、そのマージコミット全体をrevertすることで、マージによって導入された変更を取り消すことができます。ただし、マージコミットのrevertは少し複雑であり、注意が必要です。
マージコミットは通常2つ以上の親を持ちます。例えば、feature
ブランチをmain
ブランチにマージした場合、マージコミットの親は以下のようになります。
- 親1: マージ操作を実行した時点での
main
ブランチのHEAD。 - 親2: マージされた
feature
ブランチのHEAD。
git revert
が通常のコミット(親が1つ)をrevertする場合、そのコミットの親との差分を打ち消す変更を作成します。しかし、マージコミットには複数の親があるため、どの親を基準にして変更を打ち消すのかを指定する必要があります。これを指定するのが -m <parent-number>
または --mainline <parent-number>
オプションです。
<parent-number>
には、revert操作で「維持したい親」の番号を指定します。つまり、指定した親以外のブランチから持ち込まれた変更を打ち消すことになります。一般的なマージコミットの場合、親1はマージ先のブランチ、親2はマージ元のブランチです。例えば、feature
ブランチの変更(親2)をmain
ブランチ(親1)に取り込んだマージコミットをrevertし、feature
ブランチから持ち込まれた変更を打ち消したい場合は、マージ先のブランチである親1を維持したい親として指定します。したがって、-m 1
を指定します。
手順:
- 対象ブランチにいることを確認:
bash
git checkout <対象ブランチ名> # 例: main ブランチ - revert対象のマージコミットのハッシュを特定:
git log --merges
コマンドでマージコミットのみを表示するか、git log --graph --oneline
などで履歴を確認して、revertしたいマージコミットのハッシュを特定します。
また、どの親が親1でどの親が親2なのかを確認するために、マージコミットの詳細情報を見ます。
bash
git log -1 <merge-commit-hash>
出力例:
“`
commit
Merge:Author: …
Date: …Merge branch 'feature' into 'main'
`Merge:` 行に表示されているハッシュ値が親のハッシュ値です。左側が親1、右側が親2です。親1がマージ先のブランチ(例: `main`)のHEAD、親2がマージ元のブランチ(例: `feature`)のHEADであることが多いですが、必ずしもそうとは限らないので、ハッシュ値から元のブランチを特定できるとより確実です。GitLabのMR画面でマージコミットの元となったMRを確認すると、マージ元・マージ先のブランチが明確です。
bash
通常、revertしたいのはマージによって導入された変更、つまりマージ元のブランチ(親2)の変更を打ち消したい場合が多いでしょう。その場合、マージ先のブランチの状態(親1)を維持したい親として指定するため、`-m 1` を使用します。
3. **`git revert -m <親の番号> <マージコミットハッシュ>` を実行:**
マージによって導入された変更(親2側の変更)を打ち消すために、`-m 1` オプションを指定してrevertします。
git revert -m 1
4. **コンフリクトの発生と解決:**
bash
マージコミットのrevertもコンフリクトが発生しやすい操作です。特にマージ後にマージ先ブランチで開発が進んでいる場合、コンフリクトのリスクは高まります。コンフリクトが発生した場合は、シナリオ2と同様の手順でコンフリクトを解決します。コンフリクト解決…
git add <コンフリクトを解決したファイル>
git commit
5. **revertコミットが作成されたことを確認:**
bash
git log –oneline
新しいrevertコミットが追加されていることを確認します。メッセージは通常「Revert "Merge branch '...'"」のようになります。
bash
6. **変更をリモートリポジトリ (GitLab) にプッシュ:**
git push origin <対象ブランチ名>
“`
7. GitLabでの確認:
GitLabのWeb UIで、作成されたrevertコミットを確認します。このコミットによって、以前マージされた内容が打ち消されている状態になります。
マージコミットをrevertした後の注意点:
マージコミットをrevertした後、同じマージ元のブランチを再度マージしようとすると、Gitはすでに一度マージ済みであると判断し、何もしない(差分を取り込まない)という問題が発生する可能性があります。これは、Gitがマージ済みかどうかを判断する際に、履歴上のつながり(先祖関係)を見ているためです。revertコミットはマージコミットそのものを打ち消すものではなく、その差分を逆にするコミットを追加しただけなので、Gitから見れば元のマージコミットはまだ履歴上に存在し、「マージは完了している」と判断されてしまうのです。
もし、revertしたマージ元のブランチの変更を、後で修正した上で再度マージしたいという場合は、以下のいずれかの方法を検討する必要があります。
- 元のrevertコミットをさらにrevertする: revertコミットそのものを
git revert
します。これにより、元のマージコミットの差分が復活します。その上で、必要に応じてさらに修正コミットを追加してから再度マージを試みます。これは後述の「revertしたコミットをさらにrevertする」シナリオで説明します。 git merge --no-ff
で新しいマージコミットを作成する: 高度な方法ですが、git merge
に--no-ff
オプションを付けて、ファストフォワードマージを防ぎ、常に新しいマージコミットを作成するように強制する方法があります。ただし、この場合も差分が正しく取り込まれるかは状況によります。- cherry-pickを使用する: revertしたマージ元のブランチから、再度取り込みたい特定のコミットを
git cherry-pick
で現在のブランチに適用します。この方法であれば、マージ済みの判断に影響されず、必要な変更のみを取り込めます。
マージコミットのrevertは慎重に行い、その後の再マージの際には上記のような挙動を理解しておくことが重要です。
シナリオ5: revertしたコミットをさらにrevertする(元に戻す)
一度行ったgit revert
操作を取り消し、元のコミットによって導入された変更を再び適用したい場合があります。例えば、誤って必要なコミットをrevertしてしまった、またはrevertした変更をやっぱり元に戻すことにした、といったケースです。
これは非常に簡単で、revertコミットそのものをgit revert
するだけです。
手順:
- 対象ブランチにいることを確認:
bash
git checkout <対象ブランチ名> - revertしたいrevertコミットのハッシュを特定:
git log
コマンドやGitLabのコミット履歴画面を使って、取り消したいrevertコミットのハッシュ値を特定します。revertコミットのメッセージは通常「Revert “…”」の形式なので見つけやすいでしょう。
bash
git log --oneline
例えば、ハッシュ値abcdef1
が元のコミット、ハッシュ値fedcba1
がそのrevertコミットだとします。今回はfedcba1
をrevertします。 git revert <revertコミットのハッシュ>
を実行:
bash
git revert fedcba1
このコマンドを実行すると、Gitはハッシュ値fedcba1
のrevertコミットによって導入された差分(元のコミットの逆の差分)の、さらに逆の差分を作成しようとします。これは結局、元のコミット(abcdef1
)が持っていた差分と同じになります。- コンフリクトの発生と解決:
この場合もコンフリクトが発生する可能性があります。発生した場合は、シナリオ2と同様の手順で解決します。 - revert(のrevert)コミットが作成されたことを確認:
bash
git log --oneline
新しく「Revert “Revert ‘…'”」のようなメッセージのコミットが追加されているのが確認できます。この新しいコミットによって、元のコミットの変更が再び適用された状態になります。 - 変更をリモートリポジトリ (GitLab) にプッシュ:
bash
git push origin <対象ブランチ名> - GitLabでの確認:
GitLabのWeb UIで、新しく作成されたコミットを確認します。このコミットによって、最初にrevertした変更が元に戻されていることを確認できます。
このテクニックは、マージコミットをrevertした後に、同じブランチの変更を再マージしたい場合に非常に有効です。マージコミットをrevertし、その後revertコミットをさらにrevertすることで、マージコミットの差分が(新しいコミットとして)履歴に復活します。その上で、必要に応じて修正コミットを追加し、再度マージを行うことができます。ただし、これは履歴が少し複雑になるため、チーム内でどのように取り扱うかを事前に決めておく方が良いでしょう。
git revert
実行時の注意点とトラブルシューティング
git revert
は安全なコマンドですが、特に過去のコミットやマージコミットをrevertする際には、いくつかの注意点と発生しうる問題があります。
コンフリクトの発生
前述のシナリオでも触れましたが、git revert
の実行時にコンフリクトはよく発生します。これは、revert対象のコミットが変更したファイルや箇所が、それ以降のコミットでも変更されている場合に起こります。Gitはrevert対象のコミットの差分を現在のブランチの状態に適用しようとしますが、同じ箇所が異なる方法で変更されていると、どちらの変更を採用すべきか判断できずに衝突が発生します。
- 解決策: コンフリクトが発生したら、Gitの指示に従い、コンフリクトマーカーが挿入されたファイルを一つずつ手動で編集して解決します。
git status
でコンフリクトファイルを特定し、エディタで開き、<<<<<<<
,=======
,>>>>>>>
のマーカーを取り除きながら、正しいコードを記述します。解決後、git add <解決したファイル>
でステージングし、最後にgit commit
でrevertコミットを完了させます。コンフリクト解決はGitを使用する上で基本的なスキルであり、git revert
でも必要になります。 - GitLabでのコンフリクト解決: GitLabのWeb UIには、マージリクエストのコンフリクトをWeb上で解決する機能がありますが、これは通常、MRのターゲットブランチに変更がプッシュされたことでMRのソースブランチとの間にコンフリクトが発生した場合に使用します。
git revert
によってローカルで発生したコンフリクトは、ローカルの環境で解決する必要があります。ローカルでrevertコミットを作成し、プッシュした後に、そのコミットを含むMRでさらに別のコンフリクトが発生した場合などは、GitLabの機能も利用可能です。
すでにプッシュ済みのコミットをrevertする場合
これがgit revert
の最も得意とするシナリオです。 すでにリモートリポジトリ(GitLab)にプッシュ済みのコミットに対しても、git revert
は安全に実行できます。ローカルでrevertコミットを作成し、それを通常通り git push
するだけです。履歴は書き換わらないため、他の開発者に影響を与えるリスクが低いです。
対照的に、プッシュ済みのコミットに対して git reset --hard
を実行し、git push --force
で強制プッシュすることは、他の開発者のローカルリポジトリとリモートリポジトリの履歴を乖離させ、彼らが git pull
した際に問題(履歴の衝突)を引き起こす可能性が極めて高いため、避けるべきです。git revert
は、この問題を回避するための標準的な方法です。
過去のコミットをrevertした場合の影響
過去のコミットをrevertすると、そのコミットによって導入された変更が打ち消されます。しかし、それ以降のコミットがその変更に依存していたり、同じファイルを異なる方法で変更していたりする場合、意図しない副作用が生じる可能性があります。
- コンフリクト: 前述の通り、コンフリクトは最も一般的な問題です。
- 機能の不具合: Revertした変更に依存していた後続のコードが、revertによって壊れてしまう可能性があります。revert後には、十分なテストを実施して、影響範囲と意図しない不具合がないかを確認することが重要です。
- 部分的な打ち消し: Revert対象のコミットで行われた変更の一部が、後続のコミットで再度導入されている場合、revertしてもその部分の変更は打ち消されません。これはGitの差分計算の性質によるものです。このような場合、revertコミットを作成した後、手動でさらに修正が必要になることがあります。
大規模な変更を含むコミットのrevert
大量のファイル変更や複雑なリファクタリングを含む大規模なコミットをrevertしようとすると、コンフリクトが発生する可能性が非常に高まり、その解決も困難になる傾向があります。
- 解決策: 可能であれば、大規模な変更を複数の小さなコミットに分割しておくと、後で個別にrevertが必要になった場合のリスクを減らせます。また、revert時にコンフリクトが発生した場合は、時間をかけて慎重に解決する必要があります。必要であれば、一度revertを中断し、手動でそのコミットの変更を打ち消すための作業を別途行って、それを新しいコミットとして作成する方が簡単な場合もあります。
revertしたマージコミットの再マージ
シナリオ4の注意点で述べたように、マージコミットをrevertした後で、同じマージ元のブランチを再度マージしようとすると、Gitがそれを無視する可能性があります。
- 解決策: 最も一般的な対処法は、最初に作成したrevertコミットをさらにrevertすることです。これにより、元のマージコミットの差分が新しいコミットとして復活します。その上で、必要に応じてさらに修正コミットを追加し、再度マージ操作を行います。あるいは、必要なコミットをcherry-pickするという方法もあります。重要なのは、この挙動を理解し、意図した通りの結果を得られるように適切な手順を選択することです。
GitLab上での操作との連携
git revert
コマンドは、主にローカルのターミナルで実行されるコマンドです。しかし、GitLabのWeb UIもGitの操作をサポートしており、連携して利用することでより効率的な開発が可能になります。
- コミット履歴の確認:
git log
と同様に、GitLabのWeb UIでコミット履歴を簡単に確認できます。プロジェクトの Repository > Commits にアクセスすると、ブランチごとのコミットリストが表示されます。ここでrevertしたいコミットのハッシュ値やタイトルを確認したり、マージコミットの詳細(どの親を持つかなど)を確認したりできます。 - ファイルの変更履歴: GitLabでは、特定のファイルの変更履歴(BlameやHistory)も確認できます。revertによってファイルがどのように変更されたか、あるいはコンフリクト解決のために手動でどのように編集したかなどを追跡するのに役立ちます。
- マージリクエスト:
git revert
によって作成されたコミットを、マージリクエストのソースブランチに含めてプッシュすることで、MRのレビュー担当者はrevertの変更内容を確認できます。これは、特定のコミットをrevertした理由や、それによって何が変更されたかをチームメンバーに共有する上で重要です。GitLabのMR画面では、影響を受けたファイルの変更内容(Diff)も確認できます。
GitLabのWeb UI上で直接git revert
のような操作を行う機能は(ファイルを過去の状態に戻すような簡易的な機能はありますが)、完全なgit revert
コマンドの機能を提供しているわけではありません。したがって、複雑なrevert操作は基本的にローカルの環境でコマンドを実行し、その結果をGitLabにプッシュするという流れになります。
git revert
とGitLabにおけるチーム開発
GitLabは、Gitリポジトリを中心とした共同開発を支援するプラットフォームです。チーム開発においては、個々の開発者がローカルで行った作業を共有リポジトリで安全に共有し、他の開発者と連携しながらプロジェクトを進めていく必要があります。このような環境において、git revert
コマンドは非常に重要な役割を果たします。
なぜ共有リポジトリ(GitLab)でgit revert
が推奨されるのか
繰り返しになりますが、GitLabのような共有リポジトリでのチーム開発においてgit revert
が推奨される最大の理由は、履歴を書き換えない点にあります。
- 履歴の透明性と追跡可能性:
git revert
は、取り消し操作自体を一つのコミットとして履歴に残します。これにより、「いつ」「誰が」「どのコミットの変更を打ち消したか」が履歴から明確に追跡できます。これはデバッグや問題の原因特定において非常に役立ちます。git reset --hard
のように履歴を削除してしまうと、何が起こったのか後から追跡するのが困難になります。 - 他の開発者への影響を最小限にする:
git revert
で作成されたコミットは、通常のコミットと同様に扱われます。他の開発者は、git pull
やgit fetch
でこの新しいrevertコミットを自分のローカルリポジトリに取り込むだけで済みます。履歴が書き換えられていないため、彼らのローカルの履歴との間で互換性のない差異が生じるリスクがほとんどありません。彼らは単に新しいコミットをマージまたはリベースするだけで、最新の状態に追従できます。 - コードレビューと承認フロー: GitLabでは、マージリクエスト(MR)を利用したコードレビュープロセスが一般的です。もし誤った変更がMRに含まれてマージされてしまった場合でも、そのマージコミットを
git revert
することで、マージされた変更を安全に取り消すことができます。revert操作自体を新しいMRとして作成し、チームメンバーにレビュー・承認してもらった上でマージするというフローも可能です。これにより、意図しない変更がプロダクション環境にデプロイされるリスクを低減できます。
ブランチ戦略とgit revert
の関連
Git FlowやGitLab Flowなどのブランチ戦略を採用している場合、main
(またはmaster
)やdevelop
といった共有ブランチは特に履歴の安定性が求められます。これらのブランチにマージされた変更に問題があった場合、git reset --hard
で履歴を書き換えることは通常許容されません。このような場合に、git revert
が安全な手段として利用されます。
例えば、feature
ブランチでの作業が完了し、develop
ブランチにマージした後で問題が発覚した場合、develop
ブランチでそのマージコミットをgit revert
します。これにより、問題のある変更はdevelop
ブランチから取り除かれます。その後、元のfeature
ブランチで問題を修正し、改めてdevelop
ブランチにマージし直す、といったワークフローが考えられます。
マージリクエストのレビュープロセスとgit revert
の活用
GitLabのマージリクエスト(MR)は、チーム開発における変更の提案、レビュー、承認、マージのワークフローを効率化します。git revert
は、このMRプロセスと密接に関連して活用されます。
- MRマージ後の問題: レビューを通過し、無事マージされた変更に、後で問題が見つかることは少なくありません。例えば、本番環境にデプロイした後にクリティカルなバグが発見された場合などです。このような緊急時には、問題の原因となっているマージコミットを迅速に
git revert
することで、一時的に問題のある変更を無効にし、安定した状態に戻すことができます。revert操作を新しいMRとして作成し、緊急レビュー・マージすることで、素早く対応できます。 - MRの変更内容の一部を取り消す: MRのソースブランチに複数のコミットが含まれており、そのうち特定のコミットだけが不要になった場合、MRをマージする前にその不要なコミットをソースブランチ上で
git revert
するという選択肢もあります。あるいは、一度マージしてしまった後で、特定のコミットの変更だけを取り消したい場合も、そのコミットに対してgit revert
を実行します。
まとめ
本記事では、GitLabをプラットフォームとするチーム開発環境において、過去の変更を安全に取り消すための強力なコマンドであるgit revert
について、詳細な説明と具体的な手順を解説しました。
Gitにはいくつかの取り消し方法がありますが、履歴を書き換えるgit reset
は主にローカルでの利用に限定されるべきです。一方、git revert
は新しいコミットを作成することで変更を打ち消すため、履歴を保持し、他の開発者に影響を与えにくいという大きなメリットがあります。特に、すでにリモートリポジトリ(GitLab)にプッシュ済みの変更を取り消したい場合や、共有ブランチのマージ後に問題が発生した場合など、チーム開発における安全な取り消し手段としてgit revert
が広く推奨されます。
記事では、以下の主要なシナリオにおけるgit revert
の具体的な手順をステップバイステップで解説しました。
- 直前のコミットをrevertする
- 過去の特定のコミットをrevertする(コンフリクト解決を含む)
- 複数のコミットをまとめてrevertする (
-n
オプションの利用) - マージコミットをrevertする (
-m
オプションの利用と注意点) - revertしたコミットをさらにrevertする(元の状態に戻す)
また、git revert
実行時に発生しうるコンフリクトへの対処法、過去のコミットrevertによる影響、マージコミット再マージ時の問題、そしてGitLabのWeb UIとの連携についても触れました。
git revert
コマンドは、その概念といくつかのオプション、そしてコンフリクト発生時の対処法を理解すれば、開発ワークフローにおいて非常に強力なツールとなります。特にGitLabを利用したチーム開発においては、予期せぬ問題に迅速かつ安全に対応するために、git revert
の知識は必須と言えるでしょう。
Gitの学習は奥深く、様々なコマンドとそれらを組み合わせたワークフローが存在します。本記事が、GitLab環境でのgit revert
コマンドの理解を深め、日々の開発作業の一助となれば幸いです。安全なバージョン管理とスムーズなチーム連携のために、ぜひgit revert
を使いこなしてください。