マージ

ブランチを開発の独立したラインでメンテナンスするのに使用する所では、何らかの段階で、ブランチに対して行われた変更をトランクにマージしたくなるでしょう。逆もまた然りです。

Subversion 内でどのようにブランチやマージを作成しているるかを、はじめる前に理解しておくのは(かなり複雑になってしまいますが)重要です。Subversion book の Branching and Merging の章を読むのを強くお勧めします。ここには詳しい説明とたくさんの使用例があります。

注意する次のポイントは、マージは 常に 作業コピーで行われるということです。ブランチの中に、変更をマージしたい場合、ブランチの作業コピーをチェックアウトして、その作業コピーで TortoiseSVNマージ... を行い、マージウィザードを起動しなければなりません。

一般的には、マージは変更されていない作業コピーに対して行うのが良いでしょう。作業コピーに何か他に変更が加えられているなら、まずコミットしてください。思うようにマージできないのなら、変更の取り消しを行う必要があるかもしれません。 変更の取り消し コマンドは、マージする前に行った変更を すべて 取り消してしまいます。

マージを行うには、以下で説明するような、少し異なる方法で行う、3つのよくあるケースがあります。マージウィザードの最初のページで、どちらで行うか確認しますので、選択してください。

リビジョンの範囲をマージ

これは、1つ以上のリビジョンがブランチ(もしくはトランク)に存在し、それらの変更を別のブランチに反映したいような場合に使用します。

Subversion に以下のことを行うように指示を出しています。ブランチAのリビジョン 1 [FROM] から、ブランチAのリビジョン 7 [TO] までの必要な変更点を計算し、(トランクやブランチBの)作業コピーに変更点を適用する

リビジョン範囲を空欄のままにした場合、 Subversion はマージ追跡機能を使用して、マージに使用する正しいリビジョン範囲を計算します。この機能は再統合や自動マージとして知られています。

異なる2つのツリーをマージ

これは、再統合する方法のもっと一般的なケースで、Subversion に対して、トランクの HEAD リビジョン(開始点)からブランチの HEAD リビジョン(終了点)までの必要な変更点を計算し、(トランクの)作業コピーに変更点を適用しなさい と指示します。結果的に、トランクがまさしくブランチと同様になっているということになります。

サーバー・リポジトリが、マージ追跡をサポートしていない場合、これはブランチをトランクにマージする唯一の方法になります。別のケースとしては、ベンダブランチを使用していて、新しくベンダから落としたコードの変更を、トランクにマージする必要がある場合もありえます。詳細は、Subversion Book の vendor branches をご覧ください。

リビジョン範囲のマージ

図4.54 マージウィザード - リビジョン範囲の選択

マージウィザード - リビジョン範囲の選択


マージ元のURL 欄には、作業コピーに取り込みたい変更があるブランチやタグのフォルダーの URL をすべて入力してください。... をクリックしてリポジトリを閲覧し望みのブランチを探すこともできます。以前このブランチからマージしたことがあるのであれば、以前使用されたURLの履歴のドロップダウンリストから選択するだけです。

名前が変更されたり削除されたりしたブランチからマージしようとする場合は、そのブランチが存在したリビジョンに戻らなければなりません。このような場合、マージする範囲のリビジョン(下記)をペグリビジョンで指定しないと、HEADリビジョンのパスが見つけられずにマージが失敗します。

マージするリビジョンの範囲 フィールドに、マージするリビジョンのリストを入力してください。ここではリビジョン1つ、コンマで区切ったリビジョンの指定、ダッシュでつないだリビジョンの範囲と以上の組み合わせが使用できます。

マージ時にペグリビジョンを特定する必要がある場合、ペグリビジョンをリビジョン番号の最後に、 5-7,[email protected] [email protected][email protected]ます。

重要

TortoiseSVN とコマンドラインクライアントを比較すると、リビジョン範囲を指定する方法に重要な違いがあります。これを思い浮かべる簡単な方法は、フェンスの柱と、フェンスの板について考えることです。

