T-CREATOR

Git でコンフリクトが起きたときの正しい解決手順と再発防止策

Git でコンフリクトが起きたときの正しい解決手順と再発防止策

チーム開発を進めていく中で、必ずと言っていいほど遭遇するのがGitコンフリクトです。「またコンフリクトが起きた」「どうやって解決すればいいの?」と頭を抱えた経験をお持ちの方も多いのではないでしょうか。

コンフリクトは確かに面倒ですが、正しい知識と手順さえ身につければ、冷静に対処できるようになります。むしろ、コンフリクトを適切に解決することで、より品質の高いコードベースを維持できるのです。

この記事では、Gitコンフリクトの基礎知識から実際の解決手順、そして二度と同じ問題で悩まないための予防策まで、体系的にお伝えいたします。

Git コンフリクトとは何か

コンフリクトが発生する仕組み

Gitコンフリクトは、複数の人が同じファイルの同じ部分を異なる方法で変更した際に発生します。これは、Gitが「どちらの変更が正しいか判断できない」状況を意味しています。

以下の図で、コンフリクト発生のメカニズムを確認しましょう。

mermaidflowchart TD
    main[main ブランチ] --> commit1[コミット A]
    main --> feature1[feature ブランチ]
    main --> feature2[hotfix ブランチ]
    
    feature1 --> change1[同じファイルを変更]
    feature2 --> change2[同じファイルを変更]
    
    change1 --> merge1[マージ試行]
    change2 --> merge1
    
    merge1 --> conflict[コンフリクト発生!]
    
    style conflict fill:#ff9999
    style change1 fill:#ffeb9c
    style change2 fill:#ffeb9c

この図が示すように、異なるブランチで同じファイルに変更が加わると、マージ時にGitは自動的に統合できず、開発者に判断を委ねることになります。

コンフリクトのタイプ別分類

Gitコンフリクトには主に以下の種類があります。

#タイプ説明発生頻度
1テキストコンフリクト同じ行の文字列変更
2削除 vs 変更一方で削除、他方で変更
3ファイル名変更同じファイルを異なる名前に変更
4バイナリファイル画像やドキュメントファイル

最も頻繁に発生するのはテキストコンフリクトですので、まずはこちらの解決方法をマスターすることが重要です。

コンフリクトが起きる主な原因

同一ファイル・同一行の変更

最も一般的なコンフリクトの原因は、複数の開発者が同じファイルの同じ行を変更することです。

javascript// 開発者Aの変更
const message = "Hello World";

// 開発者Bの変更  
const message = "こんにちは、世界";

このような状況で、GItは自動的にどちらを採用すべきか判断できません。

javascript// ファイル内で実際に表示されるコンフリクトマーカー
<<<<<<< HEAD
const message = "Hello World";
=======
const message = "こんにちは、世界";
>>>>>>> feature-branch

コンフリクトマーカーの意味を理解することで、解決の第一歩となります。

マージ戦略の問題

Gitには複数のマージ戦略があり、不適切な戦略選択がコンフリクトを複雑にする場合があります。

mermaidflowchart LR
    A[Fast-forward] --> B{同一直線上?}
    C[3-way merge] --> D{複数親コミット}
    E[Squash merge] --> F{履歴を圧縮}
    
    B -->|Yes| G[コンフリクトなし]
    B -->|No| H[別戦略必要]
    D -->|複雑| I[コンフリクト発生しやすい]
    
    style I fill:#ff9999

マージ戦略の理解により、コンフリクトが起きやすい状況を事前に把握できます。

リベースでのコンフリクト

リベース操作は履歴を美しく保てますが、コンフリクトが発生しやすい操作でもあります。

bash# リベース中のコンフリクト例
git rebase main
# Auto-merging src/utils.js
# CONFLICT (content): Merge conflict in src/utils.js
# error: could not apply 7eb2c4a... Update utility function

リベース時のコンフリクトは、一つ一つのコミットに対して解決が必要になるため、特に注意が必要です。

基本的なコンフリクト解決手順

