diff & patch — 소스 코드 차이 비교와 패치(Patch) 적용

diff는 두 파일(또는 디렉터리)의 차이를 계산하여 사람이 읽을 수 있는 형식으로 출력하는 유틸리티이며, patch는 그 diff 출력을 원본 파일에 적용하여 변경 사항을 재현하는 유틸리티입니다. 이 두 도구는 리눅스 커널 개발을 비롯한 모든 오픈소스 협업의 기본 인프라(Infrastructure)입니다. Normal/Context/Unified 세 가지 출력 형식, 디렉터리 비교, patch의 퍼지 매칭(Fuzz Matching)과 되돌리기, patchutils 보조 도구, quilt 패치 스택 관리, Git과의 연계까지 상세히 다룹니다.

전제 조건: Bash 셸 스크립팅과 Git 문서를 먼저 읽으면 diff/patch가 실제 워크플로에서 어떻게 쓰이는지 맥락을 이해하기 쉽습니다. 커널 패치(Patch) 제출 절차는 패치 제출 문서에서 상세히 다룹니다.
일상 비유: diff는 두 문서를 나란히 놓고 빨간 펜으로 다른 부분에 표시하는 것이고, patch는 그 빨간 펜 표시를 보고 원본 문서를 수정하는 것입니다. 편집자가 원고에 수정 지시를 적어 주면 저자가 그대로 반영하는 과정과 동일합니다.

핵심 요약

  • diff — 두 파일의 차이를 계산합니다. -u(Unified), -c(Context), 기본(Normal) 세 가지 출력 형식이 있으며, 커널 개발에서는 Unified 형식이 표준입니다.
  • patch — diff가 생성한 패치 파일을 원본에 적용합니다. -p 옵션으로 경로 깊이를 조절하고, -R로 되돌릴 수 있습니다.
  • Unified 형식 — ---/+++ 헤더와 @@ 헝크(Hunk) 헤더로 구성되며, -는 삭제, +는 추가, 공백은 문맥(Context) 줄입니다.
  • 디렉터리 비교 — diff -ruN으로 두 디렉터리 트리 전체를 재귀적으로 비교할 수 있습니다.
  • patchutils — filterdiff, lsdiff, interdiff, combinediff 등 패치 파일을 가공하는 보조 도구 모음입니다.

단계별 이해

  1. 차이 생성
    diff -u old.c new.c > change.patch로 변경 사항을 패치 파일로 저장합니다.
  2. 패치 검증
    patch --dry-run -p0 < change.patch로 실제 적용 전에 결과를 미리 확인합니다.
  3. 패치 적용
    patch -p0 < change.patch로 원본 파일에 변경 사항을 적용합니다.
  4. 패치 되돌리기
    patch -R -p0 < change.patch로 적용한 패치를 원래 상태로 되돌립니다.
  5. 커널 워크플로
    커널 개발에서는 git diff와 git format-patch가 diff 역할을, git apply와 git am이 patch 역할을 수행합니다.
관련 표준: IEEE Std 1003.1-2024 (POSIX.1-2024) — diff와 patch는 POSIX 표준 유틸리티입니다. GNU diffutils는 POSIX를 확장한 기능을 추가로 제공합니다.

diff 유틸리티 개요

diff는 1974년 Unix V5에서 Douglas McIlroy가 만든 유틸리티입니다. 초기에는 Hunt-McIlroy 알고리즘을 사용했으며, 이후 Eugene W. Myers가 1986년에 발표한 O(ND) 차이 알고리즘이 현대 diff 구현의 표준이 되었습니다. 이 알고리즘은 두 파일의 LCS(Longest Common Subsequence, 최장 공통 부분 수열)와 최단 편집 스크립트(Shortest Edit Script, SES)가 서로 연결된다는 사실을 이용해 최소 편집 경로를 찾습니다. GNU diffutils는 Myers 알고리즘 위에 가독성 보정 규칙을 더해 실제 패치 검토에 더 읽기 쉬운 헝크를 만듭니다.

# 기본 문법
diff [옵션] <원본 파일> <수정 파일>

# 종료 코드
# 0 — 차이 없음
# 1 — 차이 있음
# 2 — 오류 발생

처음 5분: diff와 patch 직접 해보기

개념보다 먼저 손으로 해봅니다. 원본 파일과 수정 파일을 직접 만들고, diff로 차이를 추출한 뒤 patch로 적용하고 되돌리는 전체 흐름을 5분 안에 체험할 수 있습니다.

# 1. 원본 파일 생성
cat <<'EOF' > original.txt
Hello, world.
This is line 2.
This is line 3.
EOF

# 2. 수정 파일 생성
cat <<'EOF' > modified.txt
Hello, world.
This is line 2 - modified.
This is line 3.
Added line 4.
EOF

# 3. diff 로 차이 확인 (-u: unified format)
diff -u original.txt modified.txt
# --- original.txt   2024-01-01 00:00:00.000000000 +0900
# +++ modified.txt   2024-01-01 00:00:01.000000000 +0900
# @@ -1,3 +1,4 @@
#  Hello, world.
# -This is line 2.
# +This is line 2 - modified.
#  This is line 3.
# +Added line 4.

# 4. 패치 파일로 저장 (종료 코드 1은 차이 있음 — 오류 아님)
diff -u original.txt modified.txt > hello.patch

# 5. 적용 전 검증 (--dry-run: 파일을 실제로 바꾸지 않음)
patch --dry-run -p0 < hello.patch
# checking file original.txt

# 6. 실제 적용
patch -p0 < hello.patch
# patching file original.txt

# 7. 적용 결과 확인 (차이가 없으면 아무 출력도 없음)
diff original.txt modified.txt

# 8. 패치 되돌리기 (-R: reverse)
patch -R -p0 < hello.patch
# patching file original.txt
명령어 해설
cat <<'EOF' > original.txt
히어 독(here-document)으로 여러 줄 텍스트를 파일에 씁니다. 작은따옴표 'EOF'는 내부의 변수 확장을 막습니다.
diff -u original.txt modified.txt
통합 형식(unified format)으로 두 파일의 차이를 출력합니다. ---/+++가 원본/수정 파일을 나타내고, @@ 헝크(hunk) 헤더가 변경 위치를 표시합니다.
diff -u ... > hello.patch
diff 출력을 파일로 저장합니다. 차이가 있으면 종료 코드 1이 반환되지만, 이는 오류가 아니므로 무시해도 됩니다.
patch --dry-run -p0
패치를 실제로 적용하지 않고 성공 여부만 확인합니다. 배포 전 사전 검증에 필수입니다.
patch -p0
-p0은 경로 접두사를 제거하지 않음을 의미합니다. 같은 디렉터리에서 만든 패치에 적합합니다.
patch -R -p0
-R(reverse)로 패치를 되돌립니다. original.txt가 수정 전 상태로 복원됩니다.
-p0 vs -p1: 같은 디렉터리에서 만든 패치는 -p0, git format-patch나 diff -ruNp로 만든 패치는 대부분 -p1을 씁니다. 경로 접두사 처리 규칙은 -p 경로 접두사 제거 섹션에서 자세히 다룹니다.

diff/patch 역사와 발전

diff와 patch는 50년 이상의 역사를 가진 Unix 핵심 도구입니다. 각 세대마다 출력 형식과 알고리즘이 발전하면서 현대 소프트웨어 협업의 기반이 되었습니다.

diff / patch 역사와 발전 1974 1980s 1985 1986–91 2005+ Unix V5 diff Douglas McIlroy Hunt–McIlroy LCS 방식 Normal 형식 (ed 호환) 변경 적은 파일에 효율적 BSD diff -c Context 형식 도입 문맥 줄 추가 패치 적용 신뢰도 향상 네트워크 배포 시대 대비 patch 탄생 Larry Wall 퍼지 매칭·오프셋 패치만 전송하는 혁신 Usenet 원격 협업 Unified · GNU 확립 Myers O(ND) 알고리즘 -u 형식, +/- 기호 압축 GNU diffutils 1.0 (1988) Context 대비 작은 크기 Git 통합 시대 git diff / git apply Patience / Histogram format-patch / am 버전 관리에 통합 출력 형식 진화 Normal Context Unified Git·도구
diff/patch 역사 타임라인 — 1974년 diff 탄생부터 1985년 patch, 1986년 Myers 알고리즘, Git 통합까지의 발전 과정
patch의 탄생 배경: 1985년 Larry Wall(Perl 창시자)이 patch를 만든 계기는 Usenet 뉴스그룹이었습니다. 당시 소프트웨어 업데이트를 위해 전체 소스 코드를 재배포하는 것은 네트워크 대역폭(Bandwidth) 낭비였기에, 변경 부분만 담은 diff 출력(패치)을 전송하고 수신자가 자동 적용하는 방식이 필요했습니다. 이 "패치를 보내고 적용하는" 워크플로는 오늘날 리눅스 커널의 이메일 기반 패치 리뷰 문화로 직접 이어집니다.

diff 출력 형식 비교

diff는 세 가지 주요 출력 형식을 제공합니다. 각 형식의 특성과 용도가 다르므로 상황에 맞게 선택해야 합니다.

- 삭제(Delete) + 추가(Add) 문맥(Context) ! 변경(Change) Normal 형식 (기본) diff old new 3c3 명령 구문 < old line --- > new line a = 추가  d = 삭제  c = 변경 문맥 없음, 가장 간결 ed 스크립트와 유사 Context 형식 (-c) diff -c old new *** old 2024-01-01 --- new 2024-01-02 *************** *** 1,5 **** ! changed line 문맥 줄 유지 ! = 변경, - = 삭제, + = 추가 기본 문맥 3줄 BSD 전통, 문맥 포함 Unified 형식 (-u) ★ diff -u old new --- old 2024-01-01 +++ new 2024-01-02 @@ -1,5 +1,5 @@ - old line + new line 문맥 줄 유지 - = 삭제, + = 추가 공백 접두사 = 문맥 줄 커널 개발 표준, 가장 많이 사용
세 가지 diff 출력 형식 비교 — Normal/Context/Unified의 구조와 용도