コマンドラインクライアントでは、 で指定した 2 本の フェンスの柱 のリビジョンを使用して、マージする変更を指定します。

TortoiseSVN では、フェンスの板 を使用して、マージする変更セットを指定します。マージするためのリビジョンを指定するログダイアログを使用する場合、チェンジセットとしてどこに各リビジョンが現れるか、明白になるためです。

かたまりとしてリビジョンをマージしていると、subversion book にある方法では、今回 100-200 をマージし、次回に 200-300 をマージすることになります。TortoiseSVN では、今回 100-200 をマージし、次回に 201-300 をマージします。

この違いは、メーリングリストでたくさんの論争を巻き起こしてきました。私たちは、コマンドラインクライアントと違うことを認めます。しかし、大多数の GUI ユーザーにとって、私たちが実装した方法の方が理解しやすいと信じています。

必要なリビジョンの範囲を選択する最も簡単な方法は、ログを表示 をクリックして、最近の変更点一覧をログメッセージと共に表示することです。単一リビジョンからマージしたい場合は、そのリビジョンを選択するだけです。複数のリビジョンからマージしたい場合は、その範囲を(通常 Shift キーを押しながら)選択してください。OK をクリックすると、マージするリビジョン番号のリストに入力されます。

すでにコミットしてしまった変更を取り消して、作業コピーを元に戻すようにマージする場合、戻したいリビジョンを選択して、逆マージチェックボックスを必ずチェックしてください。

このブランチからの変更をすでにマージしてある場合、うまくいけば変更をコミットしたときのメッセージに、マージした最後のリビジョンを記録してあるかもしれません。この場合、作業コピーのログメッセージを追跡するのに ログを表示 を使用できます。リビジョンをチェンジセットとして考えているのを思い出し、前回のマージの終点を今回のマージの始点をしてください。たとえば、前回リビジョン 37 から 39 までマージした場合、今回のマージの始点をリビジョン 40 にしてください。

Subversion のマージ追跡機能を使用する場合、どのリビジョンをすでにマージしたかを覚えておく必要はありません。Subversion が記録しています。リビジョン範囲をからのままにしておくと、まだマージしていないリビジョンすべてが対象になります。詳細は 「マージ追跡」 をご覧ください。

マージ追跡機能を使用する場合は、ログダイアログには前回マージされたリビジョンや、共通の祖先以前の時点(つまりブランチがコピーされる以前)のリビジョンが、グレーで表示されます。マージ不可能なリビジョンを隠すチェックボックスを使用すると、こうしたリビジョンを隠し、マージすることができるリビジョンだけを表示することができます。

他の人が変更をコミットしている場合、 HEAD リビジョンを指定する場合は注意してください。最後の更新のあとに誰かがコミットを行っており、想定しているリビジョンを指していないかもしれません。

リビジョンの範囲は空欄だったり、すべてのリビジョンのラジオボタンがチェックされていたりした場合は、 Subversion はまだマージされていないすべてのリビジョンをマージします。これは再統合または自動マージとして知られています。

再統合マージを適用するには、いくつか条件があります。まず、サーバーがマージ追跡をサポートしていなければなりません。作業コピーの深さに制限があってはなりません(部分的なチェックアウトであってはならない)。ローカルの変更や切り替えられた項目、 HEAD 以外のリビジョンに更新された項目があってはなりません。ブランチでの開発中にトランクに行われた変更は、ブランチをまたがってマージされます(もしくはマージ済みとマークされます)。マージ対象になるリビジョンの範囲は自動的に計算されます。

次へ >をクリックすると、 「マージオプション」 に進みます。

2つの異なるツリーをマージする

図4.55 マージウィザード - ツリーのマージ

マージウィザード - ツリーのマージ


機能ブランチをトランクにマージするために、この方法を使用する場合、トランクの作業コピーでマージウィザードを起動する必要があります。

開始点: 欄には、トランク のフォルダーの URL を入力してください。間違っているように思われるかもしれませんが、トランクが、ブランチの変更を追加する起点となることを思い出してください。... をクリックして、リポジトリを参照することもできます。

