背景&なぜzdiff3が必要なのか
100行を超えるマージコンフリクトを解消する際、冷や汗をかいた経験はエンジニアなら誰しもあるはずです。「この部分はAさんが削除したのか、それともBさんが追加したのか? そもそも変更前の元の値は何だったのか?」と頭を抱えることになります。厄介なことに、Gitのデフォルト表示では、現在のブランチ(HEAD)とマージ対象ブランチという対立する2つのコードブロックしか画面に表示されません。両者が編集を始める前の、ファイルの元の状態に関する情報が一切ないのです。
コンテキスト(文脈)が失われるということは、推測に頼らざるを得ないことを意味します。私のチームでも以前、忘れられない苦いトラブルがありました。ある開発者がコンフリクトを解消する際、自分のブランチのコードだけを残し、相手のブランチのコードを丸ごと上書き削除してしまったのです。その結果、リリースされたばかりのクーポン検証ロジックが、セール開始当日の夜に本番環境から完全に消失してしまいました。チーム全員でコミットログを追い、深夜3時まで徹夜で緊急ロールバックとHotfix対応に追われる羽目になりました。
これが、Gitがデフォルトで表示する2方向の差分(2-way diff)によるコンフリクトです:
<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 3000;
const retries = 3;
>>>>>>> feature/retry-logic
これを見ただけで、元のtimeout値がいくつだったか分かるでしょうか? HEADブランチが1000から5000に増やしたのか、それともfeatureブランチが5000から3000に減らしたのか? これだけでは判断がつきません。真相を知るには、git logやgit blameを実行して履歴を遡るしかなく、多大な時間が奪われてしまいます。
この課題を解決するため、Gitには以前からdiff3というモードが存在していました。この仕組みは、ブランチが分岐する前の共通の祖先(Common Ancestor)を表す||||||| baseブロックを中央に追加するものです。
しかし、従来のdiff3は処理がやや大雑把でした。コンフリクト領域の両端にある、実際には両ブランチで完全に一致しているコード行まで巻き込んでしまうことが多く、その結果コンフリクトブロックが無駄に長くなり、多くの不要なコードが混入してしまっていました。
2022年初頭にリリースされたGit 2.35より、この欠点を克服したzdiff3(Zealous diff3)が登場しました。「Zealous(徹底的な)」という言葉が示す通り、Gitが先頭と末尾の一致する行を積極的にトリミング(削減)してくれます。そのため、実際に衝突している行とそれに対応するbase部分だけがスマートに残されます。私の会社で12名の開発チームにzdiff3を導入したところ、リリース時のマージミスによるロジック欠落などのインシデントが80%以上減少しました。
Gitのバージョンを確認する
この機能はGit 2.35以降で利用可能です。まずは使用中のマシンのバージョンを確認しましょう:
git --version
バージョンが2.35未満の場合(例えばUbuntu 20.04 LTSのデフォルトはGit 2.25)、Gitをアップデートする必要があります:
# Ubuntu / Debian
sudo add-apt-repository ppa:git-core/ppa -y
sudo apt update
sudo apt install git -y
# macOS (Homebrew)
brew install git
# Arch Linux
sudo pacman -S git
Git 2.35以降であれば、追加の外部プラグインなどをインストールすることなく、すぐに使用できます。
Gitでzdiff3を有効にする方法
作業環境や要件に合わせて、マシン全体(グローバル)に適用することも、特定のリポジトリだけに設定することも可能です。
1. グローバル設定(推奨)
ターミナルで以下のコマンドを一度実行するだけで、マシン上のすべてのプロジェクトに適用されます:
git config --global merge.conflictstyle zdiff3
2. .gitconfigファイルを直接編集する
ユーザー設定ファイル(~/.gitconfig)を直接編集したい場合は、以下の2行を追加するだけです:
[merge]
conflictstyle = zdiff3
3. 特定のリポジトリにのみ適用する
会社の規則などにより特定のリポジトリのみに設定したい場合は、--globalフラグを外して実行します:
cd /path/to/your-project
git config merge.conflictstyle zdiff3
4. 実際の比較:デフォルト vs zdiff3
例えば、共通の祖先(元ファイル)に以下のような計算関数があったとします:
// common ancestor
function calculateTotal(price, tax) {
const subtotal = price * 1.1;
return subtotal + tax;
}
mainブランチでは、税率を10%から15%に変更しました:
function calculateTotal(price, tax) {
const subtotal = price * 1.15;
return subtotal + tax;
}
一方、featureブランチでは変数名をsubtotalからamountに変更し、税率を20%に引き上げました:
function calculateTotal(price, tax) {
const amount = price * 1.2;
return amount + tax;
}
この2つのブランチをマージすると、zdiff3はコンフリクト箇所を次のようにすっきりと整理して表示します:
function calculateTotal(price, tax) {
<<<<<<< HEAD
const subtotal = price * 1.15;
||||||| base
const subtotal = price * 1.1;
=======
const amount = price * 1.2;
>>>>>>> feature
return subtotal + tax; // マージ後に新しい変数名に合わせて修正が必要
}
||||||| baseブロックがあるおかげで、状況が一目瞭然になります:
- 元の行は
subtotal = price * 1.1だった。 - HEADブランチは係数を1.15に引き上げただけである。
- featureブランチは係数を1.2に変更し、さらに変数名を
amountに変更している。
双方の変更の意図と経緯を正確に把握できるため、的確なマージの判断を10秒足らずで下すことができます。
設定の確認と実際の体験
設定が完了したら、次のコマンドを実行してGitに設定が正しく反映されているか確認します:
git config --get merge.conflictstyle
出力結果としてzdiff3と表示されれば設定完了です。
30秒でできるクイックテスト
/tmpディレクトリで以下のスクリプトを実行すれば、zdiff3の動作を実際に体験できます:
# 1. テスト用リポジトリの作成
mkdir /tmp/test-zdiff3 && cd /tmp/test-zdiff3
git init
git config merge.conflictstyle zdiff3
# 2. ベースコミット(Ancestor)の作成
echo -e "alpha\nbravo\ncharlie" > test.txt
git add test.txt
git commit -m "Commit base"
# 3. ブランチAを作成しbravoの行を変更
git checkout -b branch-a
echo -e "alpha\nbravo-modified-A\ncharlie" > test.txt
git commit -am "Branch A update"
# 4. mainに戻りブランチBを作成して同じ箇所を変更
git checkout main
git checkout -b branch-b
echo -e "alpha\nbravo-modified-B\ncharlie" > test.txt
git commit -am "Branch B update"
# 5. マージコンフリクトを発生させる
git merge branch-a
生成されたtest.txtを開くと、差分が明快に整理されていることが分かります:
alpha
<<<<<<< HEAD
bravo-modified-B
||||||| base
bravo
=======
bravo-modified-A
>>>>>>> branch-a
charlie
一致しているalphaとcharlieの行はコンフリクトの外側にきれいに保たれています。そして元のbravoから両ブランチがどのように分岐したのかがはっきりと見て取れます。
VS CodeやJetBrainsとの優れた親和性
VS CodeやCursor、JetBrains製IDE(IntelliJ IDEA、WebStormなど)といった主要なエディタは、この3要素の構造を適切に認識します。例えばVS Codeでzdiff3形式のコンフリクトファイルを開くと、自動的に3-Way Merge Editorが起動します。中央にCommon Ancestor、左右にCurrentとIncomingが並ぶため、チームメンバーが書いたコードを誤って消してしまう心配もなく、残したいコードにチェックを入れていくだけで安全に解決できます。
