目次


Eclipse Galileo でのパッチ機能を探る

変更の適用方法と共有方法を学ぶ

Comments

この記事では、Eclipse でのパッチ機能について、Eclipse Galileo で導入された機能を含めて説明します。この記事で説明するサンプルを利用するためには、Eclipse をインストールしておく必要があり、また SVN (Subversion) や CVS (Concurrent Versions System) などのソース・コード・リポジトリーを用意する必要があります。

問題

Eclipse IDE (Integrated development environment: 統合開発環境) を利用すると、ソース・コード管理システムを IDE に直接統合できるフィーチャーが用意されているため、チーム環境での作業が容易に行えるようになります。これらのフィーチャーを利用すると、ソースの取得や、変更内容の表示、変更内容のコミットなどができるだけではなく、コードにパッチを適用することで変更内容を扱うことができるようになります。

パッチの提供は、コードのバージョン間での変更内容を標準的な diff フォーマットの形で含むファイルの形式を使って行うことができます。適切に作成されたパッチ・ファイルには、変更されたファイルとワークスペースのファイルとの違いのみが含まれています。こうすることでパッチのサイズが小さくなり、またパッチを選択的に適用しやすくなります。

チームによる開発環境では、コード・ベースに対する変更を開発者の間で直接共有することが必要な場合があります。パッチ・ファイルを使用するシナリオとして、次のような場合があります。

  • チームの外部からの変更の場合。例えば、オープンソースのコードに関してコミュニティー内の誰かから変更が持ち込まれた場合。
  • 何らかの理由で現在のソース・ツリーに変更をコミットすることができない場合 (例えばビルドに影響する突然の変更など)。
  • 変更が複雑であり、他の変更と統合してからソース・コード管理システムにコミットする必要がある場合。

パッチ・ファイルのメリットは、E メール・メッセージやバグ・レポートの添付ファイルとして送信できることです。そうしたパッチ・ファイルをソース・コードに適用することで、変更されたコードを統合することができます。

パッチ・フォーマットの概要

Eclipse でパッチを作成する場合、パッチは unified diff フォーマットで作成されます。これはつまり、CVS や SVN から diff を作成し、それらの diff を Eclipse プロジェクトに適用できるということです。また、パッチ・ファイルには標準フォーマットが使われ、そのため容易にパッチ・ファイルを共有できるということでもあります。diff ファイルにはいくつかのフォーマットがあります (詳しくは「参考文献」を参照)。

Eclipse でのパッチの適用方法を理解する上で、パッチ・ファイルのフォーマットを理解することは重要ではありません。しかし Eclipse で使用されている diff ファイルのフォーマットの基本を理解していると、問題に対応する上で、またパッチを適用することによる効果を理解する上で役に立ちます。

例えば、リスト 1 の単純な Motorcycle クラスを見てください。この記事では、このサンプル・クラスをベースに、このクラスに変更を加え、そのパッチ・ファイルがどうなるかを後の例で見ていきます。

リスト 1. サンプルの Motorcycle クラスMotorcycle class
package com.nathangood.examples;

public class Motorcycle {

    private int cc;
    private String model;
    private String make;
    private String year;

    public String getModel() {
        return model;
    }

    public int getCc() {
        return cc;
    }