終了点: 欄に、機能ブランチのフォルダーの URL を入力してください。

開始点リビジョン 欄と 終了点リビジョン 欄の両方に、同期をとりたい2つのツリーの最新リビジョン番号を入力してください。他の誰もコミットしていないという確認が取れているのなら、どちらも HEAD リビジョンを指定できます。同期をとってから、誰かがコミットする機会があったのなら、最近のコミットの内容を失わないように、リビジョン番号を指定してください。

リビジョンを選択するには ログを表示 も使用できます。

マージオプション

ウィザードのこのページでは、マージの実行前に、さらにオプションの指定ができます。ほとんどの場合デフォルトの設定を使用するだけでしょう。

マージする深さを指定することもできます。これは、作業コピー内をどの程度深くたどってマージを行うかということです。深さについては 「チェックアウトの深さ」 で説明しています。デフォルトの深さは 作業コピー で、既存の深さをそのまま使用します。

大抵の場合、マージ時にはファイルの履歴を考慮した方が好ましいので、共通の祖先からの変更がマージされます。時には、リポジトリに入っていなくても、関連している可能性が高いファイル同士ををマージする必要もあるでしょう。例えば、第三者が作成したライブラリのバージョン1と2が、別々なディレクトリに入れられていた場合です。理屈からみれば両者は関連していますが、Subversionにはそれが別個にインポートされたファイルの集合としか認識できず、両者が関連しているとは分かりません。この2つのツリーの差分をマージしようとすると、全体を追加して全体を削除する操作になってしまいます。Subversionが履歴の差分を無視して現状の差分のみを使用するようにするには、 履歴を無視する をチェックしてください。本件についての詳細な説明は、 Subversion book の Noticing or Ignoring Ancestry を参照してください。

改行コードや空白の変更に対する扱い方を指定できます。このオプションについては 「改行コードと空白のオプション」 で説明しています。デフォルトの振る舞いは、空白や改行コードの差異も実際の変更としてマージします。

強制的にマージする をチェックすると、ローカルで変更されたファイルやバージョン管理されていないファイルに対して削除を行うようなツリーの競合を無視します。こうしたファイルが削除されると復元する方法がないので、このオプションはデフォルトではチェックされていません。

マージ追跡を使用していて、実際にはマージせずにマージしたという印をつけたい場合、 マージを実施せず、マージしたことにする チェックボックスをチェックしてください。そうする理由として2つ考えられます。1つは、マージそのものがマージアルゴリズムに対して複雑すぎ、手でコードを変更した後、マージアルゴリズムが行うようにマージにより変更したという印をつけたい場合です。もう1つは、特定のリビジョンがマージされるのを防ぐ場合です。すでにマージしたという印がついていれば、マージ追跡を関知するクライアントでは、マージを防げます。

すべての設定が終われば、あとは マージ ボタンを押すだけです。このとき マージのテスト を押すと、作業コピーを 変更せずに マージ結果をプレビューすることができます。これでマージを実行した時に変更が入るファイルの一覧や、競合が発生する 可能性のある ファイルを見ることができます。マージ処理はマージを追跡することではるかに複雑になるため、あらかじめマージが競合なしで完了するかを確実に判断する方法がないので、実際には問題なくマージできるようなファイルであっても、マージのテストでは競合すると表示される場合もあります。

マージ進行ダイアログには、マージの各状態を、リビジョン範囲とともに表示します。ここには想定していたよりも一つ多くリビジョンを表示するかもしれません。例えば、リビジョン 123 をマージするように指示した場合、進行ダイアログには、リビジョン 122 〜 123 をマージ と表示されます。マージは差分と密接に関連していることを思い出してください。マージ処理は、リポジトリの二点間における差分の一覧を生成し、その差分を作業コピーに適用する形で動作します。進行ダイアログは、単純にこの差分の始点と終点を表示しているに過ぎません。

マージ結果のレビュー

