ブランチを使用する

Git のマージ競合

バージョン管理システムの役割は、分散した複数の作成者 (通常は開発者) の間で作業を管理することです。たとえば複数の開発者が同じコンテンツを編集しようとするような場合、開発者 B が編集しているコードを開発者 A が編集しようとすると、競合が発生する可能性があります。競合をできるだけ少なくするために、開発者たちはそれぞれ隔離されたブランチで作業することになります。git merge コマンドの主な役割は、別々のブランチを結合して競合している編集結果を解決することです。

マージの競合について

Git を操作していると、マージと競合によく遭遇します。競合の解決に SVN などのバージョン管理ツールを使うと、コストがかかるうえに時間もとられます。Git ではマージはとても簡単です。ほとんどの場合、新しい変更を自動的に統合する方法を Git が判定します。

競合は通常、2 人の人間がファイルの同じ行を変更したとき、またはある開発者が編集しているファイルを別の開発者が削除したときに発生します。このような場合、Git では何が正しいのかを自動的に判断することができません。競合の影響を受けるのはマージを実行している開発者だけであるため、チームの他のメンバーが競合に気付くことはありません。Git は競合が発生しているファイルにマーキングを付けてマージ処理を停止します。ここで競合を解決するのは開発者の役割です。

マージ競合の種類

マージは開始時と処理の途中の 2 つの異なるポイントで競合状態になることがあります。以下ではこうしたマージの状況に対応する方法を説明します。

マージの開始時にエラーが発生する

現在のプロジェクトの作業ディレクトリまたはステージング領域のいずれかに変更があることを Git が検知するとマージの開始に失敗します。マージされるコミットによって保留中の変更が上書きされる可能性があるため、マージは開始されません。このような状況になった場合、開発者間の競合ではなく、保留中のローカルの変更に原因があります。git stashgit checkoutgit commit または git reset を使ってローカルの状態を安定化させます。マージの開始に失敗すると次のエラーメッセージが出力されます。

error: Entry '<fileName>' not uptodate. Cannot merge. (Changes in working directory)

マージ中にエラーが発生する

マージ中にエラーが発生したということは、現在のローカルブランチとマージ対象のブランチの間に競合があるということです。つまり別の開発者のコードとの間に競合が発生しています。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"
[master (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 をリポジトリに追加してコミットする。

これで、master というブランチと、コンテンツが入ったファイル 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 master
Switched to branch 'master'
echo "content to append" >> merge.txt
git commit -am"appended content to merge.txt"
[master 24fbe3c] appended content to merge.tx
1 file changed, 1 insertion(+)

この一連のコマンドは master ブランチをチェックアウトし、merge.txt にコンテンツを追加してコミットします。これで、サンプルリポジトリに 2 つの新しいコミットがある状態になりました。master ブランチと 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 master
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 が参照している現在のブランチマスターに存在するコンテンツを表しています。一方、中心と >>>>>>> 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 loggit resetgit statusgit checkoutgit reset の各ページを参照してください。Git 以外にも最新のマージ競合サポート機能を備えたサードパーティ製ツールがあります。

ブランチを試す準備が整いましたか?

この対話式のチュートリアルをお試しください。

今すぐ始める