    public void setCc(int cc) {
        this.cc = cc;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getMake() {
        return make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }
}

このクラスに、リスト 2 のような単純なコンストラクターを追加します。

リスト 2. make を使用する単純なコンストラクター
    public Motorcycle(String make) {
        super();
        this.make = make;
    }

ここで、変更されたクラスとオリジナルのクラスの間の unified diff を生成すると、diff はリスト 3 のようなものになります。

リスト 3. コンストラクターを追加するためのパッチ
Index: src/com/nathangood/examples/Motorcycle.java
===================================================================
--- src/com/nathangood/examples/Motorcycle.java    (revision 3)
+++ src/com/nathangood/examples/Motorcycle.java    (working copy)
@@ -7,6 +7,11 @@
     private String make;
     private String year;
 
+    public Motorcycle(String make) {
+        super();
+        this.make = make;
+    }
+
     public String getModel() {
         return model;
     }

このパッチ・ファイルに含まれているのは、追加された新しい何行かと、その変更箇所のコンテキストを示す数行のみです。

パッチ・ファイルの、「@@ -7,6 +7,11 @@」という行に注目してください。この行は、この行の直後にある、変更の行番号と範囲を示しています。各変更行の前にあるマイナス記号 (-) またはプラス記号 (+) は、この「@@ -7,6 +7,11 @@」という行のすぐ上に記載されているファイル名に対応します。この例では、- はリビジョン 3 を示し、+ は適用するコピーを示します。

適用するコピー (このファイルの新しいバージョン) での変更は、7 行目から始まり、コンテキストを示すための行を含めて 11 行続きます。このファイルのオリジナルのバージョン (この例ではリビジョン 3) の該当箇所には 6 行しか含まれていませんでした。この新しいバージョンとオリジナルのバージョンとの違いが、前に + が付いた新しい 5 行です。

別の例を考えて見ましょう。とりあえずコンストラクターへの変更を破棄し、make 変数のアクセサー (getMake()setMake()) を削除することにします。この違いを示すパッチはリスト 4 のようになります。

リスト 4. パッチを使って要素を削除する例
Index: src/com/nathangood/examples/Motorcycle.java
===================================================================
--- src/com/nathangood/examples/Motorcycle.java    (revision 3)
+++ src/com/nathangood/examples/Motorcycle.java    (working copy)
@@ -23,14 +23,6 @@
         this.model = model;
     }
 
-    public String getMake() {
-        return make;
-    }
-
-    public void setMake(String make) {
-        this.make = make;
-    }
-
     public String getYear() {
         return year;
     }

このパッチでは、変更箇所 (そのコンテキストを含む) はリポジトリーにあるバージョン (リビジョン 3) のファイルの 23 行目から始まり、14 行続きます。新しいバージョンでこれに該当する変更箇所も 23 行目から始まりますが、ファイルから行が削除されているため、6 行にしかわたっていません。削除対象の行は - で始まります。

最後に、オリジナルのファイルへの変更について考えて見ましょう (この場合も上記の変更は破棄します)。リスト 5 では変更箇所を太字で示し、cccubicCentimeters にリネームしたことを強調しています。

リスト 5. パッチを使って要素を変更する例
Index: src/com/nathangood/examples/Motorcycle.java
===================================================================
--- src/com/nathangood/examples/Motorcycle.java    (revision 3)
+++ src/com/nathangood/examples/Motorcycle.java    (working copy)
@@ -2,7 +2,7 @@
 
 public class Motorcycle {
 
-    private int cc;
+    private int cubicCentimeters;
     private String model;
     private String make;
     private String year;
@@ -11,12 +11,12 @@
         return model;
     }
 