さて、マージが完了しました。マージ結果を見て期待通りになっているかを確認するのがいいでしょう。通常マージはかなり複雑です。ブランチがトランクからかなりずれてしまえば、しばしば競合を引き起こします。

ヒント

リビジョンが作業コピーにマージされるたびに、TortoiseSVNはマージされた全リビジョンからログメッセージを作成します。コミットダイアログの最近のログメッセージボタンを押すと現れます。

作成されるメッセージをカスタマイズするには、作業コピーに該当するプロジェクトプロパティを設定してください。詳しくは、「マージログメッセージのテンプレート」を参照してください。

バージョン 1.5 未満の Subversion のクライアント・サーバーは、マージ情報を格納しておらず、マージしたリビジョンは、手作業で追跡しなければなりません。ある変更点のテストを行い、そのリビジョンをコミットする際に、マージで取り込んだリビジョン番号を 常に コミットログに記録するべきです。あとで別のマージを適用しようとしたときに、再度変更を取り込むことがないよう、すでにマージした内容を知る必要があります。これについては Subversion Book の Best Practices for Merging をご覧ください。

サーバーとすべてのクライアントが Subversion 1.5 以上の場合、マージ追跡機構がマージしたリビジョンを記録し、再度マージすることがないようにします。これにより、単純にリビジョン範囲全体を指定し、実際に新しいリビジョンのみがマージされるといったことができるようになります。

ブランチ管理は重要です。このブランチをトランクに合わせて最新の状態を維持したければ、たびたびマージを確実に行わないと、ブランチとトランクがだんだん離れていってしまうのです。もちろん上で説明したように、変更点を再度マージしてしまうことは避けなければなりません。

ヒント

昨日ブランチからトランクへマージが完了すると、トランクには新機能のすべてのコードが含まれ、ブランチは必要なくなります。必要ならリポジトリから削除してかまいません。

重要

Subversion ファイルをフォルダーとマージはできませんし、逆もまた同様です。ただフォルダーとフォルダー、ファイルとファイルで行えます。ファイルをクリックしてマージダイアログを開いたら、ファイルのパスを与えなければなりません。フォルダーを選択してダイアログを開いたらマージするフォルダーの URL を指定しなければなりません。

マージ追跡

Subversion 1.5 は、マージ追跡機構を導入しました。あるツリーから別のツリーへ変更をマージすると、マージしたリビジョン番号を格納し、この情報を様々な異なる用途に使用します。

  • 再度同じリビジョンをマージする危険(再マージ問題)を避けられます。一度マージ済みとマークされたリビジョンは、将来のマージで、マージ範囲にそのリビジョンが含まれていてもスキップします。

  • トランクにブランチをマージする際、トランクのログの一部として、ブランチのコミットもログダイアログに表示し、変更のトレーサビリティが向上します。

  • マージダイアログの中からログダイアログを表示すると、すでにマージしたリビジョンを灰色で表示します。

  • ファイルに対して注釈履歴を表示する際、マージした人ではなく、マージされたリビジョンのオリジナル作者を表示するよう選択できます。

  • マージされたリビジョンのリストにある、実際にはマージされていないリビジョンに対して、未マージ としてマークできます。

マージを行う際、クライアントはマージ追跡情報を、svn:mergeinfo に格納します。マージをコミットする際には、サーバーはその情報をデータベースに格納し、マージやログ、注釈情報などのリクエストに適切に応答します。システムが適切に動作するには、サーバーとリポジトリ、全クライアントを確実にアップグレードしなければなりません。以前のクライアントでは、svn:mergeinfo プロパティを格納しませんし、以前のサーバーでは、新しいクライアントが要求した情報を提供できません。

マージ追跡についてそれ以外のことについては、 Subversion のMerge Tracking の説明をご覧ください。

マージ中に発生した競合の扱い

マージが常にすんなり完了するとは限りません。時には競合を起こしますし、複数の範囲をマージしている場合は、一般的に、次の範囲のマージを行う前に、競合を解決しておきたいでしょう。TortoiseSVN は、競合の解決 ダイアログを表示して、この処理の手助けをしてくれます。

