コンフリクトを解決する
このページで分かること
- コンフリクトがどんなときに起きるかが分かる
<<<<<<<・=======・>>>>>>>の印を読んで、どちらの変更かを見分けられる- ファイルを直して
git add→git commitでマージを仕上げられる git merge --abortでマージ前に戻せ、zdiff3で元の内容も表示できる
このページの目次12 items
23で、両方のブランチが同じ行を変えているとマージが止まると書きました。これがコンフリクト(conflict、衝突)です。このページでは、わざとコンフリクトを起こし、印の読み方と、止まったマージを自分の手で仕上げる手順を練習します。
コンフリクトはなぜ起きるか#
23の 3-way マージでは、マージベースから見て「片方だけが変えた部分」はその変更を採用しました。では、同じ部分を両方が別々に変えていたらどうでしょうか。どちらを採るべきかは、内容の意味が分かる人にしか決められません。そこで Git は自動で決めずに、マージを途中で止めて人に任せます。
コンフリクトは失敗やエラーではありません。「ここだけは判断してください」という Git からの問い合わせです。
練習用のリポジトリで起こしてみます。ブランチでは豆乳をオーツミルクに、main では豆乳を2本に変えます。どちらも買い物メモの1行目です。
cd ~/git-practice
git switch -c oat-milk
sed -i.bak 's/豆乳を買う/オーツミルクを買う/' shopping.txt && rm shopping.txt.bak
git commit -am "豆乳をオーツミルクに変更"
git switch main
sed -i.bak 's/豆乳を買う/豆乳を2本買う/' shopping.txt && rm shopping.txt.bak
git commit -am "豆乳を2本に変更"マージを止めて、状況を読む#
git merge oat-milkAuto-merging shopping.txt
CONFLICT (content): Merge conflict in shopping.txt
Automatic merge failed; fix conflicts and then commit the result.CONFLICT と出て、マージが止まりました。こういうときは、まず git status です。
git statusOn branch main
You 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: shopping.txt
no changes added to commit (use "git add" and/or "git commit -a")読むべきところは3つです。
| 表示 | 意味 |
|---|---|
You have unmerged paths. | マージの途中で、まだ解決していないファイルがある |
both modified: shopping.txt | 両方が変えて衝突したファイル。ここを直す |
fix conflicts and run "git commit" / git merge --abort | 次にすること:直してコミットするか、やめるか |
印の読み方#
衝突したファイルの中を見ます。
cat shopping.txt<<<<<<< HEAD
豆乳を2本買う
=======
オーツミルクを買う
>>>>>>> oat-milk
卵を買う
パンを買う
りんごを買うGit が、衝突した部分に印(コンフリクトマーカー)を書き込みました。
<<<<<<< HEADから=======まで:今いるブランチ(main)の内容=======から>>>>>>> oat-milkまで:取り込もうとしているブランチ(oat-milk)の内容
衝突しなかった「卵」以下の行は、そのまま残っています。
いったんやめる:git merge --abort#
どう直すか迷ったら、マージそのものを取り消して、始める前の状態に戻れます。
git merge --abort
git status -s
cat shopping.txt豆乳を2本買う
卵を買う
パンを買う
りんごを買うgit status -s は何も表示せず、ファイルもマージ前の main の内容に戻りました。コンフリクトが起きても、--abort で何もなかったことにできると知っておくと、落ち着いて向き合えます。
元の内容も表示する:zdiff3#
さきほどの印では、「2本」と「オーツミルク」のどちらが元の内容から変わったのかが分かりません。設定を1つ変えると、マージベースの内容も一緒に表示されます。
git config --global merge.conflictStyle zdiff3
git merge oat-milk
cat shopping.txt<<<<<<< HEAD
豆乳を2本買う
||||||| ca9807e
豆乳を買う
=======
オーツミルクを買う
>>>>>>> oat-milk
卵を買う
パンを買う
りんごを買う||||||| から ======= までが、マージベース(ca9807e)の内容です。元は「豆乳を買う」で、main は本数を、oat-milk は種類を変えたのだと読み取れます。両方の意図が分かるので、両方を生かした直し方を選べます。 このガイドでは、この設定をおすすめします。
直して、仕上げる#
1. ファイルを書き直す#
エディタで shopping.txt を開き、印の行をすべて消して、残したい内容に書き直します。今回は両方の意図を生かして「オーツミルクを2本買う」にします。
オーツミルクを2本買う
卵を買う
パンを買う
りんごを買う片方だけを採る、両方の行を残す、まったく別の内容にする、のどれでも構いません。書き直した結果が、そのままマージの結果になります。
2. git add で「解決した」と伝える#
git add shopping.txt
git statusOn branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: shopping.txtコンフリクトの途中での git add は、「このファイルは解決した」という合図を兼ねています。All conflicts fixed(すべて解決済み)になりました。
3. git commit でマージを完了する#
git commit --no-edit[main 52db35e] Merge branch 'oat-milk'--no-edit は、Git が用意した Merge branch 'oat-milk' というメッセージをそのまま使うオプションです。付けなければエディタが開くので、どう解決したかを書き足すこともできます。
git log --oneline --decorate --graph -n 4* 52db35e (HEAD -> main) Merge branch 'oat-milk'
|\
| * 2eff5e0 (oat-milk) 豆乳をオーツミルクに変更
* | 085ffbb 豆乳を2本に変更
|/
* ca9807e Merge branch 'shopping-apple'
|\ 23と同じ形のマージコミットができました。取り込み済みの oat-milk は片付けておきます。
git branch -d oat-milkよくある疑問#
Q. コンフリクトを減らす方法はありますか?
A. ブランチを長く放置しないことが一番効きます。分かれている期間が長いほど、同じ場所を触る可能性が高くなるからです。作業を小さく分けて早めに取り込む、こまめに main の変更を取り込む(25の rebase も使えます)、の2つを心がけてください。
Q. VS Code などのエディタでも解決できますか?
A. できます。VS Code は印のある部分に「Accept Current Change」(今いるブランチ側を採る)、「Accept Incoming Change」(取り込む側を採る)などのボタンを表示します。押すと印も一緒に消えます。そのあとの git add と git commit は同じです。
まとめ#
- 両方のブランチが同じ部分を別々に変えると、コンフリクトでマージが止まる。エラーではなく、判断の依頼
git statusのboth modifiedが直すべきファイル<<<<<<< HEAD〜=======が今いるブランチ、=======〜>>>>>>>が取り込む側。zdiff3にすると元の内容も見える- 印を消して書き直し、
git add→git commitで仕上げる - 迷ったら
git merge --abortでマージ前に戻れる
次のページ#
25. rebase を理解する ── マージとは別の取り込み方である rebase で、枝分かれした履歴を1本にまっすぐ並べ直します。