Git ステータスの確認方法

コンフリクトが発生したら、まず現状を正確に把握することが重要です。

bash# 現在の状況を確認
git status

出力例を見てみましょう。

bash# 典型的なコンフリクト状態の出力
On branch feature-login
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:   src/auth.js
        both modified:   src/config.js

この出力から、src​/​auth.jssrc​/​config.jsでコンフリクトが発生していることがわかります。

コンフリクトファイルの特定

次に、具体的にどの部分でコンフリクトが起きているかを確認します。

bash# コンフリクトの詳細確認
git diff --name-only --diff-filter=U

このコマンドで、コンフリクトが発生しているファイル一覧を取得できます。

bash# 特定ファイルの差分確認
git diff src/auth.js

マージツールを使った解決

Visual Studio Codeを使用している場合、内蔵のマージツールが非常に便利です。

bash# VS Code でコンフリクトファイルを開く
code src/auth.js

VS Codeでは以下のような表示になります。

javascript// VS Code のコンフリクト表示例
<<<<<<< Current Change (HEAD)
function authenticate(user) {
    return jwt.sign({id: user.id}, SECRET_KEY);
}
=======
function authenticate(userData) {
    return jwt.sign({userId: userData.id}, JWT_SECRET);
}
>>>>>>> Incoming Change

VS Codeでは「Accept Current Change」「Accept Incoming Change」「Accept Both Changes」のボタンが表示され、クリック操作で解決できます。

手動での解決方法

エディタを使わず、手動で解決する場合の手順です。

bash# 1. ファイルを開いてマーカーを確認
cat src/auth.js | grep -A5 -B5 "<<<<<<< HEAD"

マーカーを理解して、適切なコードを残します。

javascript// 解決後のコード例(両方の良い部分を統合)
function authenticate(userData) {
    // ユーザーデータの検証
    if (!userData || !userData.id) {
        throw new Error('Invalid user data');
    }
    // JWT トークンの生成
    return jwt.sign({userId: userData.id}, JWT_SECRET, {expiresIn: '1h'});
}

解決後は、必ずファイルをステージングしてコミットします。

bash# 解決したファイルをステージング
git add src/auth.js

# コンフリクト解決をコミット
git commit -m "Resolve merge conflict in authentication function"

実践的なコンフリクト解決例

ケース1: シンプルなコードの競合

最も基本的なケースから見てみましょう。設定ファイルでの値の競合です。

javascript// config.js でのコンフリクト例
<<<<<<< HEAD
const API_URL = "https://api-prod.example.com";
const TIMEOUT = 5000;
=======
const API_URL = "https://api-staging.example.com"; 
const TIMEOUT = 3000;
>>>>>>> feature-api-update

このケースでは、本番環境とステージング環境のURLが競合しています。適切な解決方法を考えてみましょう。

javascript// 環境に応じた設定に統合
const API_URL = process.env.NODE_ENV === 'production' 
    ? "https://api-prod.example.com"
    : "https://api-staging.example.com";
const TIMEOUT = 5000; // 本番環境の値を採用

このように、単純に片方を選ぶのではなく、より良い解決策を考えることが重要ですね。

ケース2: 複数ファイルでの競合

大きな機能追加で複数ファイルに競合が発生した場合の対処法です。

以下のフローで、効率的に複数ファイルのコンフリクトを解決していきます。

mermaidflowchart TD
    start[コンフリクト発生] --> list[コンフリクトファイル一覧取得]
    list --> priority[優先順位決定]
    priority --> core[コアファイルから解決]
    core --> test[テストファイル解決]
    test --> config[設定ファイル解決]
    config --> verify[動作確認]
    verify --> commit[一括コミット]

優先順位に従って解決することで、依存関係によるエラーを避けられます。

bash# 複数ファイルのコンフリクト確認
git status | grep "both modified"

出力例:

bashboth modified:   src/user.js
both modified:   tests/user.test.js  
both modified:   config/database.js

