merge とコンフリクト解消 — Git Tips
git merge はブランチを合流させます。fast-forward と merge commit の2通りがあり、同じ行が重なるとコンフリクトになります。意図的に衝突を作り、マーカーの読み方から解消・中断まで進めます。
参考: git-merge · Branching and Merging · Advanced Merging · git-status
目次
- fast-forward と merge commit の違い
- 準備:練習用リポジトリ
- fast-forward merge を体験する
- merge commit(3-way merge)を体験する
- コンフリクトを意図的に起こす
- コンフリクトマーカーの読み方
- コンフリクトの解消手順
- merge を中断する(--abort)
- うまくいかないとき
fast-forward と merge commit の違い
git merge を実行したとき、Git は分岐した位置(共通の祖先コミット)を見て、合流の仕方を自動で選びます。
| 種類 | 条件 | 結果 |
|---|---|---|
| fast-forward | 合流先ブランチが分岐後に一切進んでいない | 新しいコミットを作らず、ポインタを進めるだけ |
| merge commit | 両方のブランチが分岐後にそれぞれ進んでいる | 2 つの親を持つ「合流コミット」が新しく作られる |
fast-forward(例)main: A---B \feature: C---Dmerge 後の main: A---B---C---D(Bと同じ位置から一直線)merge commit(例)main: A---B-------E(合流コミット) \ /feature: C----Dfast-forward は履歴が一直線になり分かりやすい一方、「いつ・どこで合流したか」の情報が残りません。merge commit は履歴が分岐したまま残るため、どのブランチをいつ取り込んだかが後から追いやすくなります。どちらを使うかはチームの運用方針次第ですが、まずは両方を体験して挙動の違いを目で見ることが大切です。
準備:練習用リポジトリ
新しい作業フォルダで練習用リポジトリを作ります。既存のリポジトリで試すと履歴が汚れるので、専用フォルダを用意します。
mkdir merge-practicecd merge-practicegit initgit config user.name "Practice User"git config user.email "practice@example.com"echo "line1" > notes.txtgit add notes.txtgit commit -m "notes.txt を追加"デフォルトブランチ名は Git のバージョンや設定によって main か master になります。以降は main として説明します。ブランチ名を確認しておきます。
git branchfast-forward merge を体験する
feature ブランチを切って、main を一切動かさずに合流させます。
git switch -c feature-ffecho "line2 (feature-ff)" >> notes.txtgit add notes.txtgit commit -m "feature-ff: line2 を追加"git switch maingit merge feature-ffmain は分岐後に進んでいないため、次のように表示されます。
Updating a1b2c3d..e4f5g6hFast-forward notes.txt | 1 + 1 file changed, 1 insertion(+)ログを見ると新しいコミットは作られず、main のポインタが feature-ff の位置まで進んだだけだと分かります。
git log --oneline --graphmerge commit(3-way merge)を体験する
今度は main 側も進めてから合流します。
git switch -c feature-3wayecho "line3 (feature-3way)" >> notes.txtgit add notes.txtgit commit -m "feature-3way: line3 を追加"git switch mainecho "line4 (main)" >> notes.txtgit add notes.txtgit commit -m "main: line4 を追加"git merge feature-3way両方のブランチが分岐後にそれぞれ進んでいるため、テキストエディタが開いてマージコミットメッセージの入力を求められます(保存して閉じればそのまま進みます)。競合する行がなければ自動で合流し、次のようなコミットが作られます。
Merge made by the 'ort' strategy. notes.txt | 1 + 1 file changed, 1 insertion(+)git log --oneline --graphグラフ表示で、2 本の線が 1 つのコミットに合流している様子が確認できます。これが merge commit です。
コンフリクトを意図的に起こす
コンフリクトは 同じファイルの同じ行(またはごく近い行)を、両方のブランチで別々に変更したときに起きます。意図的に発生させてみます。
git switch mainecho "初期状態の1行目" > conflict.txtgit add conflict.txtgit commit -m "conflict.txt を追加"git switch -c branch-aecho "branch-a が書き換えた1行目" > conflict.txtgit add conflict.txtgit commit -m "branch-a: 1行目を変更"git switch mainecho "main が書き換えた1行目" > conflict.txtgit add conflict.txtgit commit -m "main: 1行目を変更"git merge branch-a次のように表示され、コンフリクトが発生します。
Auto-merging conflict.txtCONFLICT (content): Merge conflict in conflict.txtAutomatic merge failed; fix conflicts and then commit the result.コンフリクトマーカーの読み方
git status で状態を確認します。
git statusOn branch mainYou have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge)Unmerged paths: (use "git add <file>..." to mark resolution) both modified: conflict.txtconflict.txt を開くと、次のようなマーカーが挿入されています。
<<<<<<< HEADmain が書き換えた1行目=======branch-a が書き換えた1行目>>>>>>> branch-a| マーカー | 意味 |
|---|---|
<<<<<<< HEAD |
ここから現在のブランチ(合流先、通常は自分側)の内容 |
======= |
区切り線 |
>>>>>>> branch-a |
ここまでが合流しようとしたブランチの内容。ブランチ名も表示される |
HEAD 側が今のブランチ、下側が取り込もうとしている側です。どちらを残すかの判断材料になります。
コンフリクトの解消手順
- エディタでマーカー部分を確認する — マーカーの間にある両方の内容を見て、最終的にどう書くか決める
- マーカーを削除して、望む内容だけ残す
git addでステージし、解消済みだと Git に伝えるgit commitでマージコミットを完成させる
# 解消後の conflict.txt(例:両方の意図を反映する場合)main と branch-a の変更を統合した1行目git add conflict.txtgit statusgit add した直後の git status は次のようになります。
All conflicts fixed but you are still merging. (use "git commit" to conclude merge)Changes to be committed: modified: conflict.txtコミットメッセージを省略すると、Git があらかじめ用意した「Merge branch 'branch-a'」というメッセージがエディタに開きます。そのまま保存して閉じれば完了です。
git commit複数ファイルでコンフリクトが起きた場合も手順は同じで、すべてのコンフリクトを解消して add し終えてから commit します。途中のファイルがまだマーカー入りの状態で commit しようとすると、Git が警告して止まります。
merge を中断する(--abort)
解消中に「やっぱりこの merge はやめたい」と思ったら、git merge --abort で merge 開始前の状態に戻せます。
git merge --abortgit statusOn branch mainnothing to commit, working tree cleanコンフリクトマーカーも消え、conflict.txt は merge 前の内容に戻ります。add や commit をまだしていない段階なら、いつでも --abort でやり直せるので、判断に迷ったら安心して使ってください。
うまくいかないとき
コンフリクトマーカーをそのままコミットしてしまった
main が書き換えた1行目=======branch-a が書き換えた1行目>>>>>>> branch-aこういった記号がファイルに残ったままコミットされてしまうことがあります。落ち着いてファイルを開き、マーカーと不要な行を削除して git add → git commit --amend(直前のコミットに追記する場合)でやり直します。amend の詳細は Git の他の学習リソースで確認してください。
git merge が何も言わずに終わる
Already up to date.これはエラーではなく、合流先に取り込む差分がすでに反映済み というだけです。ブランチを間違えていないか git branch と git log --oneline --graph で確認します。
merge 中に別の作業を始めたくなった
コンフリクト解消中は他の git switch などのブランチ操作ができません。まず git merge --abort で中断してから、別ブランチに移動するか、コンフリクトを最後まで解消してコミットを完了させてください。
コンフリクトの範囲が広すぎて判断できない
一度 --abort し、コンフリクトを起こしたコミットをより小さい単位に分けてから合流し直すと、1 回あたりの衝突範囲が小さくなり解消しやすくなります。バイナリファイルや自動生成ファイルでの衝突は、そもそも合流対象から外す(.gitignore に入れる)ことも検討します。
git status の表示と実際のファイルが合わない気がする
git status はステージ済みかどうかで表示が変わります。git add の前後で見比べると、「Unmerged paths」から「Changes to be committed」に変わることが確認できます。表示が更新されない場合は、エディタ側の保存が反映されているか確認してください。