変更を取り消すと履歴は消える?|Gitのブランチ・マージ・revert
マージした変更に問題が見つかったら、履歴を削除して元へ戻すのでしょうか。共有済みの変更は、取り消した理由も後から追える形が役立ちます。二人が別の設定を変える例で、内容と履歴を一緒に追ってみます。
事例:タイムアウトとログの設定を、別々に変更する
初期コミットAにはtimeout.txtの100と、logging.txtのINFOがあります。taskブランチのコミットBはtimeout.txtを200へ変更。mainのコミットCはlogging.txtをDEBUGへ変更します。
ここでは別ファイルへの変更です。
mainへtaskをマージし、コミットMを作ります。その後、Bのタイムアウト変更だけを取り消す新しいコミットRを作ります。ログの変更は維持したいので、Aの内容へ丸ごと戻す操作とは分けます。
- 1. taskで変更
- 2. mainで変更
- 3. taskを統合
- 4. mainでマージ
- 5. Bの変更をrevert
矢印は作成順・統合の流れです。MはCとBの二つを親に持ち、RはMを親に持ちます。
ブランチは、ファイルの複製?
Gitのブランチはコミットを指す名前で、コミットを追加すると先へ動きます。コミットはその時点の追跡対象の内容と、親の情報を持ちます。ブランチの名前だけで、変更内容やリリースの実体が決まるわけではありません。
- ブランチ
作業の系列を指す名前です。別の系列で変更し、後で統合できます。
- コミット
記録した変更の区切りです。内容と親、説明などをたどって確認できます。
- タグ
特定のコミットに名前を付けます。リリース版などを示し、通常はブランチのように自動で先へ動かしません。
作業中の未記録の変更は、コミットの履歴と別です。切替前には作業ツリーの状態を確認します。共有される履歴、手元の作業、生成された実行ファイルを区別すると、戻す対象が明確になります。
マージ後は、どの内容になる?
BとCは別ファイルを変更したため、この事例では両方を統合し、timeout=200、logging=DEBUGになります。マージは相手の内容で全部を上書きする操作ではありません。共通の祖先からの変更を統合します。
時点 | timeout.txt | logging.txt | 履歴のポイント |
|---|---|---|---|
A | 100 | INFO | 共通の開始 |
B・task | 200 | INFO | timeoutだけ変更 |
C・main | 100 | DEBUG | loggingだけ変更 |
M・main | 200 | DEBUG | 両方を統合 |
R・main | 100 | DEBUG | Bの変更だけ取消し |
相手へ一直線に進める場合は、ブランチの指す先を動かすfast-forwardになり得ます。この例は両側に変更があり、二つの親を持つマージコミットを作ります。マージできても、仕様の整合は別途テストします。
同じ箇所を変えたら、どちらが勝つ?
別の例として、共通の100をtaskで200へ、mainで300へ変えたとします。同じ行の異なる変更は競合になり得ます。更新時刻が新しい方が自動的に正しいわけではありません。要求を確認して、両方の意図に合う値を決めます。
競合マーカーを除去しただけでは、意味が正しいとは限りません。関連設定、呼出し側、テストの期待値を確認します。同じファイルで競合しなかった場合も、別の箇所の変更が仕様上矛盾することはあります。
構文上の解消だけでなく、要求と実行結果を確認します。
revertで取り消すと、Bは消える?
revertは指定したコミットの変更を打ち消す、新しいコミットを作ります。Mの後でBをrevertすると、この例ではtimeoutだけ100へ戻り、logging=DEBUGを保ちます。BとMの履歴は残ります。
後の変更と同じ箇所へ作用する場合は、revertでも競合することがあります。履歴の記録を保てるからといって、内容が常に自動で戻るわけではありません。取消し後も検証します。
resetはブランチの指す先などを変える操作で、オプションにより作業ツリーにも影響します。共有済みの履歴を巻き戻すと、他の人が持つ履歴と食い違います。作業の目的と共有範囲に応じて選びます。
コマンドで、取り消す対象を確かめる
次の操作は、A・B・Cを事例どおり作成し、taskがBを指した状態を前提にします。実行するリポジトリに未記録の変更がないことを確認します。
git switch main
git merge --no-ff task
# timeout.txt: 200 / logging.txt: DEBUG
git revert --no-edit task
# timeout.txt: 100 / logging.txt: DEBUG
git log --oneline --graph --allこのrevertの対象はBという通常のコミットです。taskが先へ進んでいれば、同じ名前が違うコミットを指すため、取り消すコミットの識別子を確認します。
マージコミットMをrevertする場合は、どの親を基準に変更を取り消すかを選ぶ必要があります。Gitではmainlineの指定を使います。親番号を名前から決めつけず、実際の親と差分を確認します。
公開した版を、何で特定する?
タグv1.0とコミットの識別子を対応させ、ビルドした成果物の識別子も保存します。ソースが同じでも、依存ライブラリやビルド設定が違えば結果が変わるため、再現に必要な条件を記録します。
ソースの取消しを記録しただけでは、本番のプログラムは切り替わりません。新しい成果物の作成・確認・展開を行い、実際に稼働する版と記録を照合します。データ更新の取消しは、さらに別の計画が必要です。
演習1:二つの変更を統合
条件:Bはtimeoutを200、CはloggingをDEBUGへ変更します。
問い:この事例のMの内容を答えてください。
解答例:timeout=200、logging=DEBUGです。
根拠:別ファイルの両変更を共通のAから統合します。
誤答の理由:Bの全内容を上書きすると、Cのログ変更を失うことになります。
演習2:変更を一つ取り消す
条件:Mの後で、taskが指すBをrevertします。
問い:Rの内容と履歴を答えてください。
解答例:timeout=100、logging=DEBUG。BとMを残してRを追加します。
根拠:Bの変更だけを打ち消すコミットです。
誤答の理由:Aへ丸ごと戻すとloggingもINFOとなり、取消し対象以外の変更が消えます。
演習3:マージの親
条件:mainのCへtaskのBを統合し、Mを作ります。
問い:Mが持つ親を答えてください。
解答例:CとBの二つです。この操作では第一の親がCです。
根拠:統合した両方の系列を履歴へ結び付けます。
誤答の理由:最後に変更したBだけを親とすると、main側の系列を表せません。
演習4:同じ行の競合
条件:同じ100を一方で200、他方で300へ変えました。
問い:解消するとき何を根拠にしますか。
解答例:変更の目的と要求を確認し、関連する設定やテストを含めて判断します。
根拠:機械的な変更時刻だけでは正しい仕様を選べません。
誤答の理由:新しい方を常に採用すると、別の要求を満たす変更を失い得ます。
演習5:タグとブランチ
条件:mainが進んだ後もv1.0の版を再現したいとします。
問い:何を記録しますか。
解答例:タグが指すコミット、依存と設定、ビルド成果物の識別子を記録します。
根拠:動くブランチ名だけでは過去の実体を固定できません。
誤答の理由:mainという名前だけでは、どの時点の成果物か特定できません。
演習6:取消しを本番へ届ける
条件:revertのコミットを作りましたが、本番の成果物は旧版です。
問い:本番の動作は既に変わっていますか。
解答例:まだ変わりません。取消しを含む成果物を検証・展開し、稼働版を確認します。
根拠:ソースの履歴と実行中の成果物は別です。
誤答の理由:コミットしただけで本番も自動で変わるという判断は、展開の工程を抜かしています。
出典と仕様を確認する
関連テーマを続けて学ぶ
次におすすめの学習
編集・検証について
編集・検証:IT資格ラボ編集部
IPAが公開する試験要綱・シラバス・過去問題と、各技術の公式資料を優先して内容を確認しています。制度変更や誤りを確認した場合は、記事を見直して更新します。
編集方針・情報源・訂正方針を見る