ここでは、以下の順序で解決していきます。

  1. src​/​user.js(コアロジック)
  2. config​/​database.js(設定)
  3. tests​/​user.test.js(テスト)

各ファイルを順番に解決し、一つずつステージングしていきます。

bash# ファイルごとに解決してステージング
git add src/user.js
git add config/database.js  
git add tests/user.test.js

# 最後に一括コミット
git commit -m "Resolve conflicts in user management feature"

ケース3: 削除 vs 変更の競合

一方のブランチでファイルを削除し、他方で変更した場合のコンフリクトです。

bash# 削除vs変更のコンフリクト例
CONFLICT (modify/delete): old-utils.js deleted in HEAD and modified in feature-branch.
Version feature-branch of old-utils.js left in tree.

このケースでは、以下の選択肢があります。

#選択肢実行コマンド適用場面
1削除を採用git rm old-utils.jsファイルが不要な場合
2変更を採用git add old-utils.jsファイルが必要な場合
3新しいファイル名で保存git mv old-utils.js new-utils.jsリファクタリング中

適切な判断のために、変更内容を必ず確認しましょう。

bash# 変更内容の確認
git show feature-branch:old-utils.js

再発防止のためのベストプラクティス

ブランチ戦略の改善

コンフリクトを減らすためのブランチ管理戦略をご紹介します。

mermaidflowchart LR
    main[main] --> develop[develop]
    develop --> feature1[feature/user-auth]
    develop --> feature2[feature/payment]
    develop --> feature3[feature/dashboard]
    
    feature1 --> pr1[PR作成]
    feature2 --> pr2[PR作成] 
    feature3 --> pr3[PR作成]
    
    pr1 --> review[コードレビュー]
    pr2 --> review
    pr3 --> review
    
    review --> merge[develop へマージ]
    merge --> deploy[main へマージ・デプロイ]
    
    style merge fill:#99ff99
    style deploy fill:#99ccff

このような Git Flow を採用することで、コンフリクトを最小限に抑えられます。

重要なのは、長期間ブランチを分離させないことです。

bash# 定期的な develop との同期
git checkout feature-branch
git pull origin develop
git rebase develop

この習慣により、大きなコンフリクトを避けられます。

定期的なマージ・リベース

開発中は、定期的にメインブランチの変更を取り込むことが重要です。

bash# 毎日の作業開始時に実行する習慣
git checkout main
git pull origin main
git checkout feature-branch  
git rebase main

この手順を毎日実行することで、コンフリクトを小さく分割して対処できます。

#タイミング実行コマンド効果
1作業開始時git pull origin main最新状態の確認
2機能完成時git rebase main履歴の整理
3PR 作成前git pull origin develop最終チェック

チーム内のコミュニケーション強化

技術的な対策に加えて、チーム内でのコミュニケーションも重要な要素です。

以下のようなルールを設けることで、コンフリクトの発生を大幅に減らすことができます。

markdown# チーム開発ルール例

## 1. 作業分担の明確化
- 同じファイルを複数人で同時編集しない
- 大きな変更前にはチームで相談する

## 2. コミットメッセージの統一
- [feat] 新機能追加
- [fix] バグ修正  
- [refactor] リファクタリング
- [docs] ドキュメント更新

## 3. プルリクエストのサイズ制限
- 変更は 500 行以下に抑制
- 機能単位で細かく分割

チーム全体でこれらのルールを共有し、守ることで、コンフリクトの頻度を大きく減らせます。

基本的なコンフリクト解決手順

Git ステータスの確認方法

コンフリクトが発生したら、慌てずに状況を確認することから始めましょう。

bash# 基本的な状況確認コマンド
git status --porcelain

出力結果の読み方:

bash# コンフリクト状態のファイル表示例
UU src/main.js      # 両方で変更されたファイル  
AA src/config.js    # 両方で追加されたファイル
DD src/old.js       # 両方で削除されたファイル
DU src/temp.js      # 一方で削除、他方で変更

このように、先頭の文字でコンフリクトの種類を判断できるのです。

