git merge(マージ)でconflict(コンフリクト)が発生してしまった時の対処法について実例を用いてわかりやすく解説しています。
git conflictが発生した場合の対処法
git mergeで他のブランチの情報を取り込む際にコンフリクト(conflict)が発生して処理を実行できない場合があります。
例えば次のようなエラーが発生します。
エラーの内容
$ git merge aa
Auto-merging db/schema.rb
CONFLICT (content): Merge conflict in db/schema.rb
Automatic merge failed; fix conflicts and then commit the result.
エラーの内容はやたらと長くて複雑に見えますが、重要な部分は次の部分です。
CONFLICT (content): Merge conflict in db/schema.rb
マージする際に、db/schema.rbというファイルでコンフリクトが発生したという内容です。
Merge conflict in <ファイル名>
のファイル名に注目してください。
コンフリクトの2つの対処法
コンフリクトの対処方法は何をするかによって変わります。
- 自分でコードをいじってコンフリクトを解決する。
- 今回のマージをキャンセルする。
自分でコードを編集してコンフリクトを解決する
最も王道の解決方法は「自分でコードを編集してコンフリクトを解決する」ことです。
実行手順
手順は次の3ステップです。
- コンフリクトが発生しているファイルの内容を修正する。
git add <ファイル名>
で対象のファイルをステージングする。git commit <ファイル名>
で対象のファイルをコミットする。
対象ファイルの修正
まずはコンフリクトが発生しているファイルを開きます。VSCodeを使っている場合はわかりやすくカラーで何が衝突しているかを教えてくれます。
<<<<<<と====と>>>>>>の間に囲まれている部分がポイントです。
<<<<<<<< HEAD (Current Change)
現在取り込もうとしている内容
=====
自分の変更内容
>>>>>>>> <コミットメッセージ> (Incoming Change)
上側のCurrent Changeの部分が取り込もうとしている内容です。自分のローカルとは違ってレポジトリの内容になるためCurrent Change(現在の変更内容)と言われています。
下側が自分のコミットです。新たに入れ込もうとしているのでIncomming Changeとなっています。
Current Changeの方が正しければ、下側や<<<<<<<といった不要な記号を消して、Current Changeの内容にします。Incomming Changeの場合はCurrent Changeを消します。
両方とも必要な場合はどちらも残します。
要は、編集を加えて最終形にすればOKです。
上記の例ではレポジトリの内容(上側)が正しいので、Current Changeを残して他を削除します。
VSCodeの便利オプション
VSCodeではコンフリクトが発生した時に便利なオプションが用意されています。
コンフリクトしている内容の上に表示されているオプションを選択すると、コードを自動編集してくれます。
オプション | 内容 |
---|---|
Accept Current Change | Current Changeの内容を残す(Incomming Changeや余計な記号は消す) |
Accept Incoming Change | Incomming Changeの内容を残す(Current Changeや余計な記号は消す) |
Accept Both Change | 両方の内容を残す(余計な記号は消す) |
Compare Changes | 左右のウィンドウで変更点を比較する |
それぞれ選択すると次のようになります。
コンフリクト中のコード
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 2021_15_08_103381) do
=======
ActiveRecord::Schema.define(version: 2021_15_08_103372) do
>>>>>>> [A]article events
Accept Current Change
ActiveRecord::Schema.define(version: 2021_15_08_103381) do
Accept Incomming Change
ActiveRecord::Schema.define(version: 2021_15_08_103372) do
Accept Incomming Change
ActiveRecord::Schema.define(version: 2021_15_08_103381) do
ActiveRecord::Schema.define(version: 2021_15_08_103372) do
Comapre Changes
右側のウィンドウを閉じると、元のコンフリクト状態の画面に戻ります。
変更後のファイルをステージングする
ファイルの内容が正しく修正できたら、git add <ファイルパス>
を行います。
$ git add db/schema.rb
この状態でgit status
で状態を確認するとgit mergeが進行中であることが表示されます。
On branch test
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
ステージングしたファイルの一覧
変更後のファイルをコミットする
ファイルの内容が正しく修正できたら、git commit <ファイルパス>
を行います。
今回はコミットメッセージを「[F]conflict」とします。([F]はfixの意味で、コンフリクトを修正しましたという内容です)
$ git commit db/schema.rb -m "[F]conflict"
以上でコンフリクトの修正は完了です。
コミット履歴を確認すると、分岐していたブランチがmergeされて一本になっていることがわかります。
$ git log --oneline --graph
* 06d6762 (HEAD -> test) [F]conflict
|\
| * ec9e8b6 (aa) [F]add div
| * 0701d9d (origin/aa) [U]docker-compose add webpack port 3035
リモートレポジトリにプッシュできない場合
コンフリクトを修正した後に、リモートレポジトリにプッシュしようとすると、エラーメッセージが表示されてpushできない場合があります。
▼エラーメッセージの例
$ git push origin test
To github.com:xxx/yyy.git
! [rejected] test -> test (non-fast-forward)
error: failed to push some refs to 'git@github.com:xxx/yyy.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
これは既にリモートレポジトリに過去のファイルをプッシュしてしまった場合に発生します。
※基本的にコンフリクトを注意されるのは、リモートレポジトリにプッシュした後なので、上記のメッセージがでます。
対処法は、現在ローカルで行った修正内容が正しいので -f オプションを使って強制的にプッシュします。
$ git push origin test -f
Enumerating objects: 65, done.
Counting objects: 100% (65/65), done.
Delta compression using up to 12 threads
Compressing objects: 100% (50/50), done.
Writing objects: 100% (50/50), 7.32 KiB | 1.46 MiB/s, done.
Total 50 (delta 41), reused 0 (delta 0)
remote: Resolving deltas: 100% (41/41), completed with 15 local objects.
To github.com:xxx/yyy.git
+ 69751d9b1...6e98e6c92 test -> test (forced update)
以上で完了です。
最後にgit log --oneline
でコミットログを確認すると、自分のローカルとリモートレポジトリが、取り込もうとしていたブランチ(以下では origin/master)より上に来ていることがわかります。
$ git log --oneline
6e98e6c92 (HEAD -> test, origin/test) [F]article_years model and job
8dbb9dcb5 [WIP]article year pv
11ee776ea [A]article events
a170a57c2 (origin/master) Merge pull request #1422 from xxxx/zzz
今回のマージをキャンセルする
コンフリクトした際に表示される2つ目のオプションはgit merge --abort
です。
git merge自体を無かったことにできる便利コマンドです。
コンフリクトの内容や修正方法がわからない場合はとりあえずgit merge --merge
をしておくと、無難にコンフリクトなしの状態に戻せます。
use "git merge --abort" to abort the merge
比較的よく使うオプションです。