一般的なGitマージコンフリクトの解決:ステップバイステップのトラブルシューティングガイド

この不可欠なトラブルシューティングガイドで、Gitマージコンフリクトを習得しましょう。競合マーカー(`<<<<<<<`、`>>>>>>>`)の特定方法、手動での解決戦略(ローカルの保持、リモートの保持、または結合)の適用、およびマージやリベースを安全に完了する方法を学びます。これらの明確で段階的なコンフリクト解決手順に従い、フラストレーションを生産性へと変えましょう。

一般的なGitマージコンフリクトの解決:ステップバイステップのトラブルシューティングガイド

Gitマージコンフリクトとは、Gitが重複する変更を検出し、最終バージョンを選択する必要があることを意味します。これは通常、2つのブランチが同じ行を変更した場合、同じファイルを異なる方法でリネームした場合、または両方が自動マージできないファイルを編集した場合に発生します。

目標はシンプルです:コンフリクトを確認し、実際に必要なバージョンにファイルを編集し、ステージングしてから、マージまたはリベースを続行します。

Gitマージコンフリクトとは何か

マージコンフリクトは、Gitが1つのブランチから別のブランチへの変更を統合しようとしたとき(例:git mergegit rebaseを使用)、両方のブランチが同じファイルの同じ行を独立して変更した場合に発生します。Gitは重複しない変更を結合するのに優れていますが、変更が直接重複する場合は人間の介入が必要です。

Gitがコンフリクトを通知する方法

マージ中にコンフリクトが発生すると、Gitはすぐに操作を停止し、マージが失敗したことを通知します。影響を受けるファイルは、作業ディレクトリ内でコンフリクトとしてマークされます。ステータスは次のコマンドで確認できます:

git status

出力には「Unmerged paths」としてファイルがリストされ、マージを続行する前に手動での解決が必要であることを示します。

ステップ1:コンフリクトマーカーを特定する

Gitがマージを停止すると、コンフリクトしたファイルには、Gitによって挿入された特別なマーカーが含まれ、コンフリクトしているセクションを明確にします。これらのマーカーは、どの変更がどのブランチから来たかを正確に確認するのに役立ちます。

コンフリクトマーカー

通常のテキストコンフリクトでは、2つのバージョンのコンテンツを囲む3つのマーカー行が表示されます:

  1. <<<<<<< HEAD
    • 現在のブランチ(マージ先のブランチ)からの変更の開始を示します。
  2. =======
    • 2つの競合する変更セット間の区切りとして機能します。
  3. >>>>>>> branch-name
    • 受信ブランチ(マージ元のブランチ)からの変更の終了を示します。

コンフリクトブロックの例:

feature/Amainにマージしていて、両方のブランチがconfig.jsの10行目を編集したと仮定します:

// config.js

function getTimeout() {
<<<<<<< HEAD
  return 5000; // Main branch default
=======
  return 10000; // Feature A override
>>>>>>> feature/A
}

ステップ2:ファイルを編集してコンフリクトを解決する

コンフリクトの解決には、Gitマーカーを削除し、希望するコードの組み合わせを選択するためにファイルを編集することが含まれます。主な解決戦略は3つあります:

A. HEAD(現在のブランチ)からの変更を保持する

現在のブランチ(HEAD)のバージョンが正しいと判断した場合、受信変更とすべてのマーカーを削除します。

解決アクション:

// config.js

function getTimeout() {
  return 5000; // Main branch default
}

B. 受信ブランチからの変更を保持する

マージされるブランチからの変更が正しいと判断した場合、現在のブランチの変更とすべてのマーカーを削除します。

解決アクション:

// config.js

function getTimeout() {
  return 10000; // Feature A override
}

C. 変更を結合または書き換える(ハイブリッドアプローチ)

多くの場合、最善の解決策は、両方の側のロジックを取り入れた新しいバージョンを手動で構築するか、両方の元の変更の要件を満たすようにコードを完全に書き換えることです。

解決アクション(ハイブリッドの例):

// config.js

function getTimeout() {
  // Set timeout based on environment variable, combining logic
  if (process.env.NODE_ENV === 'production') {
      return 10000;
  }
  return 5000; 
}

ベストプラクティス: コンフリクトブロックを解決した後、結果のコードがコンパイルされ、正しく機能することを常に確認してください。この段階で単体テストを実行することを強くお勧めします。

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

すべてのコンフリクトファイルを手動で編集し、すべての<<<<<<<=======>>>>>>>マーカーを削除した後、これらの変更をステージングして、コンフリクトが処理されたことをGitに通知する必要があります。

解決した各ファイルに対して標準のgit addコマンドを使用します:

git add config.js
git add src/utils/helper.py
# ... すべてのコンフリクトファイルに対して繰り返し

Gitが解決を認識したことを確認するには、再度git statusを実行します。以前に未マージだったすべてのパスが、「Changes to be committed」の下に表示されるはずです。

ステップ4:マージまたはリベースを完了する

すべてのコンフリクトがステージングされたら、開始した元のコマンドに基づいて操作を完了します:

git mergeの完了

標準のマージを実行していた場合、コミットで完了します:

git commit

Gitは通常、事前に入力されたマージコミットメッセージを含む設定済みのテキストエディタを開きます。確認し、保存してエディタを閉じます。マージはこれで完了です。

git rebaseの完了

リベースを実行していた場合、プロセスを続行し、解決された状態の上に後続のコミットを適用します:

git rebase --continue

リベースシーケンス内の後続のコミットもコンフリクトを引き起こす場合、遭遇する各コンフリクトに対してステップ2から4を繰り返します。

難しいコンフリクトのトラブルシューティングのヒント

上記の手順は標準的な解決をカバーしていますが、複雑なシナリオでは代替アプローチが必要になる場合があります:

操作を中止する

マージまたはリベースを開始した後、状況が複雑すぎる、またはチームメイトと相談する必要があると気付いた場合、コマンドが発行される前の状態にいつでも戻すことができます:

git merge --abort  # 'git merge'で開始した場合
git rebase --abort  # 'git rebase'で開始した場合

ビジュアル差分ツールを使用する

多くの重複する変更がある複雑なファイルの場合、専用の3方向マージツール(VS Codeの組み込みマージエディタ、KDiff3、Meldなど)を使用することを強くお勧めします。設定済みのツールを直接起動できます:

git mergetool

このインターフェースは通常、ローカルバージョン、リモートバージョン、およびベースの共通祖先を表示し、手動での選択がはるかに明確になります。

バイナリファイルを扱う

Gitはバイナリファイル(画像やコンパイル済みアセットなど)を自動的にマージできません。2つのブランチが同じバイナリファイルを変更した場合、Gitはコンフリクトを報告します。この場合、優先するファイルを作業ディレクトリにコピーし、ステージングして、コミット/続行することで、どのバージョンを保持するかを手動で選択する必要があります。

# マージ中、現在のブランチのバージョンを保持する
git checkout --ours image.png

# または、マージされるブランチのバージョンを保持する
git checkout --theirs image.png

git add image.png
git commit

リベース中、--ours--theirsは、Gitが新しいベースにコミットを再生しているため、逆に感じられることがあります。git statusを実行し、ファイルを確認し、ステージングする前に選択したバージョンを確認してください。

まとめ

マーカーを盲目的に削除して「コンフリクトを除去」しようとしないでください。両方の側を読み、最終的なコードがどうあるべきかを決定し、関連するテストを実行し、解決したファイルをステージングします。その後、マージの場合はgit commit、リベースの場合はgit rebase --continueを使用します。