コンフリクトファイルの特定

全体状況が把握できたら、具体的なコンフリクト箇所を特定していきます。

bash# コンフリクトマーカーを含む行を検索
git grep -n "<<<<<<< HEAD"

実行例:

bashsrc/auth.js:15:<<<<<<< HEAD
src/utils.js:23:<<<<<<< HEAD
tests/auth.test.js:8:<<<<<<< HEAD

これにより、3つのファイルの具体的な行でコンフリクトが発生していることがわかります。

マージツールを使った解決

Git には様々なマージツールとの連携機能があります。

bash# 利用可能なマージツールの確認
git mergetool --tool-help

推奨ツールの設定:

bash# VS Code をマージツールに設定
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# マージツールの起動
git mergetool

マージツールを使用すると、視覚的にコンフリクトを解決できます。

手動での解決方法

コマンドラインで手動解決する場合の詳細手順です。

bash# 1. コンフリクトファイルを編集
vim src/auth.js

# 2. マーカーを削除して適切なコードに修正
# (エディタでマーカーを探して編集)

# 3. 修正結果をステージング
git add src/auth.js

# 4. 全ファイル解決後にコミット
git commit

手動解決のポイント:

  • <<<<<<<=======>>>>>>>マーカーをすべて削除
  • 必要なコードのみを残す
  • コードの動作を確認する

実践的なコンフリクト解決例

ケース1: シンプルなコードの競合

実際のコードで発生したコンフリクトの解決例をご紹介します。

まず、コンフリクトが発生した状況を確認しましょう。

javascript// src/calculator.js でのコンフリクト
function add(a, b) {
<<<<<<< HEAD
    // エラーハンドリング追加
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('引数は数値である必要があります');
    }
    return a + b;
=======
    // ログ機能追加
    console.log(`計算実行: ${a} + ${b}`);
    return a + b;
>>>>>>> feature-logging

このケースでは、一方でエラーハンドリング、他方でログ機能が追加されています。

解決策として、両方の機能を統合します。

javascript// 統合された解決案
function add(a, b) {
    // エラーハンドリング
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('引数は数値である必要があります');
    }
    
    // ログ機能  
    console.log(`計算実行: ${a} + ${b}`);
    
    return a + b;
}

両方の機能を活かした、より良いコードになりました。

ケース2: 複数ファイルでの競合

大きな機能開発で複数ファイルに競合が発生した場合の対処例です。

発生状況の確認:

bash# 競合ファイル一覧
git status --short | grep "^UU"

結果:

bashUU src/user.js
UU src/profile.js  
UU tests/user.test.js

解決戦略を図で表すと以下のようになります。

mermaidsequenceDiagram
    participant Dev as 開発者
    participant Core as コアファイル
    participant Test as テストファイル
    participant CI as CI/CD
    
    Dev->>Core: 1. user.js 解決
    Dev->>Core: 2. profile.js 解決
    Dev->>Test: 3. user.test.js 解決
    Dev->>CI: 4. テスト実行
    CI-->>Dev: 5. 結果確認
    Dev->>CI: 6. コミット

この順序で解決することで、依存関係のエラーを避けられます。

ファイルごとの解決手順:

bash# 1. user.js の解決
code src/user.js
git add src/user.js

# 2. profile.js の解決  
code src/profile.js
git add src/profile.js

# 3. テストファイルの解決
code tests/user.test.js
git add tests/user.test.js

全ファイル解決後、動作確認を行います。

bash# テストの実行で動作確認
yarn test user

問題なければコミットして完了です。

bashgit commit -m "Resolve conflicts in user profile feature

- Merge user authentication improvements
- Integrate profile update functionality  
- Update corresponding test cases"

ケース3: 削除 vs 変更の競合

ファイルのリファクタリング中によく発生するパターンです。

bash# 発生状況の例
CONFLICT (modify/delete): legacy-auth.js deleted in HEAD and modified in feature-oauth.
Version feature-oauth of legacy-auth.js left in tree.

