Git のマージ競合
バージョン管理システムの役割は、分散した複数の作成者 (通常は開発者) の間で作業を管理することです。たとえば複数の開発者が同じコンテンツを編集しようとするような場合、開発者 B が編集しているコードを開発者 A が編集しようとすると、競合が発生する可能性があります。競合をできるだけ少なくするために、開発者たちはそれぞれ隔離されたブランチで作業することになります。git merge
コマンドの主な役割は、別々のブランチを結合して競合している編集結果を解決することです。
マージの競合について
競合は通常、2 人の人間がファイルの同じ行を変更したとき、またはある開発者が編集しているファイルを別の開発者が削除したときに発生します。このような場合、Git では何が正しいのかを自動的に判断することができません。競合の影響を受けるのはマージを実行している開発者だけであるため、チームの他のメンバーが競合に気付くことはありません。Git は競合が発生しているファイルにマーキングを付けてマージ処理を停止します。ここで競合を解決するのは開発者の役割です。
マージ競合の種類
マージは開始時と処理の途中の 2 つの異なるポイントで競合状態になることがあります。以下ではこうしたマージの状況に対応する方法を説明します。
マージの開始時にエラーが発生する
現在のプロジェクトの作業ディレクトリまたはステージング エリアのいずれかに変更があることを Git が検知すると、マージの開始に失敗します。マージされるコミットによって保留中の変更が上書きされる可能性があるため、マージは開始されません。このような状況になった場合、開発者間の競合ではなく、保留中のローカルの変更に原因があります。git stash
、git checkout
、git commit
または git reset
を使ってローカルの状態を安定化させます。マージの開始に失敗すると次のエラー メッセージが出力されます。
error: Entry '<fileName>' not uptodate. Cannot merge. (Changes in working directory)
マージ中にエラーが発生する
マージ中にエラーが発生したということは、現在のローカルブランチとマージ対象のブランチの間に競合があるということです。つまり別の開発者のコードとの間に競合が発生しています。Git ではファイルのマージを試みますが、競合が発生しているファイルでは手動でそれを解決できる余地を残しています。マージ途中で失敗すると次のエラーメッセージが出力されます。
関連資料
高度な Git ログ
ソリューションを見る
Bitbucket Cloud での Git の使用方法についてのチュートリアルです。
error: Entry '<fileName>' would be overwritten by merge. Cannot merge. (Changes in staging area)
マージ競合の作成
マージの競合を現実に近い形で理解するために、次のセクションでは競合を意図的に作成して、それを調べたうえで解決します。サンプルでは Unix 形式のコマンドライン Git インターフェースを使ってサンプルのシミュレーションを実行します。
$ mkdir git-merge-test
$ cd git-merge-test
$ git init .
$ echo "this is some content to mess with" > merge.txt
$ git add merge.txt
$ git commit -am"we are commiting the inital content"
[main (root-commit) d48e74c] we are commiting the inital content
1 file changed, 1 insertion(+)
create mode 100644 merge.txt
このコードサンプルでは以下を達成するコマンドシーケンスを実行しています。
git-merge-test
という名前の新しいディレクトリを作成し、そのディレクトリに変更して新しい Git リポジトリとして初期化する。- 新しいテキスト ファイル
merge.txt
を作成してその中にコンテンツを入れる。 merge.txt
をリポジトリに追加してコミットする。
これで、main
というブランチと、コンテンツが入ったファイル merge.txt
がある新しいリポジトリができました。次は、新しいブランチを作成して競合するマージとして使用します。
$ git checkout -b new_branch_to_merge_later
$ echo "totally different content to merge later" > merge.txt
$ git commit -am"edited the content of merge.txt to cause a conflict"
[new_branch_to_merge_later 6282319] edited the content of merge.txt to cause a conflict
1 file changed, 1 insertion(+), 1 deletion(-)
上記のコマンドシーケンスでは以下を実行します。
new_branch_to_merge_later
という名前の新しいブランチを作成してチェックアウトするmerge.txt
のコンテンツを上書きする- 新しいコンテンツをコミットする
new_branch_to_merge_later
という新しいブランチで merge.txt
のコンテンツを上書きするコミットを作成しました。
git checkout main
Switched to branch 'main'
echo "content to append" >> merge.txt
git commit -am"appended content to merge.txt"
[main 24fbe3c] appended content to merge.tx
1 file changed, 1 insertion(+)
この一連のコマンドは、main
ブランチをチェック アウトして merge.txt
にコンテンツを追加してコミットします。これで、サンプル リポジトリに 2 つの新しいコミットがある状態になりました。main
ブランチと new_branch_to_merge_later
ブランチに、それぞれ新しいコミットが 1 つずつあります。今回は git merge new_branch_to_merge_later
を実行して、何が起きるかを見てみましょう。
$ git merge new_branch_to_merge_later
Auto-merging merge.txt
CONFLICT (content): Merge conflict in merge.txt
Automatic merge failed; fix conflicts and then commit the result.
競合が発生しましたが、Git のおかげでそれがわかりました。
マージの競合を特定する方法
前のサンプルからわかるとおり、競合が発生したことを説明する出力が Git から生成されます。git status コマンドを実行して、さらに詳細を確認できます。
$ git status
On 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: merge.txt
git status
の出力から、競合が原因でマージされていないパスがあることがわかります。merge.text
ファイルのステータスが編集済みになっています。ファイルを調べて何が編集されたのか見てみましょう。
$ cat merge.txt
<<<<<<< HEAD
this is some content to mess with
content to append
=======
totally different content to merge later
>>>>>>> new_branch_to_merge_later
ここでは cat
コマンドを使って merge.txt
ファイルのコンテンツを取り出しました。追加されます。不自然なものが追加されているのがわかります。
<<<<<<< HEAD
=======
>>>>>>> new_branch_to_merge_later
これらの新しい行は「競合の境目」を表しています。=======
の行は競合の「真ん中」を表しています。中心と <<<<<<< HEAD
行の間にあるすべてのコンテンツは、HEAD
ref が参照している現在のブランチ main に存在するコンテンツを表しています。一方、中心と >>>>>>> new_branch_to_merge_later
の間にあるすべてのコンテンツは、マージしているブランチに存在しているコンテンツです。
コマンドラインを使ってマージの競合を解決する方法
マージの競合を解決する最短の方法は、競合が発生しているファイルの編集です。いつも使っているエディターで merge.txt
ファイルを開いてみましょう。今回のサンプルでは競合の原因をすべて削除します。そうすると merge.txt
のコンテンツは次のような状態になります。
this is some content to mess with
content to append
totally different content to merge later
ファイルの編集が完了したら、git add merge.txt
を使って新たにマージされたコンテンツをステージングします。マージを完了するには以下を実行して新しいコミットを作成します。
git commit -m "merged and resolved the conflict in merge.txt"
Git では競合が解決したとみなされ、新しいマージコミットが作成されてマージが完了します。
マージの競合の解決に役立つ Git コマンド
一般的なツール
git status
Git を操作するときとマージ中にステータスコマンドを使うと競合が発生しているファイルを特定できます。
git log --merge
--merge
引数を git log
コマンドに渡すと、マージ対象のブランチ間で競合しているコミットの一覧を含むログが生成されます。
git diff
diff
を使うとリポジトリ/ファイルの状態間の差分を生成できます。これはマージの競合を予測して防止するのに便利です。
Git のマージ開始時にエラーが発生したときに使うツール
git checkout
checkout
を使ってファイルへの変更を元に戻したり、ブランチの変更に使ったりできます。
git reset --mixed
reset
を使って、作業ディレクトリとステージング エリアの変更を元に戻せます。
マージ中に git で競合が発生したときに使うツール
git merge --abort
--abort
オプションを指定して git merge
を実行すると、マージ処理を終了してブランチをマージ開始前の状態に戻します。
git reset
マージの競合が発生しているときに git reset
を使って、競合しているファイルを問題のなかった状態にリセットできます。
要約
マージの競合はときにやっかいな問題になります。ただ、Git には競合を特定して解決するための強力なツールが用意されているので、心配はいりません。Git では独自の自動マージ機能で大半のマージに対応できます。2 つの異なるブランチでファイルの同じ行に編集を加えた場合、または一方のブランチではファイルを削除してもう一方のブランチではそのファイルを編集した場合に競合が発生します。チームで作業に取り組むような環境では競合が発生する確率が非常に高くなります。
マージの競合を解決できるツールはたくさんあります。Git にはここで説明したように多数のコマンド・ライン・ツールがあります。これらのツールの詳細については、git log、git reset、git status、git checkout、git reset の各ページを参照してください。Git 以外にも最新のマージ競合サポート機能を備えたサード・パーティ製ツールがあります。
この記事を共有する
次のトピック
おすすめコンテンツ
次のリソースをブックマークして、DevOps チームのタイプに関する詳細や、アトラシアンの DevOps についての継続的な更新をご覧ください。