Normal 형식 (기본)

옵션 없이 diff를 실행하면 Normal 형식으로 출력됩니다. ed 스크립트(ed script)와 유사한 형태로, 문맥 줄이 없어 가장 간결하지만 사람이 읽기에는 불편합니다.

# 예제 파일 준비
cat > old.c <<'EOF'
#include <stdio.h>

int main(void) {
    printf("Hello\n");
    return 0;
}
EOF

cat > new.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    printf("Hello, %s\n", argc > 1 ? argv[1] : "World");
    return EXIT_SUCCESS;
}
EOF

# Normal diff 실행
diff old.c new.c

출력 결과:

1a2
> #include <stdlib.h>
3c4
< int main(void) {
---
> int main(int argc, char *argv[]) {
4c5
<     printf("Hello\n");
---
>     printf("Hello, %s\n", argc > 1 ? argv[1] : "World");
5c6
<     return 0;
---
>     return EXIT_SUCCESS;

Normal 형식의 명령어 구문:

구문의미예시설명
LaR추가(Append)1a2원본 1번 줄 뒤에 수정본 2번 줄 추가
FdL삭제(Delete)3,5d2원본 3~5번 줄 삭제
FcT변경(Change)3c4원본 3번 줄을 수정본 4번 줄로 변경

Context 형식 (-c)

Context 형식은 변경 지점 주변의 문맥 줄을 함께 표시하여 사람이 변경 내용을 이해하기 쉽게 만듭니다. 1980년대 BSD에서 처음 도입되었습니다.

# Context diff 생성 (기본 문맥 3줄)
diff -c old.c new.c

# 문맥 줄 수 조절 (5줄)
diff -C 5 old.c new.c

출력 결과:

*** old.c	2024-01-01 10:00:00.000000000 +0900
--- new.c	2024-01-02 10:00:00.000000000 +0900
***************
*** 1,6 ****
  #include <stdio.h>

! int main(void) {
!     printf("Hello\n");
!     return 0;
  }
--- 1,7 ----
  #include <stdio.h>
+ #include <stdlib.h>

! int main(int argc, char *argv[]) {
!     printf("Hello, %s\n", argc > 1 ? argv[1] : "World");
!     return EXIT_SUCCESS;
  }

Context 형식의 접두사 기호:

기호의미설명
(공백)문맥변경 없는 줄 — 위치 확인용
!변경원본 블록(***)과 수정본 블록(---) 양쪽에 표시
-삭제원본 블록에만 존재
+추가수정본 블록에만 존재

Unified 형식 (-u) ★

Unified 형식은 Context 형식을 개선한 것으로, 원본과 수정본을 하나의 블록으로 합쳐 표시합니다. 가독성과 공간 효율이 모두 우수하여 리눅스 커널을 비롯한 대부분의 오픈소스 프로젝트에서 표준으로 사용합니다. git diff의 기본 출력도 Unified 형식입니다.

# Unified diff 생성 (기본 문맥 3줄)
diff -u old.c new.c

# 문맥 줄 수 조절 (5줄)
diff -U 5 old.c new.c

# 파일로 저장
diff -u old.c new.c > change.patch

출력 결과:

--- old.c	2024-01-01 10:00:00.000000000 +0900
+++ new.c	2024-01-02 10:00:00.000000000 +0900
@@ -1,6 +1,7 @@
 #include <stdio.h>
+#include <stdlib.h>

-int main(void) {
-    printf("Hello\n");
-    return 0;
+int main(int argc, char *argv[]) {
+    printf("Hello, %s\n", argc > 1 ? argv[1] : "World");
+    return EXIT_SUCCESS;
 }
Unified Diff 구조 분석 ① 파일 헤더 (File Header) --- old.c 2024-01-01 10:00:00 +0900 +++ new.c 2024-01-02 10:00:00 +0900 --- 원본 파일 경로 + 타임스탬프 +++ 수정본 타임스탬프는 선택 사항 ② 헝크 헤더 (Hunk Header) @@ -1,6 +1,7 @@ ← 이 블록이 어디부터 얼마나 걸칠지 선언 git diff 는 @@ 뒤에 함수명도 표시 가능 -1,6 → 원본(old) 범위 1 = 시작 줄 번호 ,6 = 줄 수 (문맥+삭제) 원본 1번 줄부터 6줄이 이 헝크에 포함 +1,7 → 수정본(new) 범위 1 = 시작 줄 번호 ,7 = 줄 수 (문맥+추가) 수정본 1번 줄부터 7줄이 이 헝크에 포함 ※ 줄 수가 1이면 생략 가능: @@ -3 +3 @@ ③ 헝크 본문 (Hunk Body) #include <stdio.h> +#include <stdlib.h> -int main(void) { +int main(int argc, char *argv[]) { ... } 접두사 범례 공백 = 문맥 줄 (변경 없음) + = 추가된 줄 - = 삭제된 줄 patch 는 문맥 줄(공백)로 적용 위치를 정확히 찾아냅니다 전체 흐름 파일 헤더 헝크 헤더 헝크 본문 반복
Unified diff 구조 분석 — 파일 헤더(---/+++), @@ 헝크 헤더, -/+ 변경 줄, 공백 문맥 줄의 구성

동일 변경의 세 가지 출력 비교

동일한 파일 변경을 세 형식으로 출력하면 다음과 같습니다. Unified 형식은 Context 형식보다 훨씬 작으며, patch가 적용하기에 가장 효율적입니다.

# 테스트 파일: old.c
#include <stdio.h>

int main(void) {
    printf("Hello\n");
    return 0;
}

# 테스트 파일: new.c (2줄 변경: return 값 수정 + printf 메시지 변경)
#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 1;
}
# ═══ Normal 형식 (diff old.c new.c) ═══
# 가장 간결하나 문맥이 없어 patch 적용 불가
1a2
> int argc;
4c4
<     printf("Hello\n");
---
>     printf("Hello, World!\n");
5c5
<     return 0;
---
>     return 1;
# ═══ Context 형식 (diff -c old.c new.c) ═══
# 문맥 3줄 포함, 읽기 좋으나 패치 크기가 큼
*** old.c	2024-01-01 00:00:00.000000000 +0900
--- new.c	2024-01-02 00:00:00.000000000 +0900
***************
*** 1,5 ****
  #include <stdio.h>

  int main(void) {
!     printf("Hello\n");
!     return 0;
  }
--- 1,5 ----
  #include <stdio.h>

  int main(void) {
!     printf("Hello, World!\n");
!     return 1;
  }
# ═══ Unified 형식 (diff -u old.c new.c) ★ 권장 ═══
# 커널 개발 표준. 문맥 + 변경을 하나로 통합
--- old.c	2024-01-01 00:00:00.000000000 +0900
+++ new.c	2024-01-02 00:00:00.000000000 +0900
@@ -1,7 +1,7 @@
 #include <stdio.h>

 int main(void) {
-    printf("Hello\n");
+    printf("Hello, World!\n");
     return 0;
+    return 1;
 }
-
형식별 크기 비교: 위 예제에서 Context 형식은 약 20줄, Unified 형식은 약 12줄입니다. 대규모 변경에서는 차이가 더 커지며, Unified 형식은 patch 유틸리티의 기본 입력 형식이므로 별도 지정 없이도 자동 인식됩니다. 커널 커뮤니티에서는 git format-patch가 자동으로 Unified 형식을 사용합니다.

diff 주요 옵션 레퍼런스

옵션설명사용 예
-u, -U NUnified 형식 (문맥 N줄, 기본 3)diff -u old new
-c, -C NContext 형식 (문맥 N줄, 기본 3)diff -c old new
-r디렉터리 재귀 비교(Recursive)diff -r dir1/ dir2/
-N없는 파일을 빈 파일로 취급(New file)diff -ruN dir1/ dir2/
-p변경된 C 함수명 표시diff -up old.c new.c
-b공백 차이 무시(Ignore space change)diff -ub old new
-B빈 줄 차이 무시diff -uB old new
-w모든 공백 무시diff -uw old new
-i대소문자 무시diff -ui old new
--color컬러 출력 (GNU diffutils 3.4+)diff --color -u old new
-q다른 파일만 이름 표시(Brief)diff -rq dir1/ dir2/
-y나란히 비교(Side-by-side)diff -y old new
-W NSide-by-side 폭 지정diff -y -W 120 old new
--strip-trailing-crWindows CR 문자 무시diff -u --strip-trailing-cr old new
-x PAT패턴에 맞는 파일 제외(Exclude)diff -ruN -x '*.o' dir1/ dir2/
-X FILE제외 패턴 파일 지정diff -ruN -X .diffignore dir1/ dir2/
-L LABEL파일 이름 대신 라벨 표시diff -u -L a/file -L b/file old new

디렉터리 비교

커널 소스 트리처럼 대규모 디렉터리를 비교할 때는 -r(재귀)과 -N(새 파일 포함) 옵션을 함께 사용합니다.

# 커널 소스 트리 비교 (전형적인 커널 패치 생성)
diff -ruNp linux-6.8/ linux-6.8-patched/ > my-kernel-fix.patch

# 옵션 분해:
#   -r   재귀 비교
#   -u   Unified 형식
#   -N   새 파일을 빈 파일 대비로 포함
#   -p   변경된 C 함수명 표시

# 바이너리 파일과 빌드 산출물 제외
diff -ruNp -x '*.o' -x '*.ko' -x '.git' -x '.config' \
    linux-6.8/ linux-6.8-patched/ > my-kernel-fix.patch

# 제외 패턴을 파일로 관리
cat > .diffignore <<'EOF'
*.o
*.ko
*.mod
*.cmd
.git
.config
.tmp_versions
EOF
diff -ruNp -X .diffignore linux-6.8/ linux-6.8-patched/ > my-kernel-fix.patch

# 어떤 파일이 다른지만 빠르게 확인
diff -rq linux-6.8/ linux-6.8-patched/
# 출력 예:
# Files linux-6.8/drivers/net/foo.c and linux-6.8-patched/drivers/net/foo.c differ
# Only in linux-6.8-patched/drivers/net: bar.c

diff 고급 사용법

Side-by-Side 비교 (-y)

# 터미널에서 나란히 비교
diff -y -W 120 old.c new.c

# 출력 기호:
#   (빈칸)  동일한 줄
#   |       변경된 줄
#   <       원본에만 존재 (삭제)
#   >       수정본에만 존재 (추가)

# 다른 줄만 표시
diff -y --suppress-common-lines old.c new.c

통계 정보

# diffstat: diff 출력의 통계 요약 (별도 패키지)
diff -ruNp linux-6.8/ linux-6.8-patched/ | diffstat
# 출력 예:
#  drivers/net/foo.c   |   15 +++++++++------
#  drivers/net/bar.c   |   42 ++++++++++++++++++++++++++++++++++++++++++
#  include/net/baz.h   |    3 ++-
#  3 files changed, 50 insertions(+), 7 deletions(-)

# git에서의 유사 기능
git diff --stat

특수 비교

# 프로세스(Process) 치환으로 명령 출력 비교
diff <(sort file1.txt) <(sort file2.txt)

# 표준 입력과 파일 비교 (- 는 stdin)
cat modified.c | diff original.c -

# 바이너리 파일 비교 (16진수 덤프)
diff <(xxd file1.bin) <(xxd file2.bin)

# 커널 .config 비교 전용 도구
scripts/diffconfig .config.old .config
# 출력 예: CONFIG_DEBUG_INFO n -> y

diff3 — 3-way 머지

diff3는 세 파일을 동시에 비교하여 3방향 머지(3-way merge)를 수행합니다. 두 사람이 같은 원본에서 각자 수정한 경우, 양쪽 변경을 합칠 때 핵심적인 도구입니다. Git의 내부 머지 엔진도 이 3-way 머지 원리를 사용합니다.

diff3 — 3-way 머지 개념 공통 원본 (base) original.c 두 사람이 같은 원본에서 각자 수정 수정본 A (mine) my-version.c 수정본 B (yours) your-version.c diff3 -m mine base yours ① 양쪽 변경이 겹치지 않으면 자동 머지 ② 겹치면 <<<<<<< 충돌 마커 삽입 종료 코드 0(성공) / 1(충돌) 자동 머지 → merged.c 충돌 없이 양쪽 변경 반영 충돌 마커 → 수동 해결 <<<<<<< / ||||||| / ======= / >>>>>>>
diff3 3-way 머지 개념 — MINE/OLDER/YOURS 세 입력과 머지 결과의 관계
# diff3 기본 문법: diff3 MINE OLDER YOURS
# (인수 순서 주의: 내 수정본, 공통 원본, 상대 수정본)
diff3 my-version.c original.c your-version.c

# 3-way 머지 결과를 stdout으로 출력 (-m)
diff3 -m my-version.c original.c your-version.c > merged.c

# 충돌이 없으면 종료 코드 0, 충돌 시 1
diff3 -m my.c base.c yours.c > merged.c
echo $?   # 0=성공, 1=충돌, 2=오류

# 충돌 발생 시 merged.c 내용:
# <<<<<<< my-version.c
#     printf("Hello, World!\n");
# ||||||| original.c
#     printf("Hello\n");
# =======
#     printf("Hi there\n");
# >>>>>>> your-version.c

# ed 스크립트 형식 출력 (기본)
diff3 my.c base.c yours.c
# 출력 기호:
# ==== — 세 파일 모두 다름 (충돌)
# ====1 — 파일 1(mine)만 다름
# ====2 — 파일 2(base)만 다름 → A와 B가 동일하게 수정
# ====3 — 파일 3(yours)만 다름

# 겹치지 않는 변경만 자동 적용, 충돌은 표시 (-A 옵션)
diff3 -A my.c base.c yours.c

# 실전: 두 커널 패치가 같은 파일을 수정한 경우
# 1. base를 기준으로 각각 패치 적용된 파일 준비
cp base.c mine.c && patch mine.c < patch-a.diff
cp base.c yours.c && patch yours.c < patch-b.diff
# 2. 3-way 머지
diff3 -m mine.c base.c yours.c > merged.c
# 3. 충돌 확인
grep -c '<<<<<<<' merged.c   # 0이면 충돌 없음

sdiff — 대화형 나란히 비교/머지

sdiff는 두 파일을 나란히(Side-by-side) 표시하며, -o 옵션으로 대화형 머지가 가능합니다.

# 나란히 비교 (diff -y와 유사)
sdiff old.c new.c

# 대화형 머지: 차이가 있는 부분마다 선택
sdiff -o merged.c old.c new.c
# 프롬프트에서:
#   l — 왼쪽(old) 선택
#   r — 오른쪽(new) 선택
#   e — 에디터로 직접 편집
#   el — 왼쪽을 에디터로 편집
#   er — 오른쪽을 에디터로 편집
#   eb — 양쪽을 에디터로 편집
#   s — 공통 줄 무음(표시 안 함)
#   v — 공통 줄 표시 (verbose)
#   q — 종료

# 폭 지정
sdiff -w 160 old.c new.c

# 공백 무시
sdiff -b old.c new.c

# 출력 기호:
# (빈칸)  동일
# |       변경
# <       왼쪽에만 존재 (삭제)
# >       오른쪽에만 존재 (추가)

cmp — 바이트 단위 비교

cmp는 두 파일을 바이트 단위로 비교합니다. diff가 텍스트 줄 단위 비교라면, cmp는 바이너리 파일을 포함한 모든 파일의 바이트 수준 비교 도구입니다.

# 기본: 첫 번째 다른 바이트 위치만 출력
cmp file1 file2
# file1 file2 differ: byte 1024, line 8

# 동일하면 출력 없이 종료 코드 0
cmp -s file1 file2 && echo "동일" || echo "다름"

# 스크립트에서 파일 동일성 검사 (가장 흔한 용도)
if cmp -s before.bin after.bin; then
    echo "변경 없음"
else
    echo "파일이 변경됨"
fi

# 모든 다른 바이트 나열 (-l)
cmp -l file1 file2
# 출력: 바이트_위치  8진수_file1  8진수_file2
#    1024 101 142
#    1025 157 163

# 처음 N바이트만 비교
cmp -n 512 file1 file2

# 바이트 오프셋 건너뛰기
cmp -i 1024 file1 file2          # 양쪽 모두 1024바이트 건너뜀
cmp -i 512:1024 file1 file2      # file1은 512, file2는 1024 건너뜀

# 커널 빌드 결과 검증: 동일한 .config로 빌드한 vmlinux 비교
cmp -s vmlinux.old vmlinux.new && echo "재현 가능 빌드 성공"

# diff vs cmp 차이점:
# diff — 텍스트 줄 단위, 차이 내용 표시, 패치 생성 가능
# cmp  — 바이트 단위, 위치만 표시, 바이너리 안전, 더 빠름

patch 유틸리티 개요

patch는 Larry Wall이 1985년에 만든 유틸리티로, diff가 생성한 차이 파일(패치)을 원본에 적용합니다. 원본 파일이 패치 생성 시점과 약간 다르더라도 퍼지 매칭(Fuzz Matching)과 오프셋(Offset) 조정(Offset Adjustment)으로 유연하게 적용할 수 있는 것이 핵심 강점입니다.

patch 적용 흐름 원본 파일 old.c 적용 대상 패치 파일 change.patch diff 출력 patch ① 헝크 헤더 파싱 ② 문맥 줄 매칭 ③ 오프셋 / 퍼지 조정 ④ 적용 여부 결정 각 헝크를 순서대로 처리 패치의 줄 번호는 참고용 문맥 일치? 성공 — 적용 정확히 일치 · 오프셋/퍼지 조정 수정된 파일 new.c 패치가 반영된 최종 결과 실패 — reject 해당 헝크는 적용하지 않음 old.c.rej 거부 헝크 저장 · 수동 해결 필요
patch 적용 흐름 — 패치 입력 → 헝크 매칭 → 적용 성공 또는 .rej 거부 파일 생성

패치 파일 해부 — 멀티 파일 · 멀티 헝크

실제 커널 패치는 여러 파일에 걸쳐 여러 헝크(Hunk)를 포함합니다. 아래는 2개 파일, 총 3개 헝크로 구성된 패치의 전체 구조입니다.

패치 파일 구조 해부 범례: --- 원본 경로 +++ 수정본 경로 @@ 헝크 헤더 문맥 줄(앵커) + 추가 줄 - 삭제 줄 파일 1 · drivers/net/ethernet/intel/e1000e/netdev.c 헤더 + 헝크 2개 --- a/drivers/net/ethernet/intel/e1000e/netdev.c 원본 파일 경로 · a/ 접두사(패치 전) +++ b/drivers/net/ethernet/intel/e1000e/netdev.c 수정본 파일 경로 · b/ 접두사(패치 후) 헝크 1 @@ -127,7 +127,9 @@ static int e1000_open(struct net_device *netdev) 원본 127줄 부근 7줄 → 수정본 127줄 부근 9줄 struct e1000_adapter *adapter = netdev_priv(netdev); int err; + if (!adapter) { + return -EINVAL; + } 헝크 2 @@ -245,6 +247,8 @@ static void e1000_down(struct net_device *netdev) 같은 파일의 다른 위치 · 줄 번호는 헝크 1만큼 시프트 e1000_clean_all_tx_rings(adapter); e1000_clean_all_rx_rings(adapter); + adapter->flags &= ~FLAG_IS_UP; + netdev_info(netdev, "device down\n"); 파일 2 · drivers/net/ethernet/intel/e1000e/e1000.h 헤더 + 헝크 1개 --- a/drivers/net/ethernet/intel/e1000e/e1000.h 한 패치에 여러 파일 포함 가능 +++ b/drivers/net/ethernet/intel/e1000e/e1000.h 헝크 1 @@ -89,6 +89,7 @@ struct e1000_adapter { 정의(구조체/함수) 헤더 줄을 문맥으로 사용 unsigned long flags; +#define FLAG_IS_UP BIT(16) struct work_struct reset_task; 이 패치 ≈ 2개 파일 × 3개 헝크 · 6줄 추가, 0줄 삭제 | patch -p1 적용 시 a/ 접두사 자동 제거
멀티 파일·멀티 헝크 패치 구조 — 2개 파일, 3개 헝크로 구성된 실제 커널 패치의 전체 구조

퍼지 매칭 동작 시각화

원본 파일이 패치 생성 시점과 달라졌을 때, patch는 문맥 줄을 기준으로 올바른 위치를 탐색합니다. 아래 다이어그램은 오프셋(Offset)과 퍼지(Fuzz)가 어떻게 동작하는지 보여줍니다.

patch의 매칭 전략: 정확 → 오프셋 → 퍼지 → 실패 1 정확 매칭 헝크 줄 번호 위치에서 문맥 3줄이 정확히 일치 succeeded at 127. (offset 0) 2 오프셋 탐색 그 위치에서 실패하면 위·아래 줄로 후보 확장 succeeded at 142. (offset +15) 3 퍼지 매칭 오프셋으로도 실패하면 문맥 상하 N줄을 무시 succeeded with fuzz 2. (offset +15) 4 실패 (Reject) 모든 탐색이 실패하면 .rej 생성, 수동 해결 Hunk #1 FAILED at 127. 실패 시 실패 시 실패 시 patch가 매칭하려는 헝크 문맥 (기본 3줄) + 변경 줄 문맥 줄 1 (상단) 문맥 줄 2 (중간) 문맥 줄 3 (하단) +변경 내용 문맥 줄은 패치 적용의 앵커(기준선) 역할을 합니다. 퍼지 팩터(F)별 문맥 줄 매칭 조건 F 0 퍼지 없음 매칭 매칭 매칭 문맥 3줄 모두 일치해야 성공합니다. → 커널 개발에서 권장하는 가장 안전한 옵션입니다. F 1 상하 1줄 무시 무시 매칭 무시 상단/하단 문맥 1줄은 달라도 허용됩니다. 중간 문맥 줄은 반드시 일치해야 합니다. F 2 상하 각 1줄 무시 (기본값) 무시 매칭 무시 기본 최대 퍼지 팩터. 유연하지만 → 잘못된 위치에 적용될 위험이 커집니다. ⚠ 퍼지 팩터가 클수록 잘못된 위치에 패치가 적용될 위험이 증가합니다. ✓ 커널 개발에서는 -F 0 을 권장하며, 실패 시에는 패치를 새로 만들어 적용합니다.
patch 매칭 4단계 — 파일명 매칭 → 헝크 위치 탐색 → 오프셋/퍼지 조정 → 적용

patch 기본 사용법

# 패치 적용 기본
patch < change.patch                  # 패치 파일의 헤더에서 파일명 자동 인식
patch -p0 < change.patch              # 경로에서 0단계 제거
patch -p1 < change.patch              # 경로에서 1단계 제거 (가장 흔함)

# -p 옵션 이해: 경로 접두사 제거 단계
# 패치 헤더: --- a/drivers/net/foo.c
#   -p0 → a/drivers/net/foo.c (전체 경로 사용)
#   -p1 → drivers/net/foo.c   (a/ 제거) ★ 커널 패치 표준
#   -p2 → net/foo.c           (a/drivers/ 제거)

# 대상 파일 직접 지정
patch old.c < change.patch

# 대상 디렉터리 변경
patch -d /usr/src/linux -p1 < kernel-fix.patch

-p 옵션 (경로 접두사 제거) 상세

-p 옵션은 패치를 올바른 파일에 적용하기 위해 경로 접두사를 제거하는 핵심 옵션입니다. 이 옵션을 잘못 지정하면 파일을 찾지 못해 패치가 실패합니다.

-p 옵션에 따른 경로 해석 제거됨 유지 (패치가 실제 사용하는 경로) 패치 헤더 --- a/drivers/net/ethernet/intel/e1000e/netdev.c -p0 전체 경로 그대로 a / drivers / net / ethernet / intel / e1000e / netdev.c 최종 경로 → a/drivers/net/ethernet/intel/e1000e/netdev.c -p1 ★ 커널 표준 — 접두사 1단계 제거 a drivers / net / ethernet / intel / e1000e / netdev.c 최종 경로 → a/drivers/net/ethernet/intel/e1000e/netdev.c -p2 처음 2단계 제거 a drivers / net / ethernet / intel / e1000e / netdev.c 최종 경로 → a/drivers/net/ethernet/intel/e1000e/netdev.c -p3 처음 3단계 제거 a drivers / net / ethernet / intel / e1000e / netdev.c 최종 경로 → a/drivers/net/ethernet/intel/e1000e/netdev.c 주의: -p 옵션 없이 patch를 실행하면 전체 경로에서 마지막 파일명만 사용해 현재 디렉터리에서 파일을 찾습니다.
patch -p 옵션과 경로 접두사 제거 — 슬래시 단위로 a/ 디렉터리를 제거하는 과정
# 커널 소스 루트에서 패치 적용 (가장 일반적)
cd /usr/src/linux
patch -p1 < /path/to/kernel-fix.patch

# diff -ruNp 로 만든 패치 (경로에 디렉터리명 포함)
# --- linux-6.8/drivers/net/foo.c
# +++ linux-6.8-patched/drivers/net/foo.c
cd linux-6.8
patch -p1 < ../kernel-fix.patch    # linux-6.8/ 접두사 제거

받은 패치의 -p 값 결정하기

패치 파일을 받았을 때 적절한 -p 값을 고르지 못하면 "can't find file to patch" 오류가 발생합니다. 헤더의 경로를 직접 확인하는 세 가지 패턴을 알면 대부분의 경우를 처리할 수 있습니다.

# 패치 헤더를 먼저 확인한다 (--- 줄의 경로가 핵심)
head -5 received.patch

# 패턴 A — git format-patch / git diff 출력
# --- a/drivers/net/foo.c
# +++ b/drivers/net/foo.c
# → a/ 또는 b/ 접두사 1단계 제거 → 커널 소스 루트에서 -p1

# 패턴 B — diff -ruNp 출력 (소스 트리 디렉터리명 포함)
# --- linux-6.8/drivers/net/foo.c
# +++ linux-6.8-new/drivers/net/foo.c
# → 최상위 디렉터리명 1단계 제거 → linux-6.8/ 안에서 -p1

# 패턴 C — 같은 디렉터리에서 생성된 패치
# --- original.txt
# +++ modified.txt
# → 경로 접두사 없음 → 해당 디렉터리에서 -p0

# lsdiff 로 한 번에 확인 (patchutils 패키지)
lsdiff -p1 received.patch
# drivers/net/foo.c   ← -p1 적용 후 남는 경로가 실제 파일과 일치하면 정답
판단 기준 해설
패턴 A: a/ · b/ 접두사
git format-patch와 git diff가 자동으로 붙이는 접두사입니다. 항상 1단계이므로 -p1이 정답입니다.
패턴 B: 디렉터리명 접두사
diff -ruNp linux-6.8/ linux-6.8-new/처럼 소스 트리 바깥에서 실행할 때 생깁니다. 커널 소스 루트 안으로 이동한 뒤 -p1로 적용하면 첫 번째 경로 요소가 제거되어 맞아떨어집니다.
패턴 C: 경로 없음
같은 디렉터리에서 diff -u file1 file2로 만든 패치입니다. 파일명만 있으므로 -p0을 씁니다.
lsdiff -p1
patchutils 패키지의 도구입니다. 지정한 -p 값을 적용했을 때 남는 경로 목록을 출력하므로, 현재 디렉터리 기준으로 경로가 올바른지 즉시 확인할 수 있습니다.

patch 주요 옵션 레퍼런스

옵션설명사용 예
-p NUM경로 접두사 NUM단계 제거patch -p1 < fix.patch
-R패치 되돌리기(Reverse)patch -R -p1 < fix.patch
--dry-run실제 변경 없이 시뮬레이션patch --dry-run -p1 < fix.patch
-d DIR대상 디렉터리 지정patch -d /usr/src/linux -p1 < fix.patch
-b백업 파일 생성 (.orig)patch -b -p1 < fix.patch
-V STYLE백업 파일 명명법patch -b -V numbered -p1 < fix.patch
-o FILE결과를 별도 파일에 저장patch -o new.c old.c < fix.patch
-F NUM퍼지 팩터(Fuzz factor) 설정patch -F 3 -p1 < fix.patch
-l느슨한 공백 매칭(Loose matching)patch -l -p1 < fix.patch
--verbose상세 출력patch --verbose -p1 < fix.patch
-s조용한 모드(Silent)patch -s -p1 < fix.patch
-f질문 없이 강제 적용(Force)patch -f -p1 < fix.patch
-E패치 후 빈 파일 삭제patch -E -p1 < fix.patch
--merge충돌 시 merge 마커 삽입 (GNU patch 2.7+)patch --merge -p1 < fix.patch

퍼지 매칭과 오프셋 조정

patch가 강력한 이유는 원본 파일이 패치 생성 시점과 달라도 유연하게 적용할 수 있기 때문입니다.

오프셋 조정 (Offset)

헝크의 줄 번호가 맞지 않으면 patch는 문맥 줄을 기준으로 위아래로 검색하여 올바른 위치를 찾습니다.

# 오프셋이 발생하면 patch가 알려줍니다
patch -p1 < fix.patch
# 출력:
# patching file drivers/net/foo.c
# Hunk #1 succeeded at 127 (offset 15 lines).
# Hunk #2 succeeded at 245 with fuzz 1 (offset 22 lines).

퍼지(Fuzz)

문맥 줄이 일부 다를 때 patch는 퍼지 팩터(Fuzz Factor)만큼 문맥 줄을 무시하고 매칭을 시도합니다. 기본 퍼지 팩터는 2입니다.

# 퍼지 없이 엄격하게 적용 (커널 개발 권장)
patch -F 0 -p1 < fix.patch

# 퍼지 팩터를 3으로 늘려 느슨하게 적용
patch -F 3 -p1 < fix.patch
주의: 퍼지 매칭은 잘못된 위치에 패치를 적용할 위험이 있습니다. 커널 개발에서는 -F 0으로 엄격하게 적용하고, 실패 시 패치를 재생성하는 것이 안전합니다.

거부(Reject) 파일 처리

patch가 헝크를 적용하지 못하면 .rej 파일을 생성합니다.

# 패치 적용 실패 시
patch -p1 < fix.patch
# 출력:
# patching file drivers/net/foo.c
# Hunk #1 FAILED at 42.
# 1 out of 3 hunks FAILED -- saving rejects to file drivers/net/foo.c.rej

# .rej 파일 확인 → 수동으로 해결
cat drivers/net/foo.c.rej
# 내용: 적용 실패한 헝크가 그대로 들어 있음
# → 해당 부분을 수동으로 편집해야 합니다

# 정리: .rej와 .orig 파일 제거
find . -name '*.rej' -o -name '*.orig' | xargs rm -f

패치 되돌리기 (-R)

# 적용한 패치를 되돌리기
patch -R -p1 < fix.patch

# 되돌리기 전 시뮬레이션
patch -R --dry-run -p1 < fix.patch

# 이미 적용된 패치를 다시 적용하려고 하면?
patch -p1 < fix.patch
# 출력:
# Reversed (or previously applied) patch detected!  Assume -R? [n]
# → patch가 자동으로 이미 적용되었음을 감지합니다

백업과 안전한 적용

# 백업 파일 생성 후 적용
patch -b -p1 < fix.patch
# drivers/net/foo.c.orig 생성 (원본 백업)

# 번호 붙은 백업
patch -b -V numbered -p1 < fix.patch
# drivers/net/foo.c.~1~ 생성

# 안전한 적용 패턴: dry-run → 백업 → 적용
patch --dry-run -p1 < fix.patch && \
patch -b -p1 < fix.patch

# 여러 패치를 순서대로 적용
for p in 0001-*.patch 0002-*.patch 0003-*.patch; do
    echo "적용 중: $p"
    patch -p1 < "$p" || { echo "실패: $p"; exit 1; }
done

실전 워크플로

커널 패치 워크플로: diff/patch vs Git 전통적 방법 (diff/patch) 현대적 방법 (Git) 패치 파일 (Unified Diff 형식) 전통적 diff와 Git 모두 동일한 형식으로 생성 · 적용 git apply = patch + 검증 1. 소스 트리 복사 cp -a 원본 / 복사본 2. 코드 수정 vim .../netdev.c 3. diff -ruNp 패치 파일 생성 4. 메일로 패치 전송 mutt / git-email 5. patch -p1 적용 적용 + 오류 검증 1. git clone / branch 작업 브랜치 생성 2. 코드 수정 & 커밋 git commit -s 3. git format-patch 커밋 → 패치 파일 4. git send-email 패치 메일 발송 5. git am / git apply 적용 + 커밋 생성
커널 패치 워크플로 — 전통적 diff/patch 방식과 Git 기반 방식의 병행 구조

커널 패치 생성/적용 예제

# ===== 전통적 방법: diff/patch =====

# 1. 소스 트리 복사
cp -a linux-6.8 linux-6.8-fix
cd linux-6.8-fix

# 2. 코드 수정
vim drivers/net/ethernet/intel/e1000e/netdev.c

# 3. 패치 생성
cd ..
diff -ruNp linux-6.8/ linux-6.8-fix/ > fix-e1000e-null-deref.patch

# 4. 패치 검증 (다른 머신에서)
cd linux-6.8
patch --dry-run -p1 < ../fix-e1000e-null-deref.patch

# 5. 패치 적용
patch -p1 < ../fix-e1000e-null-deref.patch


# ===== 현대적 방법: Git =====

# 1. 브랜치에서 작업
git checkout -b fix/e1000e-null-deref v6.8

# 2. 코드 수정 & 커밋
vim drivers/net/ethernet/intel/e1000e/netdev.c
git add -p
git commit -s   # Signed-off-by 포함

# 3. diff 확인 (Unified 형식)
git diff v6.8..HEAD

# 4. 패치 파일 생성 (커밋 메타데이터 포함)
git format-patch -1 HEAD
# → 0001-e1000e-fix-null-deref-in-netdev-open.patch

# 5. 패치 적용 (수신자)
git apply --check 0001-*.patch   # 검증만
git am 0001-*.patch              # 적용 + 커밋 생성

Git의 diff/patch 관련 명령

Git 명령대응하는 전통 명령설명
git diffdiff -u작업 디렉터리 변경 사항 표시
git diff --cacheddiff -u스테이징된 변경 사항 표시
git diff HEAD~3..HEADdiff -ruNp최근 3개 커밋의 차이
git format-patchdiff -ruNp커밋을 이메일 형식 패치로 변환
git applypatch -p1패치 파일 적용 (커밋 없음)
git ampatch -p1 + 커밋이메일 형식 패치 적용 + 커밋 생성
git diff --statdiff | diffstat변경 통계 요약
git diff -wdiff -uw공백 무시 비교
git diff --color-words—단어 단위 차이 하이라이트
# git diff 유용한 옵션들
git diff --stat                      # 변경 파일 목록과 통계
git diff --name-only                 # 변경 파일명만
git diff --name-status               # 파일명 + 상태 (M/A/D/R)
git diff -w                          # 공백 무시
git diff --color-words               # 단어 단위 하이라이트
git diff --word-diff                 # 단어 단위 차이 [{+추가+}] [-삭제-]
git diff --ignore-blank-lines        # 빈 줄 차이 무시
git diff -U10                        # 문맥 10줄로 확장
git diff -- '*.c' '*.h'              # 특정 파일 패턴만
git diff --diff-filter=M             # 수정된 파일만

# git apply 옵션
git apply --check patch.diff         # 적용 가능 여부만 검사
git apply --stat patch.diff          # 통계만 표시
git apply -3 patch.diff              # 3-way 머지 시도
git apply --reject patch.diff        # 실패 헝크를 .rej로 저장
git apply --whitespace=fix patch.diff # 공백 문제 자동 수정

이메일 기반 패치 워크플로

리눅스 커널 커뮤니티는 이메일 기반 코드 리뷰를 사용합니다. GitHub/GitLab의 Pull Request 대신, 패치를 이메일로 linux-kernel@vger.kernel.org에 보내고 Maintainer가 git am으로 적용하는 방식입니다. 이 워크플로를 이해하는 것이 커널 개발의 핵심 진입 장벽 중 하나입니다.

커널 패치 이메일 워크플로 개발자 (Author) ① git checkout -b feature ② 코드 수정 & git commit -s ③ scripts/checkpatch.pl 검증 ④ git format-patch -v2 ⑤ scripts/get_maintainer.pl 확인 ⑥ git send-email 로 전송 git send-email (SMTP 서버 경유) Maintainer / 리뷰어 ⑦ 메일링 리스트 수신 ⑧ 코드 리뷰 (Reply) ⑨ 리뷰 결과 전달 최종 적용 ⑩ pull / git am ⑪ next / -mm 테스트 승인 리뷰 통과 시 리뷰 피드백 반영 → v2 패치 재전송 커널 커뮤니티 주요 이메일 주소 • linux-kernel@vger.kernel.org — 전체 커널 메일링 리스트 • netdev@vger.kernel.org — 네트워크 서브시스템 • linux-wireless@lists.infradead.org — 무선 • dri-devel@lists.freedesktop.org — DRM/그래픽 • stable@vger.kernel.org — stable 커널 백포트
이메일 기반 커널 패치 워크플로 — 개발자 → 메일링 리스트 → Maintainer의 git am 적용까지의 경로

git send-email 설정 & 사용

# git send-email 설치
sudo apt install git-email           # Debian/Ubuntu
sudo dnf install perl-Email-Send- Gmail  # Fedora

# ~/.gitconfig 설정
git config --global sendemail.smtpServer smtp.gmail.com
git config --global sendemail.smtpEncryption tls
git config --global sendemail.smtpServerPort 587
git config --global sendemail.smtpUser your@email.com
git config --global sendemail.chainReplyTo false
git config --global sendemail.thread true

# 패치 시리즈 전송 (가장 일반적인 방법)
git send-email --to maintainer@kernel.org \
               --cc linux-kernel@vger.kernel.org \
               0*.patch

# --dry-run으로 먼저 확인 (전송 없이 확인만)
git send-email --dry-run 0*.patch

# Cover Letter 포함 시리즈 전송
git format-patch --cover-letter -3 HEAD
git send-email --to maintainer@kernel.org 0000-cover-letter.patch 0*.patch

# versioned 패치 (v2, v3)
git format-patch -v2 --cover-letter HEAD~3
# → v2-0000-cover-letter.patch, v2-0001-*.patch, ...
git send-email --to maintainer@kernel.org v2-0*.patch
이메일 설정 주의사항:
  • Plain text 필수 — HTML 이메일은 커뮤니티에서 거부됩니다. MUA(Mail User Agent) 설정에서 Plain text 모드를 사용하세요.
  • 줄 바꿈(Wrapping) 금지 — 이메일 클라이언트가 72자를 자동 줄바꿈하면 패치가 깨집니다. sendemail.chainReplyTo = false를 설정하세요.
  • MUA 선택 — 커뮤니티에서 권장하는 MUA: git send-email(권장), neomutt, alpine. Thunderbird는 설정이 까다롭습니다.
  • SMTP 비밀번호 — Gmail은 앱 비밀번호(App Password)가 필요합니다. 일반 비밀번호로는 인증 실패합니다.

b4 — 커널 패치 수집 및 관리

b4(b4)는.kernel.org 커널 메일링 리스트에서 패치를 자동으로 수집하고, git am으로 적용할 수 있는 형식으로 변환하는 도구입니다. 커널 커뮤니티에서 패치를 받을 때 git am 대신 b4를 사용하면 이메일 인코딩, 멀티파트(Multipart), Signature 등 처리가 훨씬 간편해집니다.

# b4 설치
pip3 install b4                    # Python 패키지
sudo apt install b4                 # Debian/Ubuntu 24.04+

# 이메일 주소 설정 (~/.b4.conf 또는 환경 변수)
export B4_FROM="Your Name <your@email.com>"

# 메일링 리스트에서 커밋 메시지로 패치 다운로드
b4 am 20240101123456.12345-1-author@kernel.org
# → ⟨author⟩-cover-0.patch, ⟨author⟩-1.patch, ... 생성

# 커버 레터 포함 시리즈 다운로드
b4 am --cover-letter 20240101123456.12345-1-author@kernel.org

# 다운로드한 패치를 Git에 적용
git am *.patch                     # 커밋 포함 적용
git apply --check *.patch          # 검증만

# 특정 커밋의 패치 시리즈 가져오기 (message-id 사용)
b4 am <message-id@kernel.org>

# b4 mbox — 메일 mbox 형식으로 저장
b4 mbox 20240101123456.12345-1-author@kernel.org

# b4 prep — 리뷰용 브랜치 준비
b4 prep 20240101123456.12345-1-author@kernel.org
b4 vs 수동 이메일 다운로드: 수동으로 mutt/alpine에서 패치를 저장하는 대신 b4 am을 사용하면:
  • 이메일 인코딩(Quoted-Printable, Base64) 자동 처리
  • 멀티파트 이메일의 본문 본문(Payload) 자동 추출
  • PGP/GPG 서명 자동 검증
  • 커버 레터와 패치를 올바른 순서로 정렬
  • git am 바로 적용 가능한 .patch 파일 생성
커널 커뮤니티에서 b4 사용은 점점 표준이 되고 있습니다.

patchutils — 패치 파일 가공 도구

patchutils 패키지는 패치 파일을 분석하고 가공하는 유틸리티 모음입니다.

# 설치
sudo apt install patchutils       # Debian/Ubuntu
sudo dnf install patchutils       # Fedora/RHEL

filterdiff — 패치 필터링

# 특정 파일의 변경만 추출
filterdiff -i '*/drivers/net/*' big-patch.patch > net-only.patch

# 특정 파일 제외
filterdiff -x '*/Documentation/*' big-patch.patch > code-only.patch

# 특정 헝크만 추출 (파일의 N번째 헝크)
filterdiff --hunks=1,3 -i '*/foo.c' big-patch.patch

# 패턴으로 필터링 (정규표현식)
filterdiff -I 'include.*net' big-patch.patch

lsdiff — 패치 파일 목록

# 패치에 포함된 파일 목록
lsdiff kernel-fix.patch
# 출력:
# drivers/net/foo.c
# drivers/net/bar.c
# include/net/baz.h

# 경로 접두사 제거 (patch -p1과 동일)
lsdiff -p1 kernel-fix.patch

# 통계 정보와 함께 표시
lsdiff -s kernel-fix.patch
# 출력:
# ! drivers/net/foo.c      (변경)
# + drivers/net/bar.c      (추가)
# - drivers/net/old.c      (삭제)

# 헝크 수 표시
lsdiff -n kernel-fix.patch

interdiff — 패치 간 차이

# 두 패치 버전 간의 차이 (v1 → v2 변경 사항)
interdiff fix-v1.patch fix-v2.patch > v1-to-v2.diff

# 커널 리뷰에서 v2 변경 사항만 확인할 때 유용
# 리뷰어: "v1에서 뭐가 바뀌었나요?"
interdiff 0001-v1-fix.patch 0001-v2-fix.patch

combinediff — 패치 합치기

# 두 패치를 순서대로 합침
combinediff patch1.diff patch2.diff > combined.diff

# patch1을 적용한 후 patch2를 적용한 것과 동일한 결과

splitdiff — 패치 분할

# 파일별로 패치 분할
splitdiff -a big-patch.patch
# 출력: big-patch-drivers-net-foo.c.patch, big-patch-include-net-baz.h.patch, ...

# 디렉터리별로 분할
splitdiff -d big-patch.patch

rediff — 헝크 번호 재계산

# 패치 파일을 수동 편집한 후 줄 번호 수정
# (헝크를 직접 수정하면 줄 번호가 맞지 않을 수 있음)
rediff edited-patch.patch > fixed-patch.patch

quilt — 패치 스택 관리

quilt는 여러 패치를 스택(Stack)으로 관리하는 도구입니다. Debian 패키지 유지보수, 배포판 커널 커스터마이징, upstream에 여러 패치를 순서대로 관리할 때 유용합니다.

quilt 패치 스택 미적용 (unapplied) 0005-improve-perf.patch 0004-next.patch 적용됨 (applied) 0003-add-feature.patch ← top 0002-refactor-api.patch 0001-fix-bug.patch 원본 소스 코드 (base) push 다음 패치 적용 pop 최상위 패치 제거 주요 명령 quilt new 새 패치 생성 시작 quilt add 파일을 현재 패치에 등록 quilt refresh 패치 갱신 (diff 재수집) quilt push 다음 패치 적용 quilt pop 최상위 패치 제거 (되돌림) quilt series 전체 패치 목록 quilt applied 적용된 패치 목록 quilt top 현재 최상위 패치 이름
quilt 패치 스택 — push/pop으로 패치를 쌓고 내리는 계층 구조
# 설치
sudo apt install quilt            # Debian/Ubuntu
sudo dnf install quilt            # Fedora/RHEL

# 초기화
cd linux-6.8
quilt init

# 새 패치 생성
quilt new fix-null-deref.patch

# 수정할 파일 등록 (수정 전에 등록해야 함)
quilt add drivers/net/foo.c

# 파일 수정
vim drivers/net/foo.c

# 패치 갱신 (변경 사항을 패치 파일에 반영)
quilt refresh

# 다른 패치 추가
quilt new add-logging.patch
quilt add drivers/net/foo.c
vim drivers/net/foo.c
quilt refresh

# 패치 스택 조작
quilt pop                         # 최상위 패치 제거
quilt pop -a                      # 모든 패치 제거
quilt push                        # 다음 패치 적용
quilt push -a                     # 모든 패치 적용

# 패치 목록과 상태
quilt series                      # 전체 패치 목록
quilt applied                     # 적용된 패치 목록
quilt unapplied                   # 미적용 패치 목록
quilt top                         # 현재 최상위 패치

# 중간 패치 수정
quilt pop fix-null-deref.patch    # 해당 패치까지 되돌림
vim drivers/net/foo.c             # 수정
quilt refresh                     # 패치 갱신
quilt push -a                     # 나머지 패치 재적용

대안 도구

도구특징설치
colordiffdiff 출력에 색상 추가 (diff 래퍼)apt install colordiff
icdiff터미널 Side-by-side 컬러 diffpip install icdiff
deltagit diff 페이저, 구문 하이라이트apt install git-delta
difftasticAST 기반 구조적 diff (Structural diff)cargo install difftastic
meldGUI 3방향 머지 도구apt install meld
vimdiffVim 기반 나란히 비교/머지Vim에 포함
kdiff3GUI 3방향 머지 도구apt install kdiff3
diffoscope재현 가능 빌드 검증용 심층 비교apt install diffoscope
wdiff단어 단위 diffapt install wdiff
diffstatdiff 출력의 통계 요약apt install diffstat
# colordiff: diff에 색상 추가
colordiff -u old.c new.c

# diff 출력을 colordiff로 파이프
diff -u old.c new.c | colordiff

# vimdiff: Vim에서 나란히 비교
vimdiff old.c new.c
# ]c — 다음 차이로 이동
# [c — 이전 차이로 이동
# do — diff obtain (상대 창에서 가져오기)
# dp — diff put (상대 창으로 보내기)

# delta: git pager로 설정
git config --global core.pager delta
git diff    # 이제 delta로 보기

# difftastic: 구조적 diff (AST 기반)
difft old.c new.c
# git difftool로 사용
git config --global diff.external difft

delta 실전 설정 — 커널 리뷰 최적화

dandavison delta 0.18+는 히스토그램 알고리즘을 기본으로 사용하고, side-by-side/라인 번호/구문 하이라이트를 제공합니다. git 기본 페이저를 대체하면 대용량 패치 리뷰가 눈에 띄게 개선됩니다.

# ~/.gitconfig
[core]
    pager = delta

[interactive]
    diffFilter = delta --color-only

[delta]
    navigate = true          ; n/N으로 hunk 이동
    line-numbers = true
    side-by-side = false      ; 터미널 폭이 좁으면 false 유지
    syntax-theme = "Monokai Extended"
    features = decorations

[delta "decorations"]
    commit-decoration-style = bold yellow box ul
    file-style = bold yellow ul
    file-decoration-style = none
    hunk-header-decoration-style = cyan box ul

[merge]
    conflictStyle = zdiff3   ; delta는 zdiff3 3-way 충돌 마커 이해

[diff]
    algorithm = histogram    ; delta + histogram 조합 (커널에 권장)
    colorMoved = zebra
    colorMovedWS = allow-indentation-change

difftastic 실전 설정 — 구조적 diff

Wilfred difftastic(0.60+)은 tree-sitter 파서로 AST를 생성한 뒤 Dijkstra 최단경로로 diff를 계산합니다. 순수 라인 기반 diff에서 "주석만 바뀌었는데 함수 전체가 달라 보이는" 문제를 해결합니다. 단, 대용량 파일에서는 라인 기반보다 느립니다.

# ~/.gitconfig
[diff]
    tool = difftastic

[difftool]
    prompt = false

[difftool "difftastic"]
    cmd = difft "$LOCAL" "$REMOTE"

[pager]
    difftool = true

[alias]
    dft = difftool
    ; 사용: git dft HEAD~..HEAD -- drivers/net/
# 특정 커밋만 구조적 diff로 보기
$ git dft HEAD~1..HEAD

# 환경 변수로 외부 diff 지정 (일회성)
$ GIT_EXTERNAL_DIFF=difft git diff drivers/foo/bar.c

# 두 파일 직접 비교
$ difft old.c new.c

Myers vs Histogram vs Patience — 동일 커밋 실측

동일한 커밋에 대해 세 알고리즘으로 diff를 실행하면 결과물의 "가독성"이 달라지는 것을 확인할 수 있습니다. 커널 리뷰 경험상 histogram이 코드 이동(move)과 함수 경계 인식이 가장 좋다는 점이 JGit/libxdiff 벤치마크와 2024년 Microsoft Research 리뷰 품질 연구에서 반복 확인되었습니다.

# 세 알고리즘으로 같은 커밋 diff 생성 + 크기 비교
for algo in myers minimal histogram patience; do
    lines=$(git diff --diff-algorithm=$algo HEAD~1 HEAD | wc -l)
    echo "$algo: $lines lines"
done

# 결과 예 (단일 리팩터링 커밋):
#   myers:     412 lines
#   minimal:   398 lines
#   histogram: 356 lines   ← 가독성 최고
#   patience:  361 lines

# 전역 기본값 변경 (권장)
$ git config --global diff.algorithm histogram
Git 기본 알고리즘의 미래: Git은 여전히 Myers를 기본값으로 사용하며, 2.47~2.49에 걸쳐 histogram으로 기본 전환하자는 실험이 있었으나 호환성 이유로 확정되지 않았습니다. 개인 리뷰 환경에서는 diff.algorithm=histogram을 명시적으로 설정하는 것을 권장합니다. 자세한 내용은 시퀀스 비교 알고리즘 페이지를 참고하세요.

패치 시리즈 관리

커널에 제출하는 변경이 크면 하나의 패치 대신 패치 시리즈(Patch Series)로 분리합니다. 각 패치는 독립적으로 컴파일되고 논리적 단위를 이루어야 합니다.

패치 시리즈 구조 [PATCH v2 0/3] e1000e: fix null deref series 커버 레터 — 시리즈 전체 설명, 수정 동기, 테스트 결과 (코드 변경 없음, 오직 설명만 담는 패치) [PATCH v2 1/3] e1000e: add null check in open() [PATCH v2 2/3] e1000e: add FLAG_IS_UP tracking [PATCH v2 3/3] e1000e: add device down logging 적용 순서: 커버 레터(0/3) → 1/3 → 2/3 → 3/3 (의존성 순서대로) 패치 시리즈 규칙 1 [PATCH n/N] — n번째 패치 / 전체 N개 2 0/N = 커버 레터 (코드 변경 없음, 설명만) 3 각 패치는 독립적으로 컴파일 가능해야 함 4 논리적 단위: 리팩터링·버그 수정·기능 추가 분리 5 v2, v3 = 리뷰 피드백 반영한 재제출 버전 변경 이력 (커버 레터에 포함): v1→v2: 패치 2의 플래그명 변경 (리뷰어 A 요청) v1→v2: 패치 3에 netdev_info 로깅 추가 v2→v3: Signed-off-by 이메일 수정
패치 시리즈 구조 — 커버 레터와 개별 패치의 계층 및 버전(v1→v2→v3) 관리
# ===== git format-patch로 패치 시리즈 생성 =====

# 최근 3개 커밋을 패치 시리즈로 (커버 레터 포함)
git format-patch -3 --cover-letter -v2
# 생성 파일:
#   v2-0000-cover-letter.patch  ← 커버 레터 (Subject/내용 직접 편집)
#   v2-0001-e1000e-add-null-check.patch
#   v2-0002-e1000e-add-flag-tracking.patch
#   v2-0003-e1000e-add-logging.patch

# 커버 레터 편집
vim v2-0000-cover-letter.patch
# Subject: [PATCH v2 0/3] e1000e: fix null deref in open path
# --- 아래에 시리즈 설명, 변경 이력 작성 ---

# 수신자 자동 설정
scripts/get_maintainer.pl v2-000*.patch

# 전체 시리즈 전송 (커버 레터의 Message-Id를 In-Reply-To로 연결)
git send-email --to=... --cc=... v2-000*.patch


# ===== 전통적 diff/patch로 패치 시리즈 관리 =====

# 순서대로 적용
for p in 0001-*.patch 0002-*.patch 0003-*.patch; do
    echo "=== 적용: $p ==="
    patch --dry-run -p1 < "$p" || { echo "검증 실패: $p"; exit 1; }
    patch -p1 < "$p"
done

# 역순으로 되돌리기
for p in 0003-*.patch 0002-*.patch 0001-*.patch; do
    patch -R -p1 < "$p"
done

패치 파일 수동 편집

때로는 패치 파일을 직접 편집해야 합니다. 예를 들어 불필요한 변경을 제거하거나, 백포트를 위해 경로를 수정하거나, 패치에서 특정 헝크만 분리할 때입니다.

# ===== 헝크 제거 =====
# 패치에서 특정 헝크를 제거하려면:
# 1. 해당 @@ 줄부터 다음 @@ 줄(또는 파일 끝/다음 --- 줄) 직전까지 삭제
# 2. 파일 헤더의 줄 수는 보통 자동 무시됨 (patch가 실제 내용 기준)

# ===== 경로 수정 =====
# 패치의 파일 경로가 현재 소스 트리와 다른 경우
sed -i 's|a/old/path/|a/new/path/|g; s|b/old/path/|b/new/path/|g' fix.patch

# ===== 줄 번호 재계산 =====
# 헝크 내용을 수동 편집한 후 줄 번호가 맞지 않으면
rediff edited.patch > fixed.patch   # patchutils 필요

# ===== 패치 분리 (filterdiff 대안) =====
# 직접 에디터로 패치를 분리하는 방법:
# 1. 원본 패치를 복사
cp big.patch part-a.patch
cp big.patch part-b.patch
# 2. part-a.patch에서 불필요한 파일 섹션 삭제
# 3. part-b.patch에서 반대로 삭제
# 4. 각각 --dry-run으로 검증
patch --dry-run -p1 < part-a.patch
patch --dry-run -p1 < part-b.patch

# ===== 주의사항 =====
# - 문맥 줄(공백 접두사)은 절대 수정하면 안 됩니다
# - +/- 줄을 수정하면 해당 헝크의 줄 수(@@ 헤더)도 수정해야 합니다
# - 문맥 줄의 첫 번째 공백 문자가 실제 내용이 아닌 접두사임에 주의
# - 편집 후 반드시 patch --dry-run으로 검증
패치 편집 시 흔한 실수:
  • 탭/공백 혼동 — 문맥 줄의 접두사 공백(space)과 들여쓰기 탭(tab)을 혼동합니다. Unified 형식에서 문맥 줄의 첫 문자는 항상 공백(space)입니다.
  • 줄 수 불일치 — + 줄을 추가/삭제하면 @@ 헤더의 줄 수가 맞지 않습니다. rediff로 자동 수정하거나, 직접 수정해야 합니다.
  • 불완전한 줄(No newline at end of file) — 파일 끝에 개행이 없으면 \ No newline at end of file 표시가 있습니다. 이 줄을 삭제하면 패치가 깨집니다.

커널 소스 내 diff/patch 관련 코드

리눅스 커널 소스 트리에는 diff/patch 워크플로를 지원하는 여러 스크립트와 도구가 포함되어 있습니다.

경로용도설명
scripts/checkpatch.pl패치 검증코딩 스타일(Coding Style), 커밋 메시지, Signed-off-by 검사. checkpatch.pl *.patch로 실행
scripts/get_maintainer.pl수신자 결정MAINTAINERS 파일 기반으로 패치 수신자(To/Cc) 결정
scripts/diffconfig.config 비교두 커널 설정 파일의 차이를 "CONFIG_X n -> y" 형식으로 표시
scripts/bloat-o-meter크기 비교두 vmlinux의 심볼별 크기 변화 표시. 패치의 코드 크기 영향 측정
scripts/decode_stacktrace.sh스택 디코딩패치 적용 전후 oops 주소를 소스 줄로 변환
Documentation/process/프로세스(Process) 문서submitting-patches.rst, email-clients.rst 등 패치 제출 가이드
# checkpatch.pl로 패치 검증 (제출 전 필수)
scripts/checkpatch.pl --strict v2-0001-*.patch
# ERROR: Missing Signed-off-by: line(s)
# WARNING: line over 100 characters

# 패치 파일이 아닌 소스 파일 직접 검사
scripts/checkpatch.pl --file drivers/net/foo.c

# get_maintainer.pl로 수신자 확인
scripts/get_maintainer.pl v2-0001-*.patch
# Jesse Brandeburg <...> (maintainer:INTEL ETHERNET DRIVERS)
# netdev@vger.kernel.org (open list:NETWORKING DRIVERS)

# bloat-o-meter로 패치의 코드 크기 영향 측정
scripts/bloat-o-meter vmlinux.before vmlinux.after
# add/remove: 2/0 grow/shrink: 1/0 up/down: 156/0 (156)
# Function                                     old     new   delta
# e1000_open                                   542     698    +156

실전 레시피 모음

stable 커널 패치 백포트

# mainline 패치를 stable 커널에 적용
cd linux-6.6
patch --dry-run -p1 < ../mainline-fix.patch

# 오프셋/퍼지로 적용 가능한 경우
patch -p1 < ../mainline-fix.patch

# 충돌 발생 시: .rej 확인 후 수동 해결
patch -p1 < ../mainline-fix.patch
# → FAILED 헝크가 있으면 .rej 파일 확인
cat drivers/net/foo.c.rej
# → 해당 부분을 에디터에서 수동으로 적용
vim drivers/net/foo.c
# → 정리
rm drivers/net/foo.c.rej drivers/net/foo.c.orig

큰 패치를 파일별로 분리

# patchutils의 splitdiff 사용
splitdiff -a big-patch.patch

# 또는 filterdiff로 특정 서브시스템만 추출
filterdiff -i '*/drivers/net/*' big-patch.patch > net-changes.patch
filterdiff -i '*/include/*' big-patch.patch > header-changes.patch

이메일로 받은 패치 적용

# 이메일 본문에서 패치 추출 (git format-patch 형식)
git am patch-email.mbox

# 일반 diff 패치를 이메일에서 저장한 경우
patch -p1 < saved-patch.diff

# b4 도구로 LKML에서 패치 시리즈 가져오기
b4 am 20240101120000.12345-1-author@example.com
git am *.mbx

바이너리 파일 차이 확인

# diff는 바이너리 파일이면 "Binary files differ"만 표시
diff firmware-v1.bin firmware-v2.bin
# Binary files firmware-v1.bin and firmware-v2.bin differ

# 16진수 덤프로 비교
diff <(xxd firmware-v1.bin) <(xxd firmware-v2.bin) | head -30

# cmp: 첫 번째 다른 바이트 위치
cmp firmware-v1.bin firmware-v2.bin
# firmware-v1.bin firmware-v2.bin differ: byte 1024, line 8

# cmp -l: 모든 다른 바이트 나열
cmp -l firmware-v1.bin firmware-v2.bin

커널 .config 비교

# scripts/diffconfig (커널 소스 내장)
scripts/diffconfig .config.old .config
# 출력 예:
#  CONFIG_DEBUG_INFO n -> y
# -CONFIG_DEBUG_INFO_NONE y
# +CONFIG_DEBUG_INFO_DWARF5 y
# +CONFIG_KASAN y

# 일반 diff로도 비교 가능 (주석/빈줄 제외)
diff <(grep -v '^#\|^$' .config.old | sort) \
     <(grep -v '^#\|^$' .config | sort)

diff 알고리즘 이해

diff는 원본과 수정본을 줄 시퀀스(Sequence)로 보고 최단 편집 스크립트(Shortest Edit Script, SES)를 찾습니다. 전통적인 diff 모델에는 치환 연산이 따로 없으므로, 공통으로 남는 줄들의 최대 집합이 곧 LCS(Longest Common Subsequence, 최장 공통 부분 수열)입니다. 즉 LCS는 "무엇을 남길지", SES는 "무엇을 삭제하고 추가할지"를 표현한 같은 정보의 다른 관점입니다.

원본 줄 수를 n, 수정본 줄 수를 m, LCS 길이를 L이라 하면 SES 길이 D는 D = n + m - 2L입니다. 따라서 LCS가 길수록 삭제와 추가가 줄어듭니다. Myers 알고리즘은 이 관계를 바탕으로 편집 그래프(Edit Graph)에서 SES를 직접 찾아 실제 diff 출력에 쓸 수 있는 결과를 빠르게 만듭니다.

diff 알고리즘: 공통 줄 유지 → SES 생성 색상 범례: 공통 줄 유지 (LCS) 삭제된 줄 추가된 줄 원본 (A) #include <stdio.h> (빈 줄) - int main(void) - printf("Hello") return 0; } 수정본 (B) #include <stdio.h> + #include <stdlib.h> (빈 줄) + int main(int argc, ...) + printf("Hello, %s") return 0; } 공통 줄 유지 (LCS 대응) SES 생성 결과 — 공통 줄은 유지하고, 삭제와 추가만 남는다 삭제된 줄 (A에서 제거, 2줄) - int main(void) - printf("Hello") 추가된 줄 (B에 삽입, 3줄) + #include <stdlib.h> + int main(int argc, ...) + printf("Hello, %s") 길이 관계식 n = 6, m = 7 LCS 길이 L = 4 D = n + m - 2L = 5
LCS 기반 diff 알고리즘 — 공통 줄 유지와 SES 길이 D = n + m - 2L의 관계
더 깊은 이론: 동적 계획법 표 채우기, D = n + m - 2L 관계식, 편집 그래프, Myers 경로 탐색은 시퀀스 비교 알고리즘 문서에서 단계별로 설명합니다.

Git의 diff 알고리즘

Git은 Myers 외에도 가독성을 높이기 위한 여러 diff 알고리즘을 제공합니다. 최소 편집 길이만 맞는다고 항상 읽기 좋은 패치가 되는 것은 아니므로, 코드 이동이나 반복 줄이 많은 변경에서는 다른 선택지가 더 유리할 수 있습니다.

알고리즘옵션특징
Myers (기본)--diff-algorithm=myersSES를 빠르게 찾는 기본 diff. LCS와 쌍대 관계
Patience--diff-algorithm=patience공통 고유 줄을 앵커로 사용, 코드 이동에 강함
Histogram--diff-algorithm=histogramPatience 확장판. 낮은 빈도 공통 줄 처리 개선
Minimal--diff-algorithm=minimalMyers를 더 오래 탐색해 더 작은 diff를 시도

알고리즘별 diff 결과 비교 시각화

동일한 코드 변경에 대해 세 알고리즘이 서로 다른 diff 결과를 생성합니다. 아래 다이어그램은 함수 이동(Move)이 포함된 변경에서 각 알고리즘의 차이를 보여줍니다.

함수 이동(Move)이 포함된 변경 — 알고리즘별 결과 비교 같은 변경, 세 가지 결과 — 함수 이동(Move) void swap() static void swap_values() Myers (기본) void swap(int *a, int *b) { -int tmp = *a; -*a = *b; -*b = tmp; } // ... 50줄 이후 ... +static void swap_values(int *x, int *y) { // ... 동일한 본문 ... ❌ 삭제+추가로 표시 — 읽기 어려움 Patience // swap() 함수 이동: // void swap → static void swap_values +static void swap_values(int *x, int *y) { int tmp = *x; *x = *y; *y = tmp; } +// ✅ 고유 줄 앵커로 이동 인식 ✅ 함수 이동을 한 곳에서 표시 Histogram ★ 권장 +static void swap_values(int *x, int *y) { int tmp = *x; *x = *y; *y = tmp; } // ... 50줄 이후 ... -void swap(int *a, int *b) { // ... 본문 ... ✅ 가장 작은 diff + 가장 읽기 쉬움 알고리즘 특성 비교 항목 Myers Patience Histogram 복잡도 O(ND) O(N log N) O(N log N) 코드 이동 인식 약함 강함 강함 반복 줄 처리 보통 취약 개선 대용량 파일 빠름 보통 보통 권장 용도 대규모 파일, 빠른 diff 리팩터링, 함수 이동 커널 리뷰 (★ 범용 권장)
Git diff 알고리즘 비교 — Myers/Patience/Histogram이 함수 이동(Move)에 대해 생성하는 출력 차이
# Patience diff 사용 (코드 리팩터링 시 권장)
git diff --patience

# Histogram diff (Git 기본 권장 후보)
git diff --histogram

# 기본 알고리즘 변경
git config --global diff.algorithm histogram

트러블슈팅

증상원인해결
can't find file to patch -p 값이 잘못됨 lsdiff -p1 patch.diff로 경로 확인 후 올바른 -p 값 사용
Hunk FAILED 원본이 패치 생성 시점과 다름 .rej 파일 확인 후 수동 적용, 또는 패치 재생성
Reversed patch detected 이미 적용된 패치를 다시 적용 -R로 되돌리거나, 이미 적용됨을 확인
줄바꿈 문제 (Windows ↔ Unix) CR/LF vs LF 차이 dos2unix 변환 후 diff, 또는 --strip-trailing-cr
인코딩 문제 UTF-8 vs EUC-KR 등 iconv로 인코딩 통일 후 diff
git apply 실패 공백 문제 git apply --whitespace=warn 또는 --whitespace=fix
patch: **** malformed patch 패치 파일 손상 (이메일 줄바꿈 등) git am --patch-format=mbox 또는 b4으로 재다운로드
빈 줄/공백만 다른 패치 에디터 설정 차이 diff -B -w로 의미 없는 차이 무시
patch does not apply (오프셋 초과) 패치 생성 위치와 적용 위치가 너무 다름 -F3으로 퍼지 허용, 또는 git apply --3way로 3-way 머지 시도
git send-email 연결 실패 SMTP 서버 설정 오류 또는 비밀번호 인증 실패 Gmail은 앱 비밀번호(App Password) 필요. --smtp-debug로 로그 확인
패치에 BOM(Byte Order Mark) 포함 Windows 에디터에서 UTF-8 BOM 자동 삽입 sed -i '1s/^//' file.c로 BOM 제거 후 재패치
checking patch 이후 아무 출력 없음 패치가 이미 적용된 상태 patch -R --dry-run으로 역방향 적용 가능한지 확인

diff/patch 모범 사례

커널 개발 모범 사례:
  • 항상 Unified 형식(-u)을 사용합니다. Normal/Context 형식은 레거시(Legacy)입니다.
  • 디렉터리 비교 시 -ruNp 옵션 조합을 사용합니다 (재귀 + Unified + 새 파일 + 함수명).
  • patch --dry-run으로 항상 먼저 검증합니다.
  • 커널 소스에서는 patch -p1이 표준입니다.
  • 퍼지(-F)를 높이기보다 패치를 재생성하는 것이 안전합니다.
  • .rej와 .orig 파일은 반드시 정리합니다.
  • Git 환경에서는 git format-patch / git am을 우선합니다.
  • 대규모 변경은 논리적 단위로 분리하여 별도 패치를 만듭니다.

참고자료

도구 & 매뉴얼

알고리즘 & 이론

보조 도구 & 패키지 관리

필수 관련 문서: