チーム開発でブランチを統合するとき、「どのオプションを使えばいいのか」「コンフリクトが起きたらどう対処すればいいのか」と悩むことはありませんか。git mergeはGitの中でも特に重要なコマンドですが、オプションやマージ戦略が多く、最初は混乱しがちです。
この記事では、git mergeの基本的な使い方から、マージ戦略の選び方、コンフリクト解決の具体的な手順まで、実践的なコード例とともに解説します。
git mergeとは
git mergeは、2つ以上の開発履歴を1つに統合するGitコマンドです。複数のブランチで並行して進められた作業を、共通のブランチ(通常はmainやdevelop)に取り込む際に使用します。
基本的な構文は以下のとおりです。
# 基本構文
git merge <ブランチ名>
# オプション付き
git merge [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
[-s <strategy>] [-X <strategy-option>] [-m <msg>] [<commit>...]
# マージ操作の制御
git merge (--continue | --abort | --quit)たとえば、feature-branchの変更をmainブランチに取り込む場合は、次のように実行します。
# mainブランチに切り替え
git checkout main
# feature-branchをマージ
git merge feature-branchマージの種類を理解する
git mergeには大きく分けて2種類のマージ方法があります。状況に応じてGitが自動的に選択しますが、オプションで明示的に指定することもできます。
Fast-forwardマージ
マージ対象のブランチが現在のブランチの直接的な子孫である場合、Gitは「Fast-forward」マージを実行します。これは新しいマージコミットを作成せず、単純にブランチポインタを前方に移動させるだけの処理です。
# Fast-forwardマージの例
git checkout main
git merge feature-branch
# 出力: Fast-forward
# index.html | 2 ++
# 1 file changed, 2 insertions(+)履歴が線形のまま維持されるため、シンプルでわかりやすいというメリットがあります。ただし、機能ブランチの存在が履歴から見えなくなるため、「いつどの機能が統合されたか」を後から追跡しにくくなる点には注意が必要です。
3-wayマージ
2つのブランチの履歴が分岐している場合、Gitは3-wayマージを実行します。この方式では、両方のブランチのスナップショットと共通の祖先コミットの3つを使用して、新しいマージコミットを作成します。
# 3-wayマージの例
git checkout main
git merge feature-branch
# 出力: Merge made by the 'ort' strategy.
# src/app.js | 15 +++++++++++++++
# 1 file changed, 15 insertions(+)マージコミットには2つの親があり、どのブランチからどの変更が来たのかが履歴に記録されます。チーム開発では、この方式のほうが変更の追跡がしやすいため好まれることが多いです。
主要なオプション
git mergeには多くのオプションがありますが、実務でよく使うものを中心に紹介します。
| オプション | 説明 |
|---|---|
--ff | デフォルト。可能な場合はFast-forwardでマージ |
--no-ff | 常にマージコミットを作成(履歴を明確に保持) |
--ff-only | Fast-forwardでのみマージ。不可能な場合は中止 |
--squash | 変更を適用するがマージコミットは作成しない |
--no-commit | マージを実行するがコミットせず、ユーザーに確認させる |
-m <msg> | マージコミットのメッセージを指定 |
--abort | マージを中止し、マージ前の状態に復元 |
--continue | コンフリクト解決後、マージを続行 |
–no-ffオプションの活用
チーム開発では--no-ffオプションを使うことが推奨されます。これにより、Fast-forwardが可能な場合でも必ずマージコミットが作成され、機能ブランチの存在が履歴に明確に記録されます。
# マージコミットを強制的に作成
git checkout main
git merge --no-ff feature-branch -m "Merge feature: ユーザー認証機能を追加"この設定をデフォルトにしたい場合は、以下のコマンドでグローバル設定を変更できます。
git config --global merge.ff false–squashオプションの使いどころ
--squashオプションは、機能ブランチの複数のコミットを1つにまとめてマージしたい場合に便利です。ただし、マージコミットは作成されないため、自動的にコミットされません。
git checkout main
git merge --squash feature-branch
# この時点ではまだコミットされていない
git commit -m "Add feature: 複数コミットを1つにまとめて統合"プルリクエストでよく使われる「Squash and merge」と同様の効果が得られます。履歴をクリーンに保ちたい場合に有効ですが、個々のコミット履歴は失われる点に注意してください。
マージ戦略を理解する
git mergeは内部的に「マージ戦略」というアルゴリズムを使用して、ブランチの統合方法を決定します。-sオプションで明示的に指定できますが、通常はデフォルトのまま使用して問題ありません。
ort(デフォルト)
「Ostensibly Recursive’s Twin」の略で、Git 2.34で導入された新しいマージ戦略です。v2.50.0以降では、1つのブランチをマージする場合のデフォルトになっています。
# 明示的にort戦略を指定
git merge -s ort feature-branchortは3-wayマージアルゴリズムを使用し、ファイルのリネーム検出にも対応しています。従来のrecursive戦略と比べて、大規模リポジトリでのパフォーマンスとメモリ効率が大幅に改善されています。
その他の戦略
| 戦略 | 用途 |
|---|---|
recursive | v2.50.0以降はortの同義語。後方互換性のために残存 |
octopus | 3つ以上のブランチを同時にマージする場合のデフォルト |
ours | マージ対象の変更をすべて無視し、現在のブランチを維持 |
subtree | ツリー構造が異なるブランチをマージする場合に使用 |
複数のブランチを同時にマージする場合は、自動的にoctopus戦略が選択されます。
# 複数ブランチの同時マージ(octopus戦略が自動選択)
git merge feature1 feature2 feature3戦略オプション(-X)の活用
-Xオプションを使うと、マージ戦略の動作を細かく制御できます。特にコンフリクト解決を自動化したい場合に便利です。
| オプション | 説明 |
|---|---|
ours | コンフリクト時に現在のブランチの変更を優先 |
theirs | コンフリクト時にマージ対象ブランチの変更を優先 |
patience | 分岐が大きいブランチでミスマージを回避 |
ignore-space-change | 空白の変更を無視 |
コンフリクト時に特定の側を優先する
コンフリクトが発生したとき、一方の変更を自動的に優先させたい場合は-X oursまたは-X theirsを使います。
# コンフリクト時に現在のブランチ(main)の変更を優先
git merge -X ours feature-branch
# コンフリクト時にマージ対象(feature-branch)の変更を優先
git merge -X theirs feature-branch注意点として、-s ours(戦略)と-X ours(戦略オプション)は異なります。-s oursはマージ対象の変更をすべて無視しますが、-X oursはコンフリクトが発生した部分のみ現在のブランチを優先します。
コンフリクト解決の手順
コンフリクトは、同じファイルの同じ行に対して両方のブランチで異なる変更が加えられた場合に発生します。Gitは自動的にマージできない部分を「コンフリクトマーカー」で示し、手動での解決を求めます。
コンフリクトの検出
マージを実行してコンフリクトが発生すると、以下のようなメッセージが表示されます。
git merge feature-branch
# Auto-merging src/app.js
# CONFLICT (content): Merge conflict in src/app.js
# Automatic merge failed; fix conflicts and then commit the result.git statusコマンドで、コンフリクトが発生しているファイルを確認できます。
git status
# On branch main
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
# both modified: src/app.jsコンフリクトマーカーの読み方
コンフリクトが発生したファイルには、以下のようなマーカーが挿入されます。
<<<<<<< HEAD
現在のブランチ(main)の変更
=======
マージ対象ブランチ(feature-branch)の変更
>>>>>>> feature-branch<<<<<<< HEADから=======までが現在のブランチの内容、=======から>>>>>>>までがマージ対象ブランチの内容です。
diff3形式の活用
デフォルトのコンフリクトマーカーでは、「元々どうなっていたか」がわかりません。diff3形式を有効にすると、共通の祖先(変更前の状態)も表示されるため、解決が容易になります。
# diff3形式を有効にする
git config --global merge.conflictStyle diff3設定後のコンフリクトマーカーは以下のようになります。
<<<<<<< HEAD
現在のブランチの変更
||||||| base
元の内容(共通祖先)
=======
マージ対象ブランチの変更
>>>>>>> feature-branchこれにより、両方のブランチがどのように変更したかを比較しながら、適切な解決方法を判断できます。
解決の手順
コンフリクトを解決するには、以下の手順に従います。
# 1. コンフリクトファイルを確認
git status
# 2. ファイルを編集してコンフリクトマーカーを削除
# (エディタで開いて、最終的に残したい内容だけにする)
# 3. 解決したファイルをステージング
git add src/app.js
# 4. マージを完了
git commit
# または
git merge --continueVS Codeなどのエディタを使用している場合は、組み込みのマージエディタで視覚的に解決できます。「Accept Current」「Accept Incoming」「Accept Both」ボタンをクリックするだけで、簡単に選択できます。
VS Codeをマージツールとして設定
VS Codeの3-wayマージエディタを使いたい場合は、以下の設定を追加します。
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'設定後、コンフリクトが発生したらgit mergetoolコマンドでVS Codeが起動します。
mergeとrebaseの使い分け
ブランチを統合する方法として、mergeの他にrebaseもあります。どちらを使うべきかはチームのワークフローや状況によって異なりますが、基本的な指針を紹介します。
mergeを使うべき場合
共有ブランチや公開ブランチで作業している場合はmergeを使います。複数の開発者が同じブランチで作業しており、変更の履歴とコンテキストを保持する必要がある場合に適しています。
# 公開ブランチへの統合にはmergeを使用
git checkout main
git merge feature-branch監査やコンプライアンスの目的で完全なトレーサビリティが必要なプロジェクトにも向いています。
rebaseを使うべき場合
プライベートな機能ブランチで作業しており、クリーンな線形履歴を維持したい場合はrebaseを使います。プルリクエスト前にコミットを整理したり、メインブランチの最新変更を取り込む場合に適しています。
# ローカルの機能ブランチでrebaseを使用
git checkout feature-branch
git rebase mainGolden Rule of Rebasing
rebaseを使う際の最も重要なルールは「公開ブランチでは絶対にrebaseしない」ことです。他の開発者が作業しているブランチの履歴を書き換えると、全員の履歴が分岐し、混乱を招きます。
推奨されるワークフローは、ローカルの機能ブランチではrebaseを使ってコミットを整理し、チームとの統合にはmergeを使用するハイブリッドアプローチです。
マージの取り消し
マージ操作を取り消したい場合、状況に応じて異なるコマンドを使用します。
マージ中の中止
コンフリクトが発生してマージを中断したい場合は、--abortオプションを使います。
git merge --abortこのコマンドはマージ前の状態に復元します。ただし、マージ開始前に未コミットの変更があった場合は、完全には復元できない可能性があります。そのため、マージ前にはローカルの変更をコミットまたはstashしておくことを推奨します。
完了したマージの取り消し(ローカルのみ)
まだプッシュしていないマージを取り消す場合は、git resetを使います。
# 直前のマージを取り消し
git reset --merge HEAD~1
# または特定のコミットに戻す
git reset --hard <マージ前のコミットハッシュ>--hardオプションはワーキングディレクトリの変更も破棄するため、注意して使用してください。
プッシュ済みマージの取り消し
すでにリモートにプッシュしたマージを取り消す場合は、git revertを使います。これは履歴を書き換えずに、マージを打ち消す新しいコミットを作成します。
git revert -m 1 <マージコミットハッシュ>-m 1オプションは、マージコミットの最初の親(通常はマージ先のブランチ)を維持することを指定しています。
ベストプラクティス
日常的にgit mergeを使う上で、覚えておくと便利なベストプラクティスを紹介します。
マージ前の準備
マージを実行する前に、ローカルの変更をコミットまたはstashしておくことが重要です。未コミットの変更がある状態でマージすると、コンフリクトが発生した際にgit merge --abortで完全に復元できない可能性があります。
# 変更をstashしてからマージ
git stash
git merge feature-branch
git stash popコンフリクト予防
コンフリクトを最小化するには、小さく自己完結した変更を頻繁にコミットすることが効果的です。また、メインブランチからの変更を定期的に取り込むことで、大きな差分が蓄積するのを防げます。
# 定期的にmainの変更を取り込む
git checkout feature-branch
git merge main複数の開発者が同じファイルの同じ部分を同時に編集することを避けるのも、コンフリクト予防に有効です。
履歴の管理
チーム開発では、--no-ffオプションをデフォルトにすることで、機能ブランチの存在が履歴に明確に記録されます。これにより、どのコミットがどの機能に属していたかを後から追跡しやすくなります。
# グローバル設定で--no-ffをデフォルトに
git config --global merge.ff falseよくある質問
Fast-forwardと3-wayマージはどう使い分ければいいですか?
個人開発や小規模なプロジェクトでは、デフォルトのFast-forwardで問題ありません。チーム開発では--no-ffを使って常にマージコミットを作成し、機能ブランチの履歴を明確に残すことを推奨します。
コンフリクトが頻発する場合の対処法は?
メインブランチからの変更を頻繁に取り込むことで、コンフリクトを最小化できます。また、diff3形式を有効にすると、コンフリクトの原因がわかりやすくなり、解決が容易になります。
squashマージはいつ使うべきですか?
機能ブランチに多数の細かいコミット(WIPやtypo修正など)が含まれており、それらを1つのまとまったコミットとして統合したい場合に使います。ただし、個々のコミット履歴は失われるため、詳細な履歴が必要な場合は通常のマージを使ってください。
マージコミットのメッセージはどう書くべきですか?
マージコミットのメッセージには、何をマージしたか(機能名やチケット番号)を明記します。自動生成されるメッセージをそのまま使うこともできますが、-mオプションでカスタマイズすると後から探しやすくなります。
git merge --no-ff feature/user-auth -m "Merge feature: ユーザー認証機能 (#123)"まとめ
git mergeはブランチ統合の基本となる重要なコマンドです。Fast-forwardと3-wayマージの違いを理解し、--no-ffオプションでチーム開発に適した履歴管理を行うことで、効率的なワークフローを実現できます。
コンフリクトが発生した場合は、diff3形式を活用することで解決が容易になります。また、VS Codeなどのエディタのマージツールを使えば、視覚的にわかりやすく解決できます。
次のステップとして、チームのブランチ戦略(Git Flow、GitHub Flowなど)に合わせて、マージのオプション設定をカスタマイズしてみてください。
参考リンク
Git Book – Basic Branching and Merging



