Git merge conflictの解決方法を初心者向けに徹底解説

Git

Git merge conflictの解決方法を初心者向けに徹底解説

Gitを使った開発をしていると、避けて通れない問題の一つが「merge conflict(マージコンフリクト)」です。複数の開発者が同じファイルを編集したり、異なるブランチで同じ部分を変更したりすると、Gitはどちらの変更を優先すべきか判断できず、コンフリクトが発生します。

本記事では、merge conflictの原因から実際の解決手順、よくある間違いまで、初心者でも理解できるように詳しく解説します。

Git merge conflictとは?原因の説明

merge conflictは、Gitで2つ以上のブランチをマージしようとする際に、同じファイルの同じ箇所が異なる内容に変更されている場合に発生します。

conflictが発生する主なケース

  • 複数の開発者が同じファイルを編集した場合:AさんがファイルのX行目を「aaa」に変更し、Bさんが同じX行目を「bbb」に変更すると、どちらを採用するか決められません。
  • 異なるブランチで同じ部分を修正した場合:featureブランチとmainブランチで同じバグ修正が異なる方法で実装されている場合です。
  • 削除と編集の競合:一方のブランチでファイルが削除され、もう一方で編集されている場合。
  • ファイル名の変更と編集:ファイル名を変更したブランチと内容を編集したブランチをマージする場合。

conflictはなぜ発生するのか?

Gitは「分散型バージョン管理システム」であり、各開発者が独立して作業できるのが特徴です。この利点がある一方で、複数の変更が衝突する可能性も生じます。Gitはマージ時に自動的に変更を統合しようとしますが、同じ箇所に異なる変更があると、どちらを優先するか判断できないため、conflictが発生するのです。

merge conflictの解決手順

ステップ1:conflictの発生を確認する

マージコマンドを実行すると、conflictが発生した場合は以下のようなメッセージが表示されます。

$ git merge feature-branch
Auto-merging sample.txt
CONFLICT (content): Merge conflict in sample.txt
Automatic merge failed; fix conflicts and then commit the result.

このメッセージが出たら、conflictが発生しています。

ステップ2:conflictが発生したファイルを確認する

以下のコマンドで、conflictが発生したファイルを確認できます。

$ 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:   sample.txt

ステップ3:conflictの詳細を確認する

conflictが発生したファイルをテキストエディタで開いて、詳細を確認します。

$ cat sample.txt
これは通常のテキストです
<<<<<<< HEAD
こちらはmainブランチの内容です
=======
こちらはfeature-branchの内容です
>>>>>>> feature-branch
これも通常のテキストです

conflictが発生した場合、ファイルには以下の形式で両方の内容が記載されます:

  • <<<<<<< HEAD:現在のブランチ(mainなど)の内容
  • =======:区切り線
  • >>>>>>> feature-branch:マージするブランチの内容

ステップ4:conflictを手動で解決する

conflictの詳細を確認したら、どちらの変更を採用するか、または両方を組み合わせるかを決定して、ファイルを編集します。

$ nano sample.txt  # または、お好みのエディタを使用

例えば、feature-branchの内容を採用する場合は、以下のように編集します:

これは通常のテキストです
こちらはfeature-branchの内容です
これも通常のテキストです

または、両方を組み合わせたい場合:

これは通常のテキストです
こちらはmainブランチの内容です
こちらはfeature-branchの内容です
これも通常のテキストです

ステップ5:解決したファイルをステージングする

conflictを解決したら、ファイルをステージングします。

$ git add sample.txt

ステップ6:マージコミットを作成する

最後に、マージコミットを作成します。

$ git commit -m "Merge feature-branch into main"

これで、conflictの解決が完了します。

実践的なコード例

例1:シンプルなテキスト競合の解決

状況:同じファイルの異なる行が変更されている場合

$ git log --oneline -n 5
* a1b2c3d (feature-branch) Add feature description
* e4f5g6h Update version number
| * i7j8k9l (HEAD -> main) Update author name
| * m0n1o2p Fix typo in README
|/
* q3r4s5t Initial commit

$ git merge feature-branch
Auto-merging config.txt
CONFLICT (content): Merge conflict in config.txt
Automatic merge failed; fix conflicts and then commit the result.

conflictが発生したconfig.txtを確認:

project_name = "MyApp"
version = "1.0.0"
<<<<<<< HEAD
author = "Alice"
=======
author = "Bob"
>>>>>>> feature-branch
description = "A sample application"

Aliceを採用する場合の解決:

project_name = "MyApp"
version = "1.0.0"
author = "Alice"
description = "A sample application"

解決後のコマンド:

$ git add config.txt
$ git commit -m "Resolve merge conflict: keep author as Alice"
$ git log --oneline -n 3
* u8v9w0x (HEAD -> main) Resolve merge conflict: keep author as Alice
*   c1d2e3f Merge made by the 'recursive' strategy
|\

例2:複数ファイルのconflict解決

状況:複数のファイルでconflictが発生している場合

$ git merge develop
Auto-merging src/main.py
CONFLICT (content): Merge conflict in src/main.py
Auto-merging src/utils.py
CONFLICT (content): Merge conflict in src/utils.py
Auto-merging README.md
Automatic merge failed; fix conflicts and then commit the result.

$ git status
Unmerged paths:
  both modified:   src/main.py
  both modified:   src/utils.py

各ファイルを順番に解決:

$ # src/main.pyを編集
$ nano src/main.py
$ git add src/main.py

$ # src/utils.pyを編集
$ nano src/utils.py
$ git add src/utils.py

