Git - Patchファイルの作成と適用

この記事では、Gitパッチの作成方法と適用方法について説明します。

オープンソースプロジェクトに参加するとgitを使用することになり、他人からpatchファイルを渡される場合があります。では、このパッチファイルを自分のローカルのgitに適用し、うまく動作するかどうかを確認する必要があります。

1. gitに適用されたコミットのパッチファイルを作成する

次のコマンドを使用すると、最新のコミットから入力したコミットの前までのすべてのコミットのパッチを作成できます。

$ git format-patch [commit-id]

たとえば、現在のgitに以下のようなcommitが適用されています。最新のコミットは 2eadda0です。

$ git log
commit 2eadda0d1931632a4dcdcd2bce170f55dc9928a2 (HEAD -> master)

commit 53192f622e94f814f56352248fb33fc7bd017184

commit 31c06b3ce501bee7f9a784999337ec72ecdc1bb7

以下のようにパッチを作成すると、最新のコミットから 31c06b3 以前のコミットである 53192f6 までパッチを作成します。 生成されたファイルを見ると、各コミットごとにパッチファイルを作成します。

$ git format-patch 31c06b3ce501bee7f9a784999337ec72ecdc1bb7
0001-changes.patch  # => patch file for 53192f6
0002-changes2.patch # => patch file for 2eadda0

-[commit count] オプションを使うと、現在の commit から commit count 個数の以前の commit までパッチを作成できます。

$ git format-patch -[commit count]

たとえば、次のように入力すると、最近反映した2つのコミットをパッチにします。

$ git format-patch -2
0001-changes.patch
0002-changes2.patch

2. パッチファイルをローカルに適用して commit として反映

作成したパッチをgitプロジェクトに適用するには、 git am [patch file]コマンドを使用してください。

$ git am [patch file]

このコマンドは、パッチをgitに適用しながらcommitまで反映します。

パッチ適用過程でconflictがない場合は、次のログが出て完了します。

$ git am 0001-changes.patch
Applying: changes
$ git am 0002-changes2.patch
Applying: changes2

merge 処理中に conflict が発生するとエラーログが出力されます。

3. gitの変更に対するパッチの作成

修正がコミットに反映されておらず、unstaged領域に修正がある場合(git diffコマンドで表示される変更)、パッチファイルを作成することもできます。

以下のように git diff > [patch file name] コマンドでファイルを作成できます。

$ git diff > patchfile

作成されたパッチファイルの内容を確認してみると、以下の形式で見えます。

$ cat patchfile
diff --git a/patch-515db99 b/patch-515db99
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/java/hello/HelloWorld.java b/src/main/java/hello/HelloWorld.java
index 576a0c7..650be6d 100644
--- a/src/main/java/hello/HelloWorld.java
+++ b/src/main/java/hello/HelloWorld.java
@@ -4,6 +4,7 @@ public class HelloWorld {
     public static void main(String[] args) {
         Greeter greeter = new Greeter();
         String suffix = "@@@";
-        System.out.println(greeter.sayHello() + suffix);
+        String prefix = "$$$"
+        System.out.println(prefix + greeter.sayHello() + suffix);
     }
 }

4.パッチファイルをローカルに適用し、unstaged領域にのみ反映

patch -p[number] < [patch file name]コマンドでパッチファイルを適用すると、unstaged領域にのみファイルが保存され、commitに反映されません。 パッチの内容を少し変更してコミットを反映したい場合は、この方法を使用できます。

すぐ上で作成したファイルを、以下のような命令で適用できます。 (-p1はファイルパスの最初のパスを無視してパッチを適用することを意味します)

$ patch -p1 < patchfile

パッチ適用後、 git diffコマンドを入力すると、unstaged領域にのみ変更が反映されていることを確認できます。 ここで commit を反映してもよいし、もう少しコード修正をして commit を登録することができます。

$ git diff
diff --git a/patch-515db99 b/patch-515db99
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/java/hello/HelloWorld.java b/src/main/java/hello/HelloWorld.java
index 576a0c7..650be6d 100644
--- a/src/main/java/hello/HelloWorld.java
+++ b/src/main/java/hello/HelloWorld.java
@@ -4,6 +4,7 @@ public class HelloWorld {
     public static void main(String[] args) {
         Greeter greeter = new Greeter();
         String suffix = "@@@";
-        System.out.println(greeter.sayHello() + suffix);
+        String prefix = "$$$"
+        System.out.println(prefix + greeter.sayHello() + suffix);
     }
 }

4.1 -p[number] オプション

-p[number] は、パッチの中にある file path で number 個数だけのディレクトリを無視してパッチに適用するという意味です。

パッチが適用される原理を説明しながら -p[number] について説明します。

たとえば、以下の内容のパッチファイルがあります。

diff --git a/src/main/java/hello/HelloWorld.java b/src/main/java/hello/HelloWorld.java
...

そして現在の端末では、 MyProjectディレクトリの下に srcフォルダとpatchfileがあります。

../MyProject$ ls
src patchfile

この状況で以下のように patch -p1 < patchfile コマンドを入力すると、パッチファイルの a/src/main/java/hello/HelloWorld.java から先の 1 つのディレクトリを削除した src/main/java/hello/HelloWorld.java ファイルを現在のパスで探し、そのファイルに修正を適用します。

../MyProject$ patch -p1 < patchfile

patch -p2 &lt;patchfileのように入力すると、前の2つのディレクトリを削除した main/java/hello/HelloWorld.javaファイルを現在の作業ディレクトリで見つけ、パッチを適用します。ないため、ファイルが見つからないため失敗します。

したがって、現在の端末の作業ディレクトリパスとパッチファイルのパスが一致しない場合は、 -p[number]オプションを使用して調整できます。 ちなみに、 patch --help コマンドを見るとファイルパスを外すと説明されています。

$ patch --help
Usage: patch [OPTION]... [ORIGFILE [PATCHFILE]]

Input options:
  -p NUM  --strip=NUM  Strip NUM leading components from file names.

4.2 a/b path が削除されたパッチファイルの作成

git diff --no-prefix > [patchfile] コマンドは以下のように a/b path prefix が削除されたパッチファイルを生成します。

diff --git src/main/java/hello/HelloWorld.java src/main/java/hello/HelloWorld.java

このように作成されたパッチファイルを適用するときは、 -p0オプションを使用してください。

$ patch -p0 < patchfile

-p0はパッチに見えるファイルパスを省略せずに適用することを意味します。

codechachaCopyright ©2019 codechacha