🗿

VSCodeによるGitコンフリクト解消

に公開

はじめに

Gitを使って開発を進めていると、避けて通れないのが コンフリクト(競合) です。
チーム開発ではもちろん、個人開発でもブランチを分けて作業する際に発生することがあります。

コンフリクトとはなにか

Gitでは、複数人(あるいは複数のブランチ)で同じリポジトリを編集することができます。
しかし、同じファイルの同じ箇所を異なる内容に変更してしまったとき、Gitは 「どちらを正しい最終形にするのか」 を判断できなくなります。
この「Gitが自動で解決できない状態」を コンフリクト(競合) と呼びます。

図解イメージ


コンフリクトが発生するケース

Gitのマージ時に、同じファイルの同じ部分を異なるブランチで修正していると、コンフリクトが発生します。

よくある例

  1. 同じ行を別の内容に変更したとき
    ブランチA → title = "Hello";
    ブランチB → title = "Hi";
    → マージ時にどちらを採用するかGitが決められずコンフリクト。

  2. 同じ行を片方が削除し、もう片方が変更したとき
    ブランチA → 行を削除
    ブランチB → 行を編集
    → 「削除する?変更を残す?」が判断できずコンフリクト。

  3. 1ファイルの中で複数箇所を修正(両方のブランチで異なる修正)
    ブランチA → 2行目を「A版」に、5行目を「A版」に修正
    ブランチB → 2行目を「B版」に、5行目を「B版」に修正
    → 1ファイルに複数のコンフリクトブロックが発生する。

  4. ファイルの追加と削除が競合するとき (CONFLICT modify/delete, add/delete)
    ブランチA → dir/config.json を追加
    ブランチB → ディレクトリdir を削除
    → Gitは「ファイルを残すのかディレクトリごと削除するのか?」で判断できずコンフリクト。

  5. ファイル名変更(リネーム)が競合するとき (CONFLICT rename/rename)
    ブランチA → user.js を account.js にリネーム
    ブランチB → user.js を profile.js にリネーム
    → 「最終的にどの名前にするのか?」が不明でコンフリクト。
    順番にpushするとコンフリクトにならない場合もあり。

マージ以外の操作でもコンフリクトは発生する

ここまで紹介したのは「ブランチをマージするとき」に典型的に起きるパターンです。
しかし実際には、マージ以外の操作(rebase や cherry-pick)でも同様にコンフリクトが発生することがあります。
原因は基本的に同じで、同じ箇所を複数のコミットが異なる形で触っているためにGitが自動で決められない、という点は変わりません。

よくある例

  1. リベース時の競合
    git rebase で履歴を付け替えるとき、過去のコミットが「別のブランチの修正」と同じ場所を触っている
    → rebase中にコンフリクトが発生。

  2. cherry-pick時の競合
    あるコミットだけを別ブランチに取り込むときに、その行が既に別の内容に変わっていた
    → cherry-pick実行時にコンフリクトが発生。

バイナリファイル(画像やWordなど)でも発生する

通常のテキストファイルは行単位で比較・統合が可能ですが、画像ファイルやWordファイルなどのバイナリファイルは行という概念がないため、Gitは内容をマージできません。
そのため両方のブランチで同じファイルを変更すると、以下のようにコンフリクトが発生します。

CONFLICT (content): Merge conflict in image.png

この場合はテキストの競合のように中身をマージすることはできず、どちらか一方のファイルを採用する/新しいファイルで置き換える といった対応が必要になります。


コンフリクト解消の流れ(マージ時のコンフリクト解消)

ここではマージ時のコンフリクトに関して解消方法を記載します。

1. コンフリクト発生を確認

  • git merge あるいは git pull 実行後にエラーメッセージが出ます。
  • GitLabであればマージ実行時に下記のような表示になります。
    • 両方のブランチでファイル内容の変更時

    • 「両方のブランチでファイル内容の変更時」以外(片方削除や片方追加などのパターン)

      • 「競合を解決」の表示が出ません。

2. マージファイルをWebIDEもしくはVSCodeで開く

  • 両方のブランチでファイル内容の変更時

    • WebIDEでの開き方

      • 簡単な競合であればGitLab上で「競合を解決」を押下することで、WebIDEが起動し、手動で修正することができます。
      • 「自分の変更を使用」「相手の変更を使用」のどちらかを押下し、その後にMRを再読み込みするとマージが可能になります。
    • VSCodeでの開き方

      • VSCodeをマージツールに設定する必要があります。

      • 設定方法は下記Zenn記事を確認してください。 https://zenn.dev/owayo/articles/70814e23ab3cfb

      • 下記コマンドをコンソール部に入力してください。

    git checkout <コンフリクトが発生しているブランチ>
    git fetch origin 
    git merge origin/main 
    
    • VSCodeでは下記の画像のようにコンフリクト部分が表示されます。
      • 両方のブランチでファイル内容の変更時
        • 複数ファイルが同時にコンフリクトした場合でも1ファイルごとの修正が必要です。

        • 両方のブランチでファイル内容の変更時(複数箇所のコンフリクト)

        • 「両方のブランチでファイル内容の変更時」以外

3. どの内容を採用するか判断

  • 両方のブランチでファイル内容の変更時
    修正時には下記の修正方針があります。
    どの修正方針を採用するにしても、「結果」欄に修正内容を記載して「マージの完了」ボタンを押下することでコミットが可能です。

    • 片方の修正を採用

    • 両方を組み合わせて修正を行う

    • まったく新しい修正を書く

    • 同ファイル内で数箇所コンフリクトが発生してる場合、ブロックごとに修正方針を決定してください。

    • 受信中を適用する、組合せを受け入れるなど、コンフリクト箇所の上部にあるボタンを押下することで「結果」欄に反映されます。

    • もちろん手動で修正も可能です。

    • コンフリクト箇所は画面上部の上下矢印で移動ができます。長めのファイルでコンフリクトが数箇所発生した場合に次のコンフリクト箇所への移動が簡単にできます。

  • 両方のブランチでファイル内容の変更時以外

    • この場合マージエディタは起動しません。
    • 削除か変更後のファイルを残すのかを決めた上で修正を実施し、コミットし直してください。
    • マージエディタでの解消ではなく、現在コンフリクトしている他ブランチの修正状況を確認して、手動で修正を行い、再pushをすることでコンフリクト解消ができます。

    * 下記コマンドを入力して現状の状態を把握して作業を行ってください。

     git status

4. 修正後に解消をGitに登録する

  • 画面左部の「変更の同期」を押下することで、Gitにpushすることができます。

  • なお、コマンドでは下記で実行できます。

git add <ファイル名>
git commit -m <コミットメッセージ>
git push

効率よく解決するコツ

  • 小まめにマージする
    • ブランチの差分を小さく保つことで、コンフリクトが複雑化しにくくなります。
  • チーム内で修正範囲を共有
    • 同じファイルの同じ箇所を複数人が触らないように意識する。
  • レビュー時に潜在的な競合を意識
    • MRの段階で早めに気付くことができる。

まとめ

  • コンフリクトは「同じ箇所を別ブランチで修正」すると発生します。
  • 基本は 該当部分を手動で修正→ VSCodeでの操作 または → git addgit commitgit push

Gitを使った開発では避けられない問題ですが、VSCodeなどのマージツールを活用すればストレスを最小限に抑えられます。自分の開発環境に沿ったツールを見つけてみてください!

おまけ 便利な補助コマンド

  1. どうしても早く解決したい場合は、比較せずに片方の変更をそのまま採用する --ours / --theirs という方法もあります。
    時短になる反面、失われる変更があるかもしれないので、実務では「とりあえず通す」用途か、「どちらか一方の変更で良いと明確に分かっている」場合に限られます。
  • 片側まるごと採用(テキスト/バイナリ共通)
  • ours(現在ブランチ)
git checkout --ours <file> && git add <file> && git commit && git push
  • theirs(マージ相手)
git checkout --theirs <file> && git add <file> && git commit && git push
  1. 競合中のファイルを一覧表示をすることができます。
    大量のファイルを触っていると「どのファイルが競合しているのか」を探すのが大変ですが、このコマンドを使うと未解決のファイルが一目でわかります。
git diff --name-status --diff-filter=U
  1. 競合解消をやめて元の状態に戻したいときに作業をリセットすることができます。
    「解消がうまくいかない」「間違えてマージを始めてしまった」などの場合に、これを実行すれば競合発生前の状態に戻せます。
    ただし競合解消途中の作業内容は破棄されるので注意が必要です。
git merge --abort
git rebase --abort
git cherry-pick --abort

Discussion