$ # 全てのconflictが解決されたか確認
$ git status
On branch main
All conflicts fixed but you are still merging.

$ # マージコミットを作成
$ git commit -m "Merge develop branch"

例3:conflictの自動ツールを使用した解決

Gitには、conflictを半自動的に解決するツール(mergetool)があります:

$ git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
meld opendiff kdiff3 tkdiff xxdiff meld.exe gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare

$ # Visual Studio Codeを使用する場合
$ git config --global merge.tool vscode
$ git config --global mergetool.vscode.cmd 'code --wait $MERGED'
$ git mergetool

よくある間違いと対処法

間違い1:conflictを不完全に解決したまま、git commitしてしまう

よくあるケース:マーカー(<<<<<<<や>>>>>>>)を削除し忘れたまま、commitしてしまう。

$ cat problematic.txt
こちらはmainブランチの内容です
<<<<<<< HEAD  # マーカーが残っている!
こちらはfeature-branchの内容です
>>>>>>> feature-branch  # マーカーが残っている!

対処法:commitする前に、必ずファイルを確認し、conflictマーカーが全て削除されていることを確認します。

$ # conflictマーカーを検索
$ git grep -n '<<<<<<<\|=======\|>>>>>>>'

$ # 結果が0件なら、マーカーは全て削除されている

間違い2:どちらの変更も必要なのに、片方だけを採用してしまう

よくあるケース:conflictを急いで解決しようとして、どちらか一方の内容だけを保持し、もう一方の重要な変更を失ってしまう。

$ cat database.py
<<<<<<< HEAD
def connect():
    return mysql.connect(host='localhost')
=======
def connect():
    return mysql.connect(host='localhost', timeout=30)
>>>>>>> feature-branch

タイムアウト設定は重要なので、両方を組み合わせるべき場合:

$ cat database.py
def connect():
    return mysql.connect(host='localhost', timeout=30)  # 両方の変更を組み合わせた

間違い3:git merge –abortを知らずに、間違ったconflict解決をしてしまう

よくあるケース:conflictの解決に失敗して、ディレクトリが混乱した状態になる。

対処法:マージを中止して、最初からやり直すことができます。

$ # マージを完全に中止する
$ git merge --abort

$ # ブランチの状態が元に戻ります
$ git status
On branch main
nothing to commit, working tree clean

間誤り4:同じconflictを何度も解決してしまう

よくあるケース:長期間並行開発していたブランチをマージする際、同じ部分でconflictが何度も発生する。

対処法:rebase戦略を使用することで、conflictを一度だけ解決できます。

$ # マージ戦略を変更(rebase)
$ git rebase main feature-branch

$ # または、最初からrebaseでマージする
$ git merge --no-ff feature-branch

間違い5:conflictの解決後、テストを実施せずにpushしてしまう

よくあるケース:conflictを解決したら、すぐにリモートリポジトリにpushして、後で本番環境でバグが発見される。

対処法:必ず、conflict解決後にテストを実施してから、pushします。

$ git add .
$ git commit -m "Resolve merge conflicts"

$ # テストを実施
$ npm test  # または、python -m pytest など

$ # テストが成功したら、pushする
$ git push origin main

conflictを防ぐためのベストプラクティス

1. 頻繁にpullする

他の開発者の変更を頻繁に取り込むことで、conflictを小さく保つことができます。

$ git pull origin main  # 毎日、または複数回実施

2. 短命なブランチを使用する

ブランチの生存期間が長いほど、conflictのリスクが高まります。小さな機能単位でブランチを分割し、すぐにマージすることをお勧めします。

3. コードレビュープロセスを導入する

Pull Requestを使用して、マージ前にコードレビューを実施することで、conflictを早期に発見できます。

4. ファイルの役割分担を明確にする

複数の開発者が同じファイルを編集しないようにすることで、conflictの発生を回避できます。

git mergeコマンドのオプション

conflictの解決に役立つ、様々なオプションが存在します。

$ # conflictが発生した場合、マージを中止する
$ git merge --abort

$ # マージ戦略を指定する
$ git merge -s recursive feature-branch  # デフォルト
$ git merge -s resolve feature-branch    # 古い戦略
$ git merge -s ours feature-branch       # 現在のブランチを優先
$ git merge -s theirs feature-branch     # マージするブランチを優先

$ # conflict解決時に特定のツールを使用する
$ git mergetool --tool=vimdiff

$ # マージの結果をシミュレートする(実際にはマージしない)
$ git merge --no-commit --no-ff feature-branch
$ # 確認後、コミットするか、マージを中止するか選択
$ git commit  # またはgit merge --abort

まとめ

Git merge conflictは、複数の開発者で開発する際に避けられない問題です。しかし、正しい手順と知識があれば、確実に解決することができます。

重要なポイント:

  • 原因を理解する:conflictは、同じファイルの同じ箇所が異なる内容に変更されている場合に発生します。
  • 解決手順を覚える:status確認→詳細確認→手動編集→ステージング→コミット、という6つのステップで確実に解決できます。
  • よくある間違いを避ける:マーカーの削除忘れ、片方の変更だけを採用、テスト未実施など、よくある間違いを認識して回避します。
  • 事前対策を講じる:頻繁なpull、短命なブランチ、コードレビューなど、conflictを防ぐためのベストプラクティスを実践します。

conflictが発生した場合、焦らず、冷静に対処することが重要です。本記事の内容を参考に、自信を持ってconflictと向き合いましょう。Git mergetoolなどの便利なツールも活用することで、より効率的にconflictを解決できます。

皆さんが快適なGit開発ライフを過ごせることを願っています。

タイトルとURLをコピーしました