この記事では開発途中にプルリクエストを間違ってマージしてしまった際やmainブランチに直接Pushした際,変更内容を戻す(revert,reset)方法について記載する.
今回の操作は基本的なgit操作を熟知している人が行うべきである.そのため以下の記事の内容を知っていることは前提とする
そもそも...
親ブランチに直接プッシュ,マージする際はプロジェクトマネージャーに相談し,勝手に親ブランチに変更を加えないようにすること
githubではプルリクエストができてマージができないようにするWriter権限がある,admin権限付与は適切な人のみにすること.
git revert git resetとは
事象確認
このような作業ツリーの構造だった場合,
1.コミットを取り消したい!
2.細かくコミットしすぎたのでまとめたい!
3.featureブランチから直接mainブランチにマージしてしまった!,
4.developmentブランチからmainブランチに間違ったタイミングでマージしてしまった!,
5.mainブランチ/developmentブランチに直接プッシュしてしまった!,
時に,どのように対処するかについて記載する.
revertとresetの違い
使用シナリオ
git revert
公開されたコミットを取り消す際に使用される.コミット履歴を維持しながら,特定のコミットの変更を元に戻すことができる.
git reset
ローカルでのコミットの取り消しや,ブランチを特定のコミットに戻す際に使用される.通常,公開されていないコミットに対して使用する.
動作の違い
git revert
指定したコミットの変更を取り消すための新しいコミットを作成する.元のコミットは履歴に残り,新しいコミットで変更が打ち消される.
git reset
現在のブランチを指定したコミットまで移動させ,それ以降のコミットを取り消す.コミット履歴が変更され,指定したコミット以降のコミットは削除される.
コミット履歴への影響
git revert
新しいコミットを作成することでコミットを取り消すため,コミット履歴が維持される.取り消したコミットと新しいコミットの両方が履歴に残る.
git reset
指定したコミット以降のコミットを完全に削除するため,コミット履歴が変更される.取り消したコミットはなくなる.
作業ツリーとインデックスへの影響
git revert
作業ツリーとインデックスを更新する.新しいコミットによって,作業ツリーとインデックスが変更される.
git reset
作業ツリーとインデックスを指定したコミットの状態に戻す.--hardオプションを使用すると、作業ツリーとインデックスの変更が失われる.
リモートリポジトリへの影響
git revert
作成された新しいコミットは,通常のgit pushでリモートリポジトリにプッシュできる.他の開発者のリポジトリに影響を与えることはない.
git reset
変更したコミット履歴をリモートリポジトリにプッシュするには,通常はgit push --forceが必要.これにより、他の開発者のリポジトリに影響を与える可能性がある.
一般的に,git revertは,コミット履歴を維持し,他の開発者への影響を最小限に抑えるために推奨される.git resetは,ローカルでのコミットの操作に適している.
git reset を使用する場合は,リモートリポジトリへの影響を考慮し,他の開発者との調整が必要.
git revert を使用する場合は,取り消すコミットを正確に指定し,新しいコミットのメッセージを適切に記述することが重要.
git resetの種類
git resetは3種類のオプションがある.
git reset --soft
git reset --mixed
git reset --hard
である.
resetの種類はこの記事でも説明している
https://qiita.com/bearl27/items/288ead3137bef3670d94
HEADとoriginについて
git resetの種類を知る前に,Gitを触っているとよく見るHEADとoriginについて説明する.
HEAD
現在作業しているコンテキスト(コミット)で,通常,チェックアウトしたブランチの最新のコミットを表す.
origin
Git で最も一般的に使用されるリモートリポジトリの参照名であり,リモートリポジトリとの間でコードの同期を行う際に使用される.つまりローカルブランチとリモートブランチの区別をするためにリモートブランチにつける名前のこと.
ワーキングエリアとステージングエリアについて
resetの仕組みはワーキングエリアとステージングエリアがおおいに関係している
ワーキングエリア
プロジェクトの実際のファイルが存在する場所.ファイルの編集、新規作成、削除などの作業はすべてワーキングディレクトリで行われる.
ステージングエリア
次のコミットに含める変更を準備する場所.つまりローカルブランチに変更するファイルを確認する最終ステージ.
git reset --softとは
git reset --soft <コミットのハッシュ>
HEADのみが指定したコミットに移動する,ステージングエリアとワーキングエリアの内容は変更されない.つまり指定したコミットにステージングエリアの内容が集約される.これは、現在の変更を保持したまま、新しいコミットを行う,つまりコミット(ファイルの変更履歴)をまとめる時に便利である.
git reset --mixedとは
git reset --mixed <コミットのハッシュ>
このオプションを使用すると,HEAD とステージングエリアが指定したコミットに移動するが,ワーキングエリアは影響を受けない.つまり,ステージングされた変更は取り消されるが,ワーキングエリア内のファイル自体の編集内容はそのまま残る.コミットを取り消し,git addするファイルを変更する際に便利である.
git reset --hardとは
git reset --hard <コミットのハッシュ>
HEAD,インデックス,そしてワーキングディレクトリがすべて指定したコミットの状態に完全にリセットされる.これにより,現在のブランチ上のすべてのローカル変更(ステージングおよび未ステージングの変更)が失われる.変更履歴をなくし,現在作業しているファイルを特定のコミットの状態に戻したい時に便利である.
実際の事例からどう対処するのか
ここからは実際に起こりうる例に基づいてどのように対処するかの例を記載する.
直前のコミットを取り消したい
リモートブランチのコミットを取り消す場合ではなくローカルブランチの直前のコミットを取り消す
git reset --mixed HEAD^
これでローカルブランチの最新のコミットが解除されるのでコミットし直してプッシュすればリモートに反映される.(mainブランチだった場合)
git push -f origin/main
mainブランチで細かくコミットしすぎたので1つのコミットにまとめたい
これをすることで複数のコミットが一つにまとめられる
mainブランチをチェックアウト
git checkout main
リセットをする.
これはワーキングエリアやステージングエリアの内容を保持したままHEADを戻すことができる.
git reset --soft HEAD~n
HEAD~n
の部分はgit log
によって出てくるコミットハッシュを指定しても良い.指定した場合,HEADから指定したコミットまでコミットをまとめることができる
これでnこ分の変更内容がgit add
された状態になる.
この後commitをする.
git commit -m "Commit message"
これでnこ分の変更内容がgit commit
された状態になった.
一個前のコミットに追加でファイルをコミットしたい際には以下のコマンドを実行する
git add sample.txt
git commit --amend -m "Update Commit message"
git commit --amend -m "Update Commit message"
のみ実行すればコメントのみ変更できる
その後,リモートにフォースプッシュする.
git push -f origin main
この操作はgit rebaseを使うと操作しやすい.以下の記事を参考にすること.
[鋭意製作中]
間違えたコミットを残して変更内容を戻したい
これをすることで変更したコミットを残しつつ取り消すことができる(mainブランチを例にする)
mainブランチをチェックアウト
git checkout main
取り消したいコミットの1つ前のコミットのハッシュを確認
git log
取り消したいコミットのハッシュをメモして,以下のコマンドを実行する.
git revert <取り消したいコミットのハッシュ>
この後コミットメッセージを入力するためのエディタが出てくるので1行目にコメントを入力する.
このエディタを保存して閉じればコミットできる.
エディタを保存して閉じる方法(デフォルトのVimなら Esc
→:wq
と入力)
mainブランチ(ローカルブランチ)の変更をorigin/mainブランチ(リモートブランチ)にプッシュする.
git push -f origin main
ある地点までのすべてのコミットを削除して変更内容を戻したい
これをすると指定したコミットより後のコミットが全て削除される.
リモートブランチにプッシュする際は履歴が変更されるため,他の開発者は自分のローカルリポジトリを更新する必要がある.
(mainブランチを例にする)
mainブランチをチェックアウト
git checkout main
取り消したいコミットの1つ前のコミットのハッシュを確認
git log
取り消したいコミットの1つ前のコミットのハッシュをメモして,以下のコマンドを実行し,メモしたコミットまでmainブランチをリセットする.
git reset --hard <コミットのハッシュ>
mainブランチ(ローカルブランチ)の変更をorigin/mainブランチ(リモートブランチ)にフォースプッシュする.
git push -f origin main
これでコンフリクトを無視してプッシュできる
featureブランチから間違ってmainブランチにマージしてしまった時やdevelopmentから間違ったタイミングでマージした時も同様の処理をする.
developmentブランチの内容をmainブランチに強制的に上書きしたい
これをするとmainブランチの現在の内容が完全に失われるので注意.
developmentブランチをチェックアウト
git checkout development
origin/developmentブランチ(リモートブランチ)から最新の状態をdevelopmentブランチ(ローカルブランチ)に同期する.
git pull origin development
mainブランチをチェックアウト
git checkout main
origin/mainブランチ(リモートブランチ)から最新の状態をmainブランチ(ローカルブランチ)に同期する.
git pull origin main
mainブランチの内容を破棄して,developmentブランチの内容で上書きする.
git reset --hard development
mainブランチ(ローカルブランチ)の変更をorigin/mainブランチ(リモートブランチ)にフォースプッシュする.
git push -f origin main
これで完了