Git - Patch 파일 만들기 & 적용하기

이 글에서는 Git 패치를 만드는 방법과 적용하는 방법을 알아보겠습니다.

오픈소스 프로젝트에 참여하면 git을 사용하게 되고, 다른 사람으로부터 patch 파일을 전달받는 경우가 있습니다. 그럼 이 패치 파일을 내 로컬 git에 적용하고 잘 동작하는지 확인해봐야하는데요, 어떻게 나의 로컬에 패치 파일을 적용하는지, 어떻게 내 수정사항을 패치 파일로 만드는지 알아보겠습니다.

1. git에 적용된 commit의 패치 파일 만들기

다음 명령어를 사용하면 가장 최신 commit부터 입력한 commit 이전까지의 모든 commit에 대한 패치를 만들 수 있습니다.

$ git format-patch [commit-id]

예를 들어, 현재 git에 아래와 같은 commit이 적용되어 있습니다. 가장 최신 commit은 2eadda0입니다.

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

commit 53192f622e94f814f56352248fb33fc7bd017184

commit 31c06b3ce501bee7f9a784999337ec72ecdc1bb7

아래와 같이 패치를 만들면, 가장 최신 commit에서 31c06b3 이전 commit인 53192f6까지 패치를 만듭니다. 생성된 파일을 보면 각각의 commit마다 패치 파일을 만듭니다.

$ 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]

예를 들어, 다음과 같이 입력하면 최근에 반영한 commit 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의 변경 사항에 대한 패치 만들기

수정사항이 commit으로 반영되지 않았고, 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으로 반영되진 않습니다. 패치 내용을 조금 수정하고 commit을 반영하고 싶을 때 이런 방법을 사용할 수 있습니다.

바로 위에서 만든 파일을, 아래와 같은 명령어로 적용할 수 있습니다. (-p1는 file path의 첫번째 경로를 무시하고 패치를 적용한다는 의미)

$ 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 < patchfile처럼 입력하면 앞의 2개의 디렉토리를 제거한 main/java/hello/HelloWorld.java 파일을 현재 작업 디렉토리에서 찾고 패치를 적용하게 되는데, MyProject아래에 main 디렉토리가 없기 때문에 파일을 찾을 수 없어서 실패합니다.

따라서, 현재 터미널의 작업 디렉토리 경로와 패치 파일의 경로가 맞지 않을 때 -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는 패치에 보이는 file path를 생략하지 않고 적용한다는 의미입니다.

Loading script...
codechachaCopyright ©2019 codechacha