Gitで初心者が苦手意識を持つマージ、そのマージの中でやっぱり苦手意識を持たれるものがあります。
それがコンフリクト。
コンフリクトは現役のGitユーザーでもぶっちゃけ嫌なものです。
正しいコンフリクトの知識を持っておきましょう!
コンフリクトとは?
コンフリクトは日本語で競合とか衝突、なんて意味ですがGitの世界では衝突と表現される事が多いです。
例えばお菓子の製造工場を思い浮かべてみましょう。
1つのお菓子を作るために、中身であるお菓子を作る製造ラインとパッケージである袋を作る製造ラインがあったとします。
で、並列作業を進めてそれぞれお菓子と袋が出来ますよね。
最後にお菓子を袋に詰める、これがマージです(並行作業していた物を1つにする)。
しかしお菓子を詰める前に袋を作る製造ラインでは何を勘違いしたのか自分達の製造ラインオリジナルのお菓子を作ってしまいました。
お菓子の製造ラインが「さあ出来たぞ!後はお菓子を詰めるだけだ!」と思ったら、もう既に袋には自分達が知らないお菓子が入っていました。
これがコンフリクトです!
コンフリクトはルール作りが確立していないから起こる
今回のコンフリクトは別々の製造ラインがそれぞれ与えられた作業を認識しきれていなかったから起こった問題です。
マージというのは通常それぞれのブランチの差分を検証して行います。
しかし別々のブランチで同じファイルをそれぞれ編集してしまうと、Gitはどちらを正として採用するべきか分からなくなります。
コンフリクトはルール作りがしっかりしていないから起こる、まずはそれを頭に入れて行きましょう。
実際にコンフリクトを起こしてみる
コンフリクトに対処する為にはコンフリクトがどんな物なのか、しっかり経験して行きましょう!
前回の続き通り終わっていれば今はmasterブランチに居るはずです。
今回はおさらいも兼ねて、新しいtestブランチとtest2ブランチを作成してみましょう。
#testブランチを作成
$ git branch test
$ git branch test2
#testブランチに移動
$ git checkout test
#ブランチの確認
$ git branch
develop
fix
master
* test
test2
では今回はわざとコンフリクトを起こすので、testブランチではindex.htmlに以下の内容を加えます。
#index.htmlの最後にpタグを挿入
$ echo "<p>testブランチで作業したよ</p>" >> index.html
#index.htmlの内容確認
$ cat index.html
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<p>testブランチで作業したよ</p>
無事作業が終わったのでステージしてコミットしておきます。
$ git add .
$ git commit -m "testブランチで作業完了"
[test c38dab4] testブランチで作業完了
1 file changed, 1 insertion(+)
次にtest2ブランチにチェックアウトして、test2ブランチでは違う内容をindex.htmlに挿入します。
#test2ブランチに移動
$ git checkout test2
#ブランチ確認
$ git branch
develop
fix
master
test
* test2
#index.htmlにtestブランチと違う文言を挿入
$ echo "<p>test2ブランチで作業したよ</p>" >> index.html
#index.htmlの内容を確認
$ cat index.html
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<p>test2ブランチで作業したよ</p>
#ステージ
$ git add .
#コミット
$ git commit -m "test2ブランチで作業完了"
[test2 774ca3f] test2ブランチで作業完了
1 file changed, 1 insertion(+)
マージしてみる
さあ、この世に内容が2つに分かれてしまったindex.htmlが出来上がりました。
testブランチにtest2ブランチをマージしてみます。
#testブランチにチェックアウト
$ git checkout test
Switched to branch 'test'
#ブランチ確認
$ git branch
develop
fix
master
* test
test2
#test2ブランチをマージ
$ git merge test2
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
コンフリクトが起こりました。
マージした時のメッセージにCONFLICTとでていますが、その後にどのファイルでコンフリクトが起こっているか表示されています。
今回は表示通りconflict in index.htmlですね。
コンフリクトが起こるとGit側で差分を分かりやすくするためにファイル内容がちょっと変わっています。
index.htmlをみてみましょう。
#index.htmlの内容を確認
$ cat index.html
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<<<<<<< HEAD
<p>testブランチで作業したよ</p>
=======←
<p>test2ブランチで作業したよ</p>
>>>>>>> test2
計3ヶ所に文字が挿入されているのが分かりますね。
< HEADから======が今いるtestブランチ、======から> test2までがtest2ブランチの内容です。
コンフリクトを解消しよう
それではこの起こってしまったコンフリクトを解消させます!
方法はいくつかあるので、とりあえず「そんな方法があるんだ!」と覚えておきましょう。
ちなみに今の状態はちょっと特殊で「マージを一時的に止めている」状況です。
つまりコンフリクトを解消させるためにファイルを編集した後行うのはgit mergeではなくgit commit、マージをコミットするという事になります。
流れでみていきましょう!
ファイルを編集してコミットする
まず1つ目がファイルを編集してコミットする方法。
つまり今いるブランチ上のファイルを直接編集して、差異を埋めます。
$ vi index.html
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<<<<<<< HEAD
<p>testブランチで作業したよ</p>
=======
<p>test2ブランチで作業したよ</p>
>>>>>>> test2
viコマンドでindex.htmlを指定するとファイルの中身が表示されます。
今回はtestブランチの内容を反映させる事にするので、余計な部分を消して行きます。
iキーを押して入力モードにし、余計な部分を削除して下さい。
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<p>testブランチで作業したよ</p>
終わったらescキーを押し、続けて:wqキーで編集終了の為にEnterキーを押します。
#index.htmlの中身を表示
$ cat index.html
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<p>testブランチで作業したよ</p>
無事編集が完了したので、ステージしてコミットします。
$ git add index.html
#マージをコミット
$ git commit -m "コンフリクト解消"
これで無事解決しました!
が、実は今回みたいにどっちを採用するか明確にわかる場合はもっと簡単な方法があります。
一旦マージ前の状態に戻してみましょう。
$ git reset --hard HEAD^
HEAD is now at c38dab4 testブランチで作業完了
これで前回のコミット、つまりtest2ブランチでindex.htmlに文言を挿入してコミットした直後に戻りました。
再びコンフリクトを発生させておきます。
$ git merge test2
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
gitコマンドだけで解決させる
次は手動での編集を行わずに、どちらかを全面的に採用するというコマンド操作のみでマージコミットまで終わらせてみましょう。
使うのはgit checkoutコマンド、これにオプションを使用します。
$ git checkout --ours index.html
Updated 1 path from the index
通常git checkoutはブランチを切り替える時に使用しますが、コンフリクトが起こっている状態でオプションと組み合わせて使うとどちらのブランチの内容を正として採用するかという指定が行なえます。
今回は--ours
と指定したので今いるtestブランチの内容を適用した、という事ですね。
#index.htmlの中身をみてみる
$ cat index.html
<link rel=stylesheet href=css/style.css>
<p>テスト</p>
<p>test</p>
<p>キャンペーン実施中!</p>
<p>testブランチで作業したよ</p>
ご覧のようにコンフリクト発生時にGitが挿入する< HEADや====のような表記も消えています。
もちろんこのままステージしてマージコミットすればOK。
#ステージする
$ git add index.html
#コミットする
$ git commit -m "コンフリクト解消"
[test 40dfc56] コンフリクト解消
この方法だとviでファイルを編集する必要がありません、絶対にこのブランチを正とするという明確な理由があれば使用できますね。
不要なブランチを削除する
今回のtestブランチとtest2ブランチは削除の練習も兼ねて作成して貰いました。
最後にこの両方のブランチを削除してしまいましょう。
まずそもそもマージ時に採用されなかったtest2ブランチから削除します。
#ブランチを削除
$ git branch --delete test2
Deleted branch test2 (was 774ca3f).
#test2ブランチが消えているか確認
$ git branch
develop
fix
master
* test
無事削除できましたね!
ではこの調子でtestブランチも削除しましょう!
その前に、チェックアウト中のブランチは削除できないのでmasterブランチに戻っておきます。
#masterブランチへ移動
$ git checkout master
#ブランチを削除
$ git branch --delete test
error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.
おっと、何やらエラーです。
これを訳すと、「testブランチはマージされてないよ!本当に削除したいならgit branch -D testコマンドを実行してね!」という事。
test2→testへのマージは行いましたが、testブランチがマージされていないのでエラーがでています。
追加しようと思って開発を進めていた機能が何らかの理由で取りやめになる、みたいなイメージですね。
今回は指示に従って-Dオプションでtestブランチを削除してみます。
#ブランチを削除
git branch -D test
#ブランチ確認
$ git branch
develop
fix
* master
無事消えていますね、ブランチの削除はそのブランチでの全ての作業履歴が消えてしまうので注意が必要ですが、このように削除できる事も覚えておきましょう!
コンフリクトは対処が分かれば怖く無い
コンフリクトは本来発生させないような運用が求められますが、発生してしまった時に冷静に対処する事ができるというのは重要な能力です。
是非しっかり練習して、コンフリクトが起こってしまった時でも落ち着いてどうするべきか考えられるようになっておきましょう!
また繰り返しになりますが、基本的にコンフリクトを起こさせないような運用方法をしっかりルール作りする事を普段から意識しておきましょう!
コメント