-    public int getCc() {
-        return cc;
+    public int getCubicCentimeters() {
+        return cubicCentimeters;
     }
 
-    public void setCc(int cc) {
-        this.cc = cc;
+    public void setCubicCentimeters(int cubicCentimeters) {
+        this.cubicCentimeters = cubicCentimeters;
     }
 
     public void setModel(String model) {

この変更を示すパッチは単純な追加や削除よりも少し高度ですが、それは変更に追加と削除が組み合わされているためです。

最初の変更箇所 (そのコンテキストを含む) は 2 行目から始まり、7 行続きます。変更後のファイルにも同じ範囲が含まれていますが、これは 1 行削除して 1 行追加した変更であるためです。cc を含む元の行は、cubicCentimeters を含む新しい行で置き換えられています。このファイルでの 2 番目の変更箇所 (そのコンテキストを含む) は、11 行目から始まり、12 行続きますが、削除した行数と追加した行数が同じなので変更後のファイルにも同じ範囲が含まれています。

単純なパッチを作成する

これでパッチ・ファイルの基本的な構造を理解できたので、Eclipse でパッチ・ファイルを作成し、別のワークスペースにある同じプロジェクトにそのパッチを適用することができます。これらの例を同じワークスペースで実行したい場合には、Eclipse のソース管理に含まれるファイルを変更します。パッチを作成し、オリジナルのバージョンのファイルに戻します。そしてそのパッチを再度適用すると、先ほどの変更が再度行われることがわかります。

オリジナルのバージョンの Motorcycle クラスを用意し、「Source (ソース)」メニューを使っていくつかのコンストラクターを生成します (リスト 6)。

リスト 6. 新しいコンストラクターの例
    public Motorcycle() {
        super();
    }
    
    public Motorcycle(int cc, String model, String make, String year) {
        super();
        this.cc = cc;
        this.model = model;
        this.make = make;
        this.year = year;
    }

Eclipse で、このプロジェクトのコンテキスト・メニュー (通常、右マウス・ボタンをクリックして表示されるメニュー) を開き、「Team (チーム)」 > 「Create Patch (パッチの作成)」 の順に選択します。

「Create Patch (パッチの作成)」ウィンドウで「Save in File System (ファイル・システムへ)」を選択し (図 1)、「Browse (参照)」をクリックし、新しいパッチ・ファイルを保存するのに適した場所を探します。場所を指定したら、「Next (次へ)」をクリックします。

図 1. 「Create Patch (パッチの作成)」画面
「Create Patch (パッチの作成)」画面の画像
「Create Patch (パッチの作成)」画面の画像

次のステップでは、「Project (プロジェクト)」(図 2) を選択し、「Finish (完了)」をクリックします。ワークスペースにある複数のプロジェクトに対するパッチを同時に作成することができます。「Project (プロジェクト)」を選択すると、Eclipse は現在のプロジェクトをパッチ用のルート・ディレクトリーに使用してパッチを作成し、他のプロジェクトでの変更を無視します。後で見直しが必要となる変更の数が減るため、パッチのスコープを現在のプロジェクトに限定した方が簡単です。

図 2. パッチのルートを選択する
パッチのルートの選択方法を示す画像
パッチのルートの選択方法を示す画像

パッチを表示する

Eclipse でパッチ・ファイルが作成されると、そのファイルを Eclipse で表示することができます。そのためには「File (ファイル)」 > 「Open File (ファイルを開く)」の順にクリックし、そのファイルを開きます。このパッチはリスト 7 のようなものです。

リスト 7. 新しいコンストラクターを追加するパッチ
Index: src/com/nathangood/examples/Motorcycle.java
===================================================================
--- src/com/nathangood/examples/Motorcycle.java    (revision 3)
+++ src/com/nathangood/examples/Motorcycle.java    (working copy)
@@ -6,7 +6,19 @@
     private String model;
     private String make;
     private String year;
-
+    
+    public Motorcycle() {
+        super();
+    }
+    
+    public Motorcycle(int cc, String model, String make, String year) {
+        super();
+        this.cc = cc;
+        this.model = model;
+        this.make = make;
+        this.year = year;
+    }
+    
     public String getModel() {
         return model;
     }

パッケージ・エクスプローラーでパッチを適用する

1 つのワークスペースしか利用できない場合には、Motorcycle.java に加えた変更を元に戻します。あるいは、オリジナルのバージョンのプロジェクト・ファイルが中にあるワークスペースに切り換えます。

オリジナルのバージョンのプロジェクトで、コンテキスト・メニューの項目から「Team (チーム)」 > 「Apply Patch (パッチの適用)」の順に選択し、「Apply Patch (パッチの適用)」ウィザードを開きます (図 3)。「File (ファイル)」を選択し、その右にある「Browse (参照)」ボタンをクリックし、「単純なパッチを作成する」で作成したパッチ・ファイルの場所を指定します。

図 3. パッチの入力を選択する
パッチの入力の選択方法を示す画像
パッチの入力の選択方法を示す画像

このパッチはプロジェクトをベースに作成したものであり、ワークスペースのルートをベースに作成したものではないため、「Apply the patch to the selected file, folder or project (選択したファイル、フォルダー、またはプロジェクトにパッチを適用します)」を選択し、リストからプロジェクトのフォルダーを選択します (図 4)。プロジェクトのフォルダーを選択したら、「Next (次へ)」をクリックします。

図 4. パッチのターゲットを選択する
パッチのターゲットの選択方法を示す画像
パッチのターゲットの選択方法を示す画像

次の画面 (図 5) では、オリジナルのバージョン (「Local Copy (パッチ済みローカル・ファイル)」) と、変更を含むバージョン (「After Patch (一致しないパッチ・セグメント)」) との比較を表示しています。比較の対象は Java™ ファイルであるため、「Java Structure Compare (Java 構造体の比較)」セクションではこのファイルの中の違いをアウトライン形式で表示しており、多少理解しやすいものになっています。

図 5. パッチの適用前に変更内容を確認する
パッチの適用前に変更内容を確認するための画像
パッチの適用前に変更内容を確認するための画像

変更内容に誤りがないことを確認したら、「Finish (完了)」をクリックします。

ここでパッチを適用したファイルを開くと、先ほど行った変更が反映されています。

Eclipse Galileo を使ってパッチを適用するための別の方法として、パッチの内容を単純に「Package Explorer (パッケージ・エクスプローラー)」ビューに貼り付けることでパッチを適用することも可能です。Eclipse Galileo をインストールしてあれば、作成したパッチを以下の方法で適用することができます。

  1. 作成したパッチ・ファイルをテキスト・エディターで開きます。
  2. ファイルの内容をクリップボードにコピーします。
  3. 「Package Explorer (パッケージ・エクスプローラー)」でプロジェクトを右クリックし、コンテキスト・メニューから「Paste (貼り付け)」を選択します。
  4. 表示されるウィザードに従って、パッチ・ファイルの内容を適用します。

パッチを適用する際のオプション

図 5 の「Review Patch (パッチのレビュー)」ステップには、パッチを適用するためのオプションがいくつかあります。先ほどの例の単純なパッチでは、「Review Patch (パッチのレビュー)」ステップのデフォルトのオプションには何も変更が必要ありませんでした。しかし場合によると、パッチの確認時に望みの結果を得るために、設定を変更しなければならない場合があります。

パッチを適用する場合、「Review Patch (パッチのレビュー)」ステップで「Patch Contents (パッチ・コンテンツ)」のリストにエラーが発生している場合があります (図 6)。エラーの発生は、パッチ・オプションをいくつか変更する必要があることを示しています。

図 6. パッチを適用する際のエラーに対処する
パッチを適用する際のエラーに対処する
パッチを適用する際のエラーに対処する

パッチの中にあるオリジナルのディレクトリー構造がパッチの適用対象のディレクトリー構造と異なる場合には、「Ignore leading path name segments (先行するパス名セグメントを無視)」オプションが役に立ちます。この動作を試してみるには、プロジェクトに対するパッチを作成し、「Apply Patch (パッチの適用)」ウィザードの「Target Resource (ターゲット・リソース)」ステップ (図 4) で「src」フォルダーを選択します。するとエラーが発生しますが (図 6)、「Ignore leading path name segments (先行するパス名セグメントを無視)」の値を「1」に変更すると、Eclipse は適切にパッチを適用します。

Show matched hunks (一致したハンクの表示)」オプションは、「Patch Contents (パッチ・コンテンツ)」のリストに行番号とコンテキストを表示します。

Fuzz factor (ファジー要素)」オプションを使うと、パッチを適用する際にコンテキスト行の一部を無視するように Eclipse に指示することができます。この記事の例で「Fuzz factor (ファジー要素)」が必要な場合を試すためには、「Package Explorer (パッケージ・エクスプローラー)」でプロジェクトを右クリックし、コンテキスト・メニューから「Source (ソース)」 > 「Sort Members (メンバーのソート)」の順にメニュー・オプションを選択することで、オリジナルのファイルをソートしてからパッチを適用します。するとコンテキスト行の一部の順序が乱れてしまうため、適切にパッチを適用するには「Fuzz factor (ファジー要素)」が必要となります。そのためには、「Guess (予測)」をクリックし、「Fuzz factor (ファジー要素)」を最大限検出できるよう Eclipse に指示するか、あるいは変更に自信を持てるまで設定を変更します。

Show Excluded (除外されたものの表示)」オプションを使うと、除外されたパッチ操作がある場合に、それらの除外されたパッチ操作を表示することができます。

Generate a .rej file for unmerged hunks (マージされないハンクとして .rej ファイルを生成)」を使うと、操作中に適用されなかったパッチ・ファイルのエントリーを保存することができます。この動作の実際を見るためには、新しいコンストラクターを含むパッチを使用します。ただしパッチを適用する前に、他のコンストラクターとは少し異なる独自のコンストラクター (リスト 8 のようなコンストラクター) を追加します。

リスト 8. ローカル・コンストラクターを追加して競合させる
    public Motorcycle() {
        // TODO:  Create some conflict with this constructor
    }

このパッチを適用しようとすると、パッチ・ファイルの中のコードと Motorcycle クラスのコードの間で競合が発生します。この競合は変更されたファイルには適用されず、代わりに Motorcycle.java.rej というファイルに書き出されます (リスト 9)。

リスト 9. 拒否されたパッチ・コード
@@ -5,6 +5,10 @@
     private String model;
     private String make;
     private String year;
+    
+    public Motorcycle() {
+        super();
+    }
 
     public String getModel() {
         return model;

リスト 9 のファイルは、パッチ・ファイルとして完全でも有効でもありません。しかし競合を最初に解決しておけば、このファイルを使って Motorcycle クラスに対するパッチを作成することができます。

Synchronize View でパッチを作成する

パッチを作成するためのもう 1 つの方法は、「Synchronize (同期化)」ビューで変更内容をコード・リポジトリーと同期させてからパッチを作成する方法です。「Synchronize (同期化)」ビューでパッチを作成するためには、「Team Synchronizing (チーム同期化)」パースペクティブを開き、図 7 の「Synchronize (同期化)」ボタンをクリックします。

図 7. 「Synchronize (同期化)」ボタン
「Synchronize (同期化)」ボタンを示す画像

「Synchronize (同期化)」ビューでプロジェクトを選択してコンテキスト・メニューを開き、「Create Patch (パッチの作成)」をクリックします。そこから先は、「単純なパッチを作成する」で説明したパッチ作成プロセスに従います。

パッチを元に戻す

「Reverse patch (パッチの反転)」は、まさに適用対象のパッチのアクションと逆のことをします。つまり、行を追加する代わりに行を削除し、パッチによって削除されるはずの行は、逆に追加されます。「Reverse patch (パッチの反転)」を使用すると、以前のバージョンのファイルからの特定の変更を元に戻したり、あるいは問題を起こしたパッチによる変更を元に戻したりすることができます。

まとめ

Eclipse のチーム・ツールを使うと、コードに対する変更を標準的な unified diff フォーマットのパッチを使って容易に共有することができます。そのため、今やチームのメンバーは、Eclipse Galileo の「Package Explorer (パッケージ・エクスプローラー)」でパッチの内容を貼り付けるという新しい方法を使って、添付ファイルとして送信されたパッチを適用することができます。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=449869
ArticleTitle=Eclipse Galileo でのパッチ機能を探る
publish-date=11032009