このケースでは、メインブランチで古い認証ファイルを削除し、フィーチャーブランチで OAuth 機能を追加したために競合が発生しています。

まず、変更内容を確認します。

bash# 変更されたファイルの内容確認
git show feature-oauth:legacy-auth.js

確認した内容をもとに、適切な判断を行います。

javascript// legacy-auth.js の変更内容例
// OAuth 2.0 対応が追加されていた場合

export function authenticateWithOAuth(provider, token) {
    // 新しい OAuth 認証ロジック
    return oauth.validate(provider, token);
}

この場合、新しい認証システムに機能を統合する方針で解決します。

bash# 新しいファイルとして保存
git rm legacy-auth.js
git add src/oauth-auth.js

新しいファイルに機能を移行:

javascript// src/oauth-auth.js
export function authenticateWithOAuth(provider, token) {
    // OAuth 2.0 認証処理
    const validationResult = oauth.validate(provider, token);
    
    if (!validationResult.success) {
        throw new Error(`OAuth認証に失敗しました: ${validationResult.error}`);
    }
    
    return {
        userId: validationResult.userId,
        accessToken: validationResult.accessToken,
        expiresAt: validationResult.expiresAt
    };
}

トラブルシューティング

よくあるエラーと対処法

コンフリクト解決中に遭遇しやすいエラーとその対処法をまとめました。

エラー1: error: your local changes to the following files would be overwritten by merge

このエラーは、未コミットの変更があることが原因です。

bash# エラーメッセージ例  
error: Your local changes to the following files would be overwritten by merge:
    src/config.js
Please commit your changes or stash them before you merge.

対処方法:

bash# 変更を一時保存
git stash push -m "作業途中の変更を保存"

# マージを再実行
git merge feature-branch

# 保存した変更を復元
git stash pop

エラー2: fatal: cannot do a partial commit during a merge

マージ中に特定のファイルのみコミットしようとした場合のエラーです。

bash# 間違ったコマンド例
git commit src/auth.js
# fatal: cannot do a partial commit during a merge.

正しい対処法:

bash# 全ファイルを解決してからコミット
git add .
git commit -m "Resolve all merge conflicts"

復旧手順

解決作業中に問題が発生した場合の復旧方法です。

bash# マージを中断して元の状態に戻す
git merge --abort

# リベース中の場合
git rebase --abort

# 特定のコミットまで戻す場合
git reset --hard HEAD~1

ただし、reset --hard は変更を完全に失うので注意が必要ですね。

安全な復旧のためのバックアップ:

bash# 作業前にバックアップブランチを作成
git checkout -b backup-before-merge
git checkout feature-branch

# マージ実行
git merge main

バックアップがあれば、何か問題が起きても安心して復旧できます。

mermaidstateDiagram-v2
    [*] --> Working: 通常の開発
    Working --> Backup: バックアップ作成
    Backup --> Merge: マージ実行
    Merge --> Success: 成功
    Merge --> Conflict: コンフリクト発生
    Conflict --> Resolve: 解決作業
    Conflict --> Abort: 作業中断
    Resolve --> Success: 解決完了
    Abort --> Working: 元の状態に復旧
    Success --> [*]

このフローに従うことで、安全にコンフリクト解決作業を進められます。

まとめ

Gitコンフリクトは避けて通れない開発課題ですが、正しい知識と手順があれば決して恐れる必要はありません。

重要なポイントを改めて整理いたします。

コンフリクト解決の基本原則

  • まず現状を正確に把握する
  • 一つずつ丁寧に解決する
  • 解決後は必ず動作確認を行う

予防策の実践

  • 定期的なマージ・リベースの習慣化
  • 適切なブランチ戦略の採用
  • チーム内でのコミュニケーション強化

トラブル時の対処

  • 慌てずにエラーメッセージを確認
  • バックアップの活用
  • 復旧手順の把握

これらの知識を活用して、より快適で効率的な Git 運用を実現してください。コンフリクトをマスターすることで、チーム開発がさらにスムーズになることでしょう。

関連リンク