図4.56 競合の解決ダイアログ

競合の解決ダイアログ


リポジトリにコミット済みの他の変更と、ローカルでの他の変更が競合した場合は、円滑にマージされている可能性が高いです。マージ可能なすべての変更がマージされます。マージ競合コールバックダイアログは、競合を解決する3つの異なる方法を提供します。

  1. テキストファイルをマージする場合、最初の2つのボタンを押すと競合しない行はそのままマージされ、競合する行については指定したバージョンが常に優先されます。ローカルを優先するを選択すると、すべての競合点でローカルの変更が選択されます。つまり、マージ元の変更よりも、ローカルの内容が優先されます。同様に、リポジトリを優先するを実行すると、すべての競合点でリポジトリの変更が選択されます。つまり、自分の作業コピーにあったものより、マージ元の変更が優先されます。簡単なようですが、競合が予想以上に多くの行を上書きしてしまい、予期しない結果をもたらすことがあります。

    マージ対象にバイナリファイルが含まれていると、そのファイルの競合を行単位にマージすることはできません。バイナリファイルの競合は、どちらのファイル全体を使うかを選択する必要があります。ローカルを優先するを実行すると、自分の作業コピー内にあるローカル版が採用され、リポジトリを優先するを実行すると、リポジトリ内のマージ元のファイルが採用されます。

  2. 通常は、自分で競合を確認して解決すると思います。この場合、マージツールが起動する際に競合の編集を選択してください。結果に問題がなければ、解決済にするをクリックしてください。

  3. 最後の選択は、解決を先延ばしにして、マージを継続することです。現在競合したファイルと、残りのマージするファイル双方に対して、その選択ができます。しかし、そのファイルにまだ変更があると、マージは完了しないでしょう。

この対話的コールバックが必要なければ、マージ進行ダイアログに 非対話的なマージ チェックボックスがあります。マージ時にこれをセットし、マージ結果に競合がある場合、ファイルに競合マークが付き、マージを継続します。マージがすべて完了したあとで、競合を解決しなければなりません。セットしない場合は、競合マークがファイルに付く前に、マージ中に競合解決を行う機会があります。ファイルが複数のマージ(複数のリビジョンがそのファイルに変更を与える)を受ける場合、影響を受ける行によって、続くマージが成功するという利点があります。しかしもちろん、マージ実行中には、コーヒーを入れにそこを離れるわけにはいきません。

機能ブランチの保守

別々のブランチで新しい機能を開発する際、機能が完成した時の再統合指針を立てると良いと思います。別の作業がトランク (trunk) で同時に進んでいる場合、時間がたつにつれて差異は深刻になり、マージを行うのは悪夢のようになります。

機能が比較的単純で、開発に時間がかからなそうであれば、機能が完成するまで全体を分けたブランチを維持し、ブランチの変更をトランクにマージするという、単純なアプローチを採用できます。マージウィザードでは、これを単純に リビジョンの範囲をマージ で、リビジョン範囲にブランチのリビジョン期間を指定して行います。

その機能に時間がかかり、トランクに対して変更を説明する必要がある場合、ブランチの同期を維持する必要があります。これは、トランクの変更点 プラス 新機能をブランチが持つように、単純にトランクへの変更を定期的にブランチへマージするということです。同期プロセスでは リビジョンの範囲をマージ を用います。機能が完成したら、ブランチを再統合する異なる2つのツリーをマージ のどちらかを用いて、trunk にマージできます。

トランクのすべての変更を機能ブランチに反映する他の(手早い)方法として、拡張コンテキストメニュー(Shiftキーを押したままファイルを右クリック)にあるTortoiseSVNすべてマージ...を使用できます。

図4.57 すべてマージダイアログ

すべてマージダイアログ


このダイアログは非常に簡単です。しなければならないことは、 「マージオプション」 にあるように、マージオプションを設定することだけです。TortoiseSVN は、マージ追跡を使用して残りを自動的に処理します。