本記事は「競合が起こってしまった場合、どうすればよいのか?」について知りたい人向けである。
「競合が起こってしまって、手っ取り早く解決したい」という方はちゃちゃっと解決したいを参照のこと。
また、詳しい説明については結論その2についてを参照のこと。
序
複数人でリポジトリを管理していると、各ブランチで同一ファイルを編集するということは日常的に起こる。
通常、各ブランチでcommit、pushしブランチ内で管理するので問題は起こらないのだが、
編集後に各ブランチでcommitし、その各ブランチの同一ファイルの内容を結合(merge)させるときに問題が生じることがある。
それは各ブランチで同一ファイルの同一箇所を編集したファイルをマージさせる場合である(この同一箇所というのがミソである)。
このとき、マージさせ結合させることが目的なので、git merge ブランチ名
を実行することになるわけだが、
それが実行されない(どのようなエラーが出力されるのかについては後述)。
これはgit側が「どちらのブランチのバージョンを使用すべきかを判断できない」からである。
より正確に言うならば、「git側がどちらのブランチのcommitを優先するかを判断できないから」である。
このように、「複数のブランチで、同一ファイルの同一箇所を編集したとき、どちらのブランチの内容を使用するかgit側が判断できないため、
マージが実行されない状況」を競合と呼ぶ。
結論
結論は2つ。
- 結論その1:そもそも競合が起こらないようにする。
競合が起こらなければ問題が起こらないため、競合を起こさないということが一番である。 - 結論その2:各ブランチの内容を踏襲したファイルを手作業で作る。
gitが判断できないのであれば、人間が判断して手動で結合させるということ。
結論その1について
そもそも競合が起こらないようにするためには、自分が編集しようとしているファイルが他のブランチで編集されているかを確認すれば良い。
具体的には次を実行すれば良い。
git log --graph
これを実行することによってコミットの一覧を出力することができるため、どのファイルが編集され、コミットされたのかを確認することができる。
このgit log --graph
の出力にあるファイルは編集されコミットされているわけであるから、そのファイルを編集することを避けることで
競合が起こらないようにする、というわけである。
ここで、--graph
の部分はオプションであるため、必要不可欠ではない。
しかし、別ブランチ間でマージされたりということも含めコミットのログを視覚的に確認することができるオプションのため、この--graph
オプションをつけることを推奨する。 注意 確かに、git log --graph
はコミットを確認することができるため有用ではあるが、逆に言えば、コミットされていないファイルについては出力されない。 つまり、今まさに編集され、コミットされていないファイルについては出力されないのである。 よってこのgit log --graph
は有用ではあれど確実に競合を回避することができるわけではないことに注意。
結論その2について
競合が起こってしまった場合、手作業で解決するという方法について実験とともに解説する。
設定
mergetest.txt
というファイルの同一箇所をmasterブランチとtestブランチの2つのブランチで編集する。
各ブランチでの編集前のファイルは
i am ironman.
という1行のテキストファイルである。
競合の発生
masterブランチとtestブランチでそれぞれ次のように編集し、コミットする。
- masterブランチの内容(編集後)
i am ironman.
avengers #この行を追加。
- testブランチの内容(編集後)
i am ironman.
#2行目に空行を追加。
may the force be with you. #この行を追加。
※編集後各ブランチでコミットしておく。
このとき、masterブランチの内容をtestブランチに(役割は逆でも良い)マージしようとしてもできない。
今回の例はmasterブランチ
の内容をtestブランチ
に反映させるため、testブランチ
にいる状態で次のコマンドを実行すると、次のように表示される。
$ git merge master
Auto-merging texts/mergetest.txt
CONFLICT (content): Merge conflict in mergetest.txt
Automatic merge failed; fix conflicts and then
commit the result.
つまり、「mergetest.txt
で競合が発生しているのでマージできません」と言われている。
注意と補足
- masterブランチとtestブランチの役割は逆でも良い、というのはどちらのバージョンを優先するかに依存するためである。今回の例はmasterブランチの内容を優先している。
- masterブランチとtestブランチの役割が逆の場合は、
masterブランチ
にいる状態でgit merge test
を実行すれば良い。 git merge master
を実行した後、git status
を実行することでもどのファイルが競合しているかを確認することができる。
その場合競合しているファイルはboth modified: ファイル名
と出力される。
具体的な競合箇所の確認
前小節で競合が発生しているファイルは特定できた。ここでは「具体的にそのファイルのどの部分が競合しているのか」を調べる方法を述べる。
具体的にはemacs mergetest.txt
を実行することで確認できる(補足を参照のこと)。先のコマンドを実行すれば、
i am ironman.
<<<<<<< HEAD
may the force be with you.
=======
avengers
>>>>>>> master
と表示される。<<<<<<< HEAD
と=======
で挟まれている部分は現在のブランチでのmergetest.txt
の内容であり、=======
と>>>>>>> master
で挟まれている部分はmasterブランチ
での内容である。より視覚的には
i am ironman.
<<<<<<< HEAD -----
| → 現在のブランチ(testブランチ)での内容
may the force be with you. |
======= -----
avengers | → masterブランチでの内容
>>>>>>> master -----
である。これを見ることによって、testブランチでは2行目が空行で3行目にmay the force be with you.
と書かれており、
masterブランチでは2行目にavengers
と書かれていることが確認でき、2行目が競合の原因であることがわかる。
補足
- 筆者はEmacsを利用しているため、Emacsで内容を確認しているが、各自利用しているエディタで開いてくれれば良い。単にどこの部分で競合が発生しているか知りたいという場合は
less mergetest.txt
で良いが、結局編集するためEmacsなどのエディタで開くことを推奨する。 <<<<<<< HEAD
と>>>>>>> master
で挟まれている部分はgit merge master
を行うことで勝手に追加してくれるというスグレモノである。
手作業で競合を解決する。
前小節及び前々小節により、どのファイルのどの部分が競合の原因となっているかを確認することができた。
本小節では「手作業により競合を解決する」方法を述べる。
「手作業により」とは言うものの、結局は手動で2つのブランチの内容を結合するということなのである。
具体的には以下のように編集する。
i am ironman.
avengers
may the force be with you.
前小節で「testブランチでは2行目が空行で3行目にmay the force be with you.
と書かれており、masterブランチでは2行にavengers
と書かれていること」を確認したので、2行目にavengers
を追加し、3行目にmay the force be with you.
を追加すれば競合は解決する。すなわち、「望ましい形のファイルを手動で作る」という操作をするわけである。
masterブランチに解決後のファイルの内容を反映させる。
前小節で競合は解決された。本小節では解決された内容をmasterブランチに反映させる方法について述べる。
とはいえ、非常に簡単である。
まず、競合が解決したことをコミットする。すなわち、次を実行する。
git commit -m "solved the conflict"
コミットメッセージは何でも良いが、「競合を解決した」という意味のコミットメッセージにするのが良いだろう。
その後、masterブランチへ移動する。すなわち、次を実行する。
git checkout master
その後、望ましい形のファイルを作ったtestブランチの内容をmasterブランチに統合(マージ)する。
すなわち、次を実行する。
git merge test
すると、マージが実行され、次のように出力される。
$ git merge test
Updating 6717a34..6b83b6c
Fast-forward
mergetest.txt | 1 +
1 file1 changed, 1 insertions(+)
これは、「mergetest.txt
というファイルにおいて1つの差分が見つかったので、その部分を統合した」という意味である。
最後に、本当に統合されているかを確認する。前述の通りemacs mergetest.txt
(今回は確認するだけで編集はしないためless mergetest.txt
で十分)を実行すると、mergetest.txt
の内容は
i am ironman.
avengers
may the force be with you.
となっており、しっかり統合(マージ)された。これで解決である。
ちゃちゃっと解決したい
解説は解説で役立つが、細かいことは抜きにして結局のところ何をすれば解決するのか?ということの方が気になる方もいらっしゃることだろう。
そこで、ここでは具体的に何をすれば解決するかということを細かい説明抜きに記す。
前節を読んでいただいた方はまとめとしてこの節を読んでいただければと思う。
※急いでいるかもしれないが、設定については省略せず読んでいただきたい。どちらのブランチの内容を基礎とするかによってコマンドが変わるためである。 設定
mergetest.txt
というファイルに対してmasterブランチとtestブランチで競合が発生している状態であるとし、
masterブランチの内容をtestブランチにマージしたいとする。すなわち、次の状態であるとする。
$ git merge master
Auto-merging texts/mergetest.txt
CONFLICT (content): Merge conflict in mergetest.txt
Automatic merge failed; fix conflicts and then
commit the result.
emacs mergetest.txt
を実行。
自分が使っているエディタで該当ファイルを開く。<<<<<<< HEAD
と>>>>>>> master
で囲まれる部分の確認。
すなわち該当ファイルに対する2つのブランチの差分および競合箇所を確認する。- 競合箇所を編集する。
適宜追加、削除し、masterブランチの内容を反映させ、testブランチの編集を追記する。このとき<<<<<<< HEAD
やら>>>>>>> master
は削除する。
すなわち、masterブランチの内容を基礎として望ましい形の内容に編集する。手動で統合すると言ってもいい。 git commit -m "solved the conflict"
を実行。
コミットメッセージは何でも良い。git checkout master
を実行。git merge test
を実行。- おしまい。
これで、masterブランチとtestブランチの内容が統一される。
この節を読んでもわかりにくい場合(それは筆者の責任であるけれども)は結論その2についてを読んでいただきたい。
結
今回は「競合が発生した場合どのようにすれば解決するか」について具体例とともに記した。
最初に書いたとおり、
- 競合が起こらないようにする、
- 競合が起こってしまったら手動で直す、
というのが本記事の結論である。確かに競合が起こらないようにするのが一番だが、競合が起こってしまったがために作業が止まるということのほうが場合としては多いだろう。
よって手動で直すということが重要になってくる。「本当に手動でしか直せないのか?」と言われると確かに自動で直してほしいものだが、今の所(第2號)筆者は手動以外での解決策を知らない。もしご存知の方がいらっしゃればぜひご享受願いたい。
今回の例はテキストファイルだったが、筆者はプログラムのコード、特に.py
ファイルでも同様の実験も行い、今回の例と同じ結果が得られることを確認した。 補足および疑問
筆者は最初、競合が発生したとき「競合箇所を思しきところを全て削除してしまえば良いのでは?」と思い、該当箇所を削除した。すなわち、今回の例に則れば、
i am ironman.
<<<<<<< HEAD
may the force be with you.
=======
avengers
>>>>>>> master
を
i am ironman.
としたわけである。その後、git commit -m "delite something"
を実行後、再度git merge master
を行えば、masterブランチの内容がそのままtestブランチに反映されるのではないか?と思い、実行したのだが、
実行後のファイルの内容は変わらず
i am ironman.
だった。そこでgit diff master test
を実行した結果、確かに差分があることは確認できた。
これは、git merge master
も通り、かつgit diff master test
で差分が存在することが確認できてもマージがうまく実行されないということである。
より端的に言えば、「マージされない差分が存在する」というわけである。
これはもしかしたらテキストファイルであることが問題なのか?とも思い、.py
ファイルでも実験したのだが結果は変わらずマージされない差分が存在することになった。
競合が発生しているわけでもなく、マージコマンドはり、差分が存在するのにもかかわらず結合ができないというこの状況がなぜ起こっているのか?については未だ解決していない。
では、逆はどうだろうか?、すなわち、現在のi am ironman
とだけ書かれた状態でgit checkout master
を実行し、masterブランチへ移動後git merge test
を実行したらどうなるだろうか?と思い実行したところ、masterブランチ内でのファイルの内容が
i am ironman.
となった。つまり、うまくマージされたということだ。これはなぜなのかについては未だ解決していない。
解決した場合、それについて新しい記事を作成しようと考えている。
現段階では「コミットには優先順位がある」かもしれないと予想している。
これについては解決した.マージされない差分が存在する【GitHubシリーズ】その7を参照.