Dev Study
← 解説「コンフリクト — なぜ起きるのか、なぜ人間が要るのか」に戻る

サンプルコードで身につける: コンフリクト — なぜ起きるのか、なぜ人間が要るのか

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1本物の競合を最初から最後まで

実際に競合を起こしてから解決するまでの全手順です。競合中はコミットが作られていないこと、解決はファイルを直してaddするだけであることを、実機の出力で追ってください。

$ git merge feature-login
Auto-merging src/index.js
CONFLICT (content): Merge conflict in src/index.js
Automatic merge failed; fix conflicts and then commit the result.

# ファイルには両方の候補が印付きで残っている
$ cat src/index.js
<<<<<<< HEAD
console.log("ログアウト機能");
=======
console.log("ログイン機能");
>>>>>>> feature-login

# この時点ではまだコミットは作られていない
$ git rev-parse --short HEAD
0361ab2                    ← マージ前と同じ

# 正しい内容に直して(印を消して)add すれば解決
$ vim src/index.js
$ git add src/index.js
$ git commit -m "Merge branch 'feature-login'"

23つの版をすべて取り出す

競合中はステージに3つの版が同時に載っています。:1 が共通祖先、:2 が自分側、:3 が相手側です。どこから食い違ったのかを調べるとき、手で読むより確実です。

# 競合中のステージを見ると、同じパスが3行ある
$ git ls-files --unmerged
100644 3f2a1b0... 1	src/index.js   ← 1 = 共通祖先(base)
100644 8c4d2e1... 2	src/index.js   ← 2 = 自分側(ours)
100644 5a9f7c3... 3	src/index.js   ← 3 = 相手側(theirs)

# それぞれの中身を取り出せる
$ git show :1:src/index.js
console.log("Hello");            ← 分岐する前はこうだった

$ git show :2:src/index.js
console.log("ログアウト機能");    ← 自分はこう変えた

$ git show :3:src/index.js
console.log("ログイン機能");      ← 相手はこう変えた

# 元がどうだったか分かると、正しい統合結果を判断しやすい

3競合マーカーの読み方

マーカーは3つの部分に分かれています。どこが自分の変更でどこが相手の変更かを取り違えると事故になるので、構造として覚えてしまうのが確実です。

Python

4競合を途中でやめる / やり直す

解決に自信が無いときは中断できます。merge --abort でマージ前の状態に完全に戻せることを知っておくと、競合が怖くなくなります。新卒が最も安心する知識です。

# 競合したが、いったん元に戻したい
$ git merge --abort

# マージ前の状態に完全に戻る
$ git status
On branch main
nothing to commit, working tree clean

# 特定のファイルだけ「相手側で上書き」したい場合
$ git checkout --theirs src/index.js
$ git add src/index.js

# 逆に「自分側で上書き」したい場合
$ git checkout --ours src/index.js
$ git add src/index.js

# 解決をやり直したい(マーカー付きの状態に戻す)
$ git checkout --merge src/index.js

5競合を減らす習慣

競合は3-way mergeの構造から生じるので完全には避けられませんが、確率は下げられます。分岐が長引くほど競合しやすくなる理由を、変更の重なりとして数えてみます。

Python