FlowingDev

マージコンフリクト入門:2つの世界が衝突したときのコードのもつれの解き方

マージツールがファイルの2つの異なるバージョンをどうやって調整し、変更を一行ずつ組み合わせて、作業を失うことなく単一の統一された結果を作り出すのかを学びましょう。

ツールを試す: Merge Tool

一文で言うと

マージツールは、ファイルの2つの異なるバージョンの変更を賢く統合し、一つの統一された結果にする手助けをします。変更が重複したときには、あなたが裁定者となって決着をつけられるのです。

解決する問題

想像してみてください。今は1995年。あなたと同僚は、会社のイケてる新しいGeoCitiesページのために、同じHTMLファイルをいじっています。あなたはイカした<marquee>タグを追加していて、同僚はゲストブックを追加しているところ。二人とも共有ネットワークドライブに変更を保存します。ここでの問題は?最後に保存した方が、もう一方の作業を完全に上書きしてしまうことです。マーキーは消え去り、涙が流れ、友情にヒビが入ります。

これは、モダンなバージョン管理が登場する前の共同作業のカオスな現実でした。その「解決策」は、「contact.htmlを編集中!触るなよ!」とオフィス越しに叫び合ったり、contact_v2_final_jennifer_edits_FINAL.htmlのようなファイル名のジャングルを作り出したりする、めちゃくちゃなやり方でした。控えめに言っても、大惨事でした。

GitやSubversion、Mercurialのようなバージョン管理システム(VCS)は、この問題を解決するために作られました。これらを使えば、複数の人が同じコードベースで、それぞれ自分のコピー上で作業し、後からその変更をマージして一つにまとめることができます。

しかし、これは新しくて、より興味深い問題を生み出します。あなたと同僚が、コードの全く同じ行を編集したらどうなるでしょう?VCSはあなたの心を読めません。あなたの変更が同僚の変更より重要かどうかなんて分からないのです。VCSはデジタルな両手を挙げて降参し、マージコンフリクトを宣言します。ここで登場するのがマージツールです。これは、ファイルの2つのバージョンを前にして冷静かつ忍耐強く座り、開発者であるあなたが、どうやって一つの調和した最終バージョンを作り出すかを決める手助けをしてくれる交渉人なのです。

内部の仕組み

マージツールは、単にテキストを並べて表示するビューアではありません。何十年にもわたって洗練されてきた、賢いアルゴリズムによって動いています。その魔法は、共通の出発点から見て変更をどう理解するかにあります。

秘密は3ウェイマージにあり

マージツールは単にyour-file.jsとtheir-file.jsを比較するだけだと思っているかもしれません。いえいえ、違います!それは2ウェイ(2者間)比較で、シンプルな「diff」ツールがやることです。真のマージツールは**3ウェイマージ(3者間マージ)**を実行します。

それは3つのファイルを見ます:

  1. MINE (または LOCAL): あなたの変更が加えられた、あなたのバージョンのファイル。
  2. THEIRS (または REMOTE): あなたがマージしようとしている、もう一方のバージョンのファイル。
  3. BASE (または ANCESTOR): あなたと相手のどちらも変更を加える前の、元のバージョンのファイル。

このBASEが鍵です。ツールは単に「これらのファイルは違うか?」と尋ねるのではなく、「MINEはBASEからどう変わったか?」そして「THEIRSはBASEからどう変わったか?」と尋ねます。この文脈が全てなのです。

以下が、ファイルの各固まり(チャンク)に対してツールが従うロジックです:

MINEはBASEから変更されたか? THEIRSはBASEから変更されたか? ツールの挙動
いいえ いいえ 何もすることなし。チャンクは同一。
はい いいえ 自動マージ: MINEからの変更を取り込む。
いいえ はい 自動マージ: THEIRSからの変更を取り込む。
はい はい コンフリクト! 両サイドが同じチャンクを変更。人間の介入が必要。

この3ウェイアプローチにより、ツールは簡単なものをすべて自動的に解決できるため、あなたと他の開発者が同時に同じアイデア(または相反するアイデア)を持っていた、実際のコンフリクトだけに集中できるのです。

Diffの「ハンク」の解剖学

内部では、マージツールはdiffアルゴリズム(古典的なHunt–McIlroyアルゴリズムなど)を実行して差分を見つけます。これらの差分は「ハンク」にグループ化されます。ハンクとは、変更が発生したファイルの連続したブロックのことです。

(ビジュアルツールを開く前の)生のテキストファイルでコンフリクトを見ると、こんなめちゃくちゃな表示になっています:

<<<<<<< HEAD
// MINE: こっちのコメントの方が良いと思う
function calculateTotal(price, quantity) {
=======
// THEIRS: 税計算を追加
function calculateTotal(price, quantity, taxRate) {
>>>>>>> feature-branch
  // ... function body
}
  • <<<<<<< HEAD: あなたの現在のバージョン(MINE)からのコンフリクトしているチャンクの始まりを示します。HEADはGitが現在のブランチを指すために使う名前です。
  • =======: 区切り線です。上部のマーカーとこの線の間にあるものがすべてMINEです。この線と下部のマーカーの間にあるものがすべてTHEIRSです。
  • >>>>>>> feature-branch: マージしようとしているもう一方のブランチ(THEIRS)からのコンフリクトしているチャンクの終わりを示します。

ビジュアルマージツールは、このフォーマットを解析し、もっと親しみやすい横並びや3ペインのビューで表示してくれます。醜いマーカーは、分かりやすい色分けやボタンに置き換えられます。

衝突の解決

コンフリクトが発生すると、マージツールはハンクのMINEバージョンとTHEIRSバージョンを提示します。最終的な決定権はあなたにあります。あなたにできることは:

  • MINEを選択: 相手の変更を破棄し、あなたの変更を保持する。
  • THEIRSを選択: あなたの変更を破棄し、相手の変更を保持する。
  • 結果を手動で編集: これが最も強力な選択肢です。相手の変更の一部とあなたの変更の一部を取り入れて、新しく正しいバージョンを作り上げることができます。例えば、相手の新しい関数パラメータは採用しつつ、あなたの改善されたコメントは保持する、といった具合です。

すべてのコンフリクトしているハンクを解決したら、ツールは最終的な統一されたファイルをビルドして保存する手助けをしてくれます。これでバージョン管理にコミットする準備が整いました。

実話から学ぶ

リファクタリングが重複したケース

二人の開発者、アニャとベンがeコマースのチェックアウト機能に取り組んでいます。アニャはギフトカード対応を追加するためのフィーチャーブランチで、calculatePrice関数を修正しています。一方、ベンは別のバグ修正ブランチで、同じ関数に欠陥を発見し、正しく動作するようにリファクタリングしています。

アニャがベンの修正を自分のブランチにマージしようとすると、GitがcalculatePriceで「コンフリクト!」と叫びます。彼女はマージツールを開きます。左側(MINE)には、新しいgiftCardAmountパラメータを持つ彼女のバージョンが表示されます。右側(THEIRS)には、ベンの大幅にリファクタリングされた、しかし正しいロジックが表示されます。単純にどちらか一方を選ぶのは間違いです。そうすれば、ギフトカード対応が失われるか、バグが再発するかのどちらかになってしまいます。マージツールのエディタを使い、彼女は手動で自分のgiftCardAmountロジックを、ベンの新しいリファクタリングされた関数の構造に統合しました。

教訓: マージコンフリクトは失敗ではありません。それは対話です。ツールは、2つの異なる、しかしどちらも正当な目標を、一つの正しい解決策に統合するための文脈を提供してくれます。

土壇場の設定ファイルシャッフル

チームは本番リリースに向けて大わらわ。mainブランチでは、リード開発者がconfig.ymlを更新し、本番データベースの認証情報を使ようにしたばかり。同時に、ジュニア開発者がホットフィックスブランチで、緊急の問題を診断するために同じconfig.ymlのログレベルをINFOからDEBUGに変更していました。

デプロイ前に、そのホットフィックスをmainにマージしなければなりません。マージコンフリクトが発生。マージツールで確認すると、2つの変更は異なる行にあることがわかります。データベースの変更は10行目、ログの変更は25行目です。変更が重複していないため、ツールの3ウェイマージアルゴリズムはこれを識別し、自動的にそれらを結合します。リード開発者はツールに表示された提案結果をざっと見て、両方の変更が存在し、正しいことを確認し、ワンクリックで承認しました。

教訓: マージツールは壊滅的なミスを防ぎます。もしツールがなければ、開発者は盲目的にどちらかのバージョンを受け入れてしまい、誤って本番データベースを指したままホットフィックスをデプロイしてしまったり、もっと悪いことに、デバッグログが有効なままmainブランチをデプロイしてしまったりしたかもしれません。

READMEのリフレッシュ

コードだけではありません!2人のテクニカルライターがプロジェクトのREADME.mdを更新しています。1人は「インストール」セクションをより明確にするために完全に書き直しています。もう1人は、ファイルの末尾に全く新しい「行動規範」セクションを追加しています。彼らはドキュメントの異なる部分で作業していたため、マージツールは彼らの作業を完璧に自動で結合し、より良いインストールガイドと新しい行動規範の両方を含む一つのREADME.mdを作成しました。

教訓: バージョン管理下にあるプレーンテキストファイルなら何でも――ドキュメント、設定ファイル、スクリプト、文章――マージツールの恩恵を受けられます。

よくある間違いと落とし穴

  • 盲目的に片方を選ぶ。 最もよくある間違いは、コンフリクトを見て、文脈を理解せずに「自分側の変更を採用」や「相手側の変更を採用」をクリックしてしまうことです。これは、機能が元に戻されたり、バグが再導入されたりする原因です。常に両方の変更を読んでください。
  • 手動編集を忘れる。 多くのコンフリクトは、二者択一の問題ではありません。正しい解決策は、しばしば両方の変更を組み合わせたものです。結果ペインに飛び込んで、コードを手で編集して正しくすることを恐れないでください。
  • 空白文字の変更を無視する。 時には、コンフリクトが単にタブ対スペースやインデントの違いだけのこともあります。些細なことに思えるかもしれませんが、一貫性を持って解決するのが最善です。プロジェクトにリンターやフォーマッターがある場合は、マージ後にファイルに対して実行し、混在したスタイルをクリーンアップしましょう。
  • テキストエディタで手動でコンフリクトを「解決」する。 <<<<<<<や>>>>>>>マーカーを見て、手で削除しようとするのは火遊びするようなものです。誤って本物のコード行を削除してしまったり、マーカーの一つを残してしまったりするのは驚くほど簡単で、そうなるとアプリケーションやビルドスクリプトが壊れてしまいます。解析はツールに任せましょう。
  • 生成されたファイルを解決しようとする。 package-lock.jsonや最小化されたCSSバンドルのようなファイルでコンフリクトが発生した場合、通常はマージを中断し、そのソースからファイル(例:npm installを実行する)を再生成してから、再度マージを試みる方が良いでしょう。これらを手で解決するのは悪夢です。

なぜ知っておくべきなのか

もしあなたがチーム(たとえ2人だけのチームでも!)の一員としてコード、ドキュメント、または設定を書くなら、マージコンフリクトに遭遇するでしょう。これは共同開発において避けられない、ごく普通のことです。

マージコンフリクトを恐れるのはジュニア開発者の証です。それが解決可能な問題だと理解しているのが経験の証です。マージツールをマスターすれば、パニックの瞬間が日常的な5分間のタスクに変わります。恐ろしい「CONFLICT」というメッセージが、単なる行き止まりの標識から、「おい、君とチームメイトが同じ場所で素晴らしいアイデアを出したみたいだ。ちょっと見て、もっと良くしてくれ」と語りかけるシンプルな道しるべに変わるのです。

さらに詳しく

理論はOK。さあ手を動かそう — 100%ブラウザ内で。

ツールを試す: Merge Tool