#【Git】コンフリクト(conflict)が発生しても大丈夫な対処法まとめ 〜自分で発生させて解決する手順〜
gitで作業していると時折発生するコンフリクトを慌てず・冷静に対処するための、コンフリクトの発生要因と解決法のまとめ。
##目次
- コンフリクトとは?
- コンフリクトが発生する主なコマンド
- コンフリクトを発生させる手順
- コンフリクトの内容の確認方法
-
コンフリクトの修正方法
6. mergeをキャンセルする
7. 手動でファイルを修正する
8. 現在のブランチの変更内容を採用する(-Xours)
9. トピックブランチの変更内容を採用する(-Xtheirs)
10. トピックブランチの内容を無視する(-s ours)
11. 空白を削除してマージ
12. 手動でファイルをマージ(応用編)
##コンフリクトとは? conflictは矛盾や対立の意味。ここでは同じファイルで相容れない状況が発生しているということ。
異なるコミットを統合したり・持ってくるときに、2つのコミットで同じファイルの重複する箇所で変更が発生していたときに生じるエラー。
Gitはどちらの変更内容が正しいかを判断できないため、エラーとして打ち上げこちらで判断できるようにしてくれる。
##コンフリクトが発生する主なコマンド コミットの統合や移動で発生する。
git merge
: 統合
git rebase
: 移動
git cherry-pick
:選択的に移動
それぞれのコマンドの詳細は以下。
・git merge
・git rebase
・git cherry-pick
##コンフリクトを発生させる手順
練習でコンフリクトを発生させる。コンフリクトの発生自体は簡単。
#コンフリクト発生用のフォルダを作成
$ mkdir conflict
#作成したフォルダに移動
$ cd conflict
#git初期化
$ git init
#index.htmlを作成(コンフリクトを発生させるファイル)
$ touch index.html
#インデックス
$ git add .
#レポジトリに追加(作業中のブランチがmasterブランチになる)
$ git commit -m "first commit"
#topicブランチを作成し移動する
$ git checkout -b topic
#index.htmlを編集(適当にテキストを追加。:wqで保存終了)
$ vim index.html
#コミット
$ git commit -am "revise index.html at topic"
#masterブランチに移動
$ git checkout master
#index.htmlファイルを編集(適当にテキストを追加。:wqで保存終了)
$ vim index.html
#コミット
$ git commit -am "revise index.html at master"
#マージ ※コンフリクトを発生させる
$ git merge topic
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
無事(?)コンフリクトが発生。
##コンフリクトの内容の確認方法
コンフリクトが発生した時点で、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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
Unmerged paths
マージできなかった内容を表している。
both modified: index.html
index.htmlファイルが両者で変更されている(矛盾が発生している)。
**▼コンフリクトが発生したファイル**
<<<<<<< HEAD
<h1> add h1 </h1>
=======
<h1> h1です。</h1>
>>>>>>> topic
コンフリクトが発生したファイルには、下記3つの記号が挿入されている。
<<<<<<< HEAD
=======
>>>>>>> トピックブランチ名
見方は1と2で挟まれた部分が、現在いるブランチ(HEAD)の内容。2と3で囲まれた部分がmergeで指定したトピックブランチの内容。
2つの変更内容が一つのファイルに統合され、ユーザーが中身を確認できる状態となっている。
<<<<<<< HEAD
{現在のブランチで保存されている内容}
=======
{topicブランチで保存されている内容}
>>>>>>> topic
##コンフリクトの修正方法 コンフリクトの修正方法は複数存在する。
###mergeをキャンセルする
最も単純なのが、先ほど実行したmergeをキャンセルして実行前の状態に戻す方法。
進行中の処理の中断を表**--abort**オプションを使う。
・git merge --abort
--abortはrebaseやcherry-pickなど他のコマンドでも使える。
###手動でファイルを修正する コンフリクトが発生したら、下記手順で解消することができる。
- ファイルを特定(git status)
- 指定のファイルを修正
- インデックス (git add)
- コミット(git commit)
$ 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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
コンフリクトが発生しているファイルがindex.htmlだとわかる。
<<<<<<< HEAD
<h1> add h1 </h1>
=======
<h1> h1です。</h1>
>>>>>>> topic
ファイルを開くと、双方の内容が上下に表示されている。内容を見ながらファイルを修正する。
どちらか一方にするのが一般的。2つを合わせたり、別の記述にすることも可能。
今回はtopicブランチの変更内容を採用する。(不要なテキストを削除)
<h1> h1です。</h1>
変更が完了したら、インデックスしてコミットする。
$ git add index.html
$ git commit -m "resolve conflict"
[master 1264d10] resolve conflict
#ログの状態を確認
$ git log --oneline --graph
* 1264d10 (HEAD -> master) resolve conflict
|\
| * 81f13bc (topic) revise index.html at topic
* | 44ef796 revise index.html at master
|/
* b70ea8b first branch
コンフリクトが解消し、merge成功。
##メインブランチの変更内容を採用する(-Xours) 先ほどはコンフリクト発生部を目視でどちらにするか選び、手動で修正したが、オプションで指定することも可能
git merge -Xours トピックブランチ
このコマンドを実行するには一旦、コンフリクトが発生している処理をキャンセルする必要がある。
- git merge --abort で処理キャンセル
- git merge -Xours topic で再度マージ
##トピックブランチの変更内容を採用する(-Xtheirs) コンフリクトが発生した際に、その箇所はトピックブランチの内容を採用する。
git merge -Xtheirs
- git merge --abort で処理キャンセル
- git merge -Xtheirs topic で再度マージ
##トピックブランチの内容を無視する(-s ours) -Xoursと-Xtheirsはコンフリクトが発生した箇所のみ指定したブランチの内容に修正するオプション。
一方で、「-s ours」をつけると、トピックブランチの内容はすべて無視して、現在のブランチの内容でmergeをする。
git merge -s ours
$ git merge -s ours topic
Merge made by the 'ours' strategy.
▼参考 merge後のブランチとmerge前のブランチの内容を比較すると何も変化していないことがわかる。
$ git diff HEAD HEAD~
$
##空白を削除してマージ コンフリクトしている内容がスペースの数や位置のみの場合、オプションを指定することでマージができる。
違いが空白行の有無や作業者によるTABの使い方のみの場合に使える。
・-Xignore-all-space
┗ 全てのスペースを無視
・-Xignore-space-change
┗ 2つ以上連続したスペースを無視
$ git merge -Xignore-space-change topic
Auto-merging index.html
Merge made by the 'recursive' strategy.
ただし、空白行以外でコンフリクトが発生している場合はエラーになる。
##手動でファイルをマージ(応用編) 応用的なやり方で、コンフリクトしているそれぞれのファイルを一つのファイルに統合する方法がある。
実際に使うことはないと思われるが、git mergeの裏側の処理を理解するために大いに役立つ内容。
###git mergeがやっていること
git mergeは(1)両者の共通の祖先のコミット、(2)現在のブランチのコミット、(3)トピックブランチのコミットの3つのスナップショットから新たなコミットを作成している。
▼merge処理のイメージ
下図で見ると、C、G、E3つのコミットから、新たなコミットHを作るのが、mergeの処理内容。
A---B---C topic
/
D---E---F---G master
$ git merge topic ↓↓↓
A---B---C topic
/ \
D---E---F---G---H master
コンフリクトが発生した時点では、masterブランチにC, G, Eに相当するファイル(スナップショット)がそれぞれ作成されている。
###3つのファイルの参照方法。
(1)両者の共通の祖先のコミット、(2)現在のブランチのコミット、(3)トピックブランチのコミットの3つのスナップショットにはそれぞれ1〜3の番号が割りふられている。
各ファイルは下記で指定できる。
:n:ファイル名
┗ nは1〜3
例えば、index.htmlでコンフリクトが発生した場合に(1)のファイルを参照するには、
git show :1:index.html
で見ることができる。
###マージされていないファイルの一覧を表示する(ls-files -u)
merge後にコンフリクトが発生した時の3つのファイルはls-files -u
コマンドで表示することができる。
各ハッシュ値の後に記述されている1〜3がそれぞれのファイルを表してている。
$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 index.html
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 index.html
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 index.html
###ファイルの統合
ファイルを手動で統合するためには次の手順を踏む必要がある。
- 物理的な3つのファイルに内容を書き込む
- コンフリクトの内容を修正する
- merge-fileコマンドで3つのファイルをマージする
- 不要になったファイルを削除する
####1. 物理的な3つのファイルに内容を書き込む
リダイレクト「>」記号を使って、3つのファイルの内容を新たなファイルを作成し書き込む。
$ git show :1:index.html > index.common.html
$ git show :2:index.html > index.ours.html
$ git show :3:index.html > index.theirs.html
それぞれのファイルの内容が何を表しているかわかるファイル名にしておく。
####2. コンフリクトの内容を修正する 修正したいファイルを開いてコンフリクトの内容を手動で修正する。
####3. merge-file -pコマンドで3つのファイルをマージする merge-file -pコマンドで3つのファイルを、コンフリクトが発生しているファイルに書き込む。
git merge-file -p 共通祖先 現在 トピックブランチのファイル > コンフリクトが発生しているファイル
$ git merge-file -p index.common.html index.ours.html index.theirs.html > index.html
####4. 不要になったファイルを削除する
git clean -f
を使って先ほど作成したファイルを削除する。
git clean
は現在のディレクトリでgitが認識していないファイルを削除するコマンド。
つまり、git addしたことがないファイルをまとめて削除する。
git clean -n
で、gitが認証していないファイルの一覧を表示することができる。
#念のため、gitが認識してないファイルの一覧を表示
$git clean -n
index.common.html
index.ours.html
index.theirs.html
#ファイルを削除
$ git clean -f
Removing index.common.html
Removing ours.html
Removing theirs.html
以上でgit mergeの一連の作業が完了。