Git — 리눅스 커널 개발을 위한 Git 완전 가이드
리눅스 커널 커뮤니티의 메일 기반 협업에 맞춘 Git 운용 절차를 전체 흐름으로 다룹니다. upstream 추적과 토픽 브랜치 분리, 커밋 단위 분해와 메시지 작성 규약, `format-patch`/`send-email` 리뷰 루프, v2/v3 재전송 관리, `rebase` 및 충돌 해결, `bisect` 회귀 원인 탐색, `worktree` 병렬 작업, stable 백포트·서브시스템 트리 머지 정책까지 실제 유지보수 업무 기준으로 상세히 설명합니다.
핵심 요약
- 저장소(Repository) — 프로젝트의 모든 파일과 변경 이력을 담는 컨테이너.
.git/디렉터리에 저장됩니다. - 커밋(Commit) — 특정 시점의 스냅샷. SHA-1/SHA-256 해시로 식별되며, 변경 내용·작성자·날짜·부모 커밋 정보를 포함합니다.
- 브랜치(Branch) — 커밋 그래프에서의 이동 포인터. 커널 개발에서는 패치 시리즈마다 전용 브랜치를 만드는 것이 관례입니다.
- 원격(Remote) — 네트워크 상의 저장소 참조.
origin(자신의 포크)과upstream(Linus 트리)을 동시에 등록하는 것이 일반적입니다. - 패치(Patch) —
git format-patch로 생성되는 이메일 형식의 차이점 파일. 커널 메일링 리스트에git send-email로 전송합니다.
단계별 이해
- 설치 및 초기 설정
이름, 이메일, 에디터, send-email SMTP 설정을~/.gitconfig에 등록합니다. 커널 개발에 최적화된 설정을 한 번만 구성해 두면 이후 모든 작업이 편리해집니다. - 커널 소스 클론과 원격 저장소 관리
Linus 메인라인 트리나 서브시스템 트리를 클론하고, linux-next 추적 원격을 추가합니다. 얕은 클론(shallow clone)은 네트워크 비용을 줄여주지만 bisect에 제약이 있습니다. - 패치 브랜치 생성과 커밋
안정적인 베이스 태그(예:v6.14)에서 브랜치를 만들고 변경 사항을 커밋합니다. 커밋 메시지는 커널 규약(subsystem: 요약,Signed-off-by)을 철저히 따릅니다. - format-patch와 send-email
git format-patch로 패치 파일을 생성하고,checkpatch.pl로 스타일을 검사한 뒤,git send-email로 적합한 메인테이너에게 전송합니다. - 리뷰 피드백 반영과 재제출
리뷰어 의견을 반영해git rebase -i로 커밋을 정리하고, 버전 번호를 올려(v2,v3) 재제출합니다. 이 과정이 리눅스 커널 패치 기여의 핵심입니다.
Git 개요 및 커널 개발에서의 역할
Git은 2005년 리눅스 커널 개발자들이 기존 버전 관리 시스템(BitKeeper)과의 계약 문제로 대안이 필요해지자 리누스 토르발즈(Linus Torvalds)가 직접 설계한 분산 버전 관리 시스템(DVCS)입니다. 설계 목표는 ① 속도, ② 단순한 설계, ③ 비선형 개발(수천 개의 병렬 브랜치) 지원, ④ 완전한 분산 운영, ⑤ 리눅스 커널 규모(수만 개 파일, 수백만 줄)의 프로젝트 처리였습니다.
커널 개발에서 Git은 단순한 소스 관리 도구를 넘어 패치 리뷰 워크플로의 핵심 인프라입니다. git format-patch로 패치를 생성하고 git send-email로 메일링 리스트에 전송하는 이메일 기반 프로세스는 GitHub 시대에도 유지되고 있습니다. Linus Torvalds는 메인라인 트리를 직접 유지하며, 각 서브시스템 메인테이너는 별도 트리에서 패치를 수집한 뒤 Linus에게 Pull Request를 보냅니다.
Git은 2005년 4월 최초 릴리스 이후 빠르게 발전해 현재 v2.x 계열이 표준입니다. git --version으로 설치 버전을 확인하세요. 커널 개발에는 v2.25 이상(sparse-checkout 개선)을, 이상적으로는 v2.40 이상을 권장합니다.
| 특성 | DVCS (Git, Mercurial) | CVCS (SVN, Perforce) |
|---|---|---|
| 저장소 사본 | 모든 클라이언트가 전체 이력 보유 | 서버에만 완전한 이력 존재 |
| 오프라인 작업 | 커밋·브랜치·로그 조회 모두 가능 | 대부분 서버 연결 필요 |
| 브랜치 비용 | 포인터 이동 — 사실상 무료 | 디렉터리 복사 — 고비용 |
| 병합 전략 | 3-way 머지, rebase 등 다양 | 제한적 (잦은 충돌) |
| 커널 채택 이유 | 비선형 개발(수천 브랜치) 지원 | 커널 규모에 부적합 |
| 무결성 보장 | SHA-1/SHA-256 해시 체인 | 리비전 번호 (위조 가능) |
--object-format=sha256 저장소를 지원하지만, 커널 메인라인은 아직 SHA-1 기반입니다. git hash-object --stdin <<< "test"로 현재 해시 형식을 확인할 수 있습니다. 내부 구조는 Git 내부 구조 섹션을 참고하세요.
Git 별칭(alias) 모음 — 커널 개발 최적화
[alias]
# 로그 시각화
lol = log --oneline --graph --decorate --all
lo = log --oneline -20
ls = log --stat --oneline -10
# 상태/diff
st = status -s
df = diff --histogram
dfc = diff --cached --histogram
wdiff = diff --word-diff=color
# 패치 워크플로
fp = format-patch --base=auto --cover-letter
se = send-email --annotate
rb = rebase -i --autosquash
fix = commit --fixup
# 브랜치 관리
br = branch -vv
bra = branch -avv
sw = switch
gone = !git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d
# 코드 탐색
who = log --no-merges --format="%an <%ae>" | sort | uniq -c | sort -rn | head
find = log -S # git find <string>
grep = grep -n
# 빠른 되돌리기
undo = reset HEAD~1 --mixed # 마지막 커밋 취소 (변경은 보존)
last = log -1 HEAD --stat
Git 내부 구조 심층
Git의 내부 구조를 이해하면 복잡한 워크플로를 훨씬 명확하게 파악할 수 있습니다. Git은 본질적으로 콘텐츠 주소 파일시스템(content-addressable filesystem) 위에 버전 관리 인터페이스를 얹은 구조입니다.
| 파일/디렉터리 | 역할 | 비고 |
|---|---|---|
objects/ | 모든 Git 객체 저장 (blob/tree/commit/tag) | SHA-1 앞 2자리가 서브디렉터리명 |
objects/pack/ | 압축된 팩 파일 (.pack + .idx) | git gc 실행 시 생성 |
refs/heads/ | 로컬 브랜치 포인터 (SHA-1 텍스트 파일) | refs/heads/master 등 |
refs/remotes/ | 원격 추적 브랜치 | refs/remotes/origin/main 등 |
refs/tags/ | 태그 포인터 | 경량 태그: SHA-1, 주석 태그: tag 객체 |
HEAD | 현재 체크아웃 위치 | 브랜치 ref 또는 SHA-1(detached) |
index | 스테이징 영역 — 다음 커밋 내용을 추적 | 바이너리 형식, git ls-files --stage로 조회 |
ORIG_HEAD | rebase/merge 전 HEAD 저장 (복구용) | git reset ORIG_HEAD로 되돌리기 |
4종 Git 객체
Git의 모든 데이터는 4종류의 불변 객체로 표현됩니다. 각 객체는 type + 크기 + 내용의 SHA-1 해시로 식별됩니다.
- blob — 파일의 특정 버전 내용(바이트 시퀀스). 파일명·권한 정보 없음
- tree — 한 디렉터리의 스냅샷. blob/tree 참조 + 파일명 + 권한 목록
- commit — 최상위 tree 참조 + 부모 커밋(들) + 작성자/커미터/날짜 + 메시지
- tag — 특정 객체(보통 commit)를 가리키는 이름 붙은 포인터 + GPG 서명
# 객체 타입과 내용 조회
$ git cat-file -t HEAD # 타입 출력: commit
$ git cat-file -p HEAD # 내용 출력: tree, parent, author...
$ git cat-file -p HEAD^{tree} # 최상위 tree 객체 내용
$ git cat-file -p HEAD:drivers/net/foo.c # 특정 파일 blob 내용
# 객체 데이터베이스 통계
$ git count-objects -v
# 새 blob 객체 생성 (내용만 저장, 커밋 없음)
$ echo "test" | git hash-object --stdin -w
# 스테이징 영역(index) 상세 조회
$ git ls-files --stage
# 출력 형식: <mode> <SHA-1> <stage> <파일명>
# 100644 a1b2c3d... 0 drivers/net/foo.c
# stage: 0=일반, 1=공통조상(충돌), 2=우리쪽, 3=상대쪽
# 작업 트리 vs index vs HEAD 3-way 비교
$ git diff # 작업 트리 vs index
$ git diff --cached # index vs HEAD
$ git diff HEAD # 작업 트리 vs HEAD
# ref 해석 (rev-parse 활용)
$ git rev-parse HEAD # HEAD의 SHA-1
$ git rev-parse HEAD~3 # HEAD에서 3번째 조상
$ git rev-parse refs/heads/master # 브랜치 SHA-1
$ git rev-parse --abbrev-ref HEAD # 현재 브랜치 이름
# packfile 생성 (git gc 수동 실행)
$ git gc # 느슨한 객체를 pack으로 압축
$ git gc --aggressive # 더 강력한 압축 (시간 오래 걸림)
$ git verify-pack -v .git/objects/pack/*.idx # pack 내용 확인
.git/refs/ 개별 파일 대신 .git/packed-refs 단일 파일에 모든 ref를 저장합니다. git pack-refs --all로 수동 압축할 수 있습니다. 커널 저장소처럼 태그가 수천 개인 경우 이 최적화가 중요합니다.
설치 및 초기 설정
커널 개발을 시작하기 전에 Git을 올바르게 설정해야 합니다. 특히 user.name과 user.email은 Signed-off-by 태그에 직접 사용되므로 실명과 공개 가능한 이메일 주소를 사용해야 합니다.
# 패키지 설치 (Ubuntu/Debian 계열)
$ sudo apt install git git-email
# 패키지 설치 (Fedora/RHEL 계열)
$ sudo dnf install git git-email
# 패키지 설치 (Arch Linux)
$ sudo pacman -S git
다음은 커널 개발에 최적화된 ~/.gitconfig 설정입니다:
[user]
name = Hong Gildong
email = gildong@example.com
[core]
editor = vim
autocrlf = input # LF 유지 (커널은 LF만 사용)
whitespace = trailing-space,space-before-tab
[sendemail]
smtpserver = smtp.gmail.com
smtpserverport = 587
smtpencryption = tls
smtpuser = gildong@gmail.com
# smtpPass는 앱 비밀번호 사용 권장 (2단계 인증 환경)
confirm = always # 전송 전 확인 요청
annotate = yes # 각 패치 에디터에서 확인
suppresscc = self # 자신에게 참조 보내지 않음
chainreplyto = false # 커버 레터에 모두 회신
[format]
signoff = true # git format-patch 시 Signed-off-by 자동 추가
coverLetter = auto # 2개 이상 패치 시 커버 레터 자동 생성
outputDirectory = patches # 기본 출력 디렉터리
[diff]
algorithm = histogram # 가독성 좋은 diff 알고리즘
colorMoved = zebra # 이동된 코드 색상 구분
[log]
abbrevCommit = true # 짧은 해시 표시
date = iso # ISO 8601 날짜 형식
[alias]
lol = log --oneline --graph --decorate --all
st = status -s
co = checkout
rb = rebase
fp = format-patch
git credential과 gnome-keyring 또는 pass를 연동하여 안전하게 저장하세요.
gitconfig 주요 섹션 참조
| 섹션 | 옵션 | 권장값 | 설명 |
|---|---|---|---|
[core] | editor | vim | 커밋 메시지 에디터 |
autocrlf | input | LF 유지 (커널은 LF만 허용) | |
whitespace | trailing-space,space-before-tab | 공백 오류 감지 | |
[diff] | algorithm | histogram | patience보다 가독성 좋은 diff |
colorMoved | zebra | 이동한 코드 블록 색상 구분 | |
[rebase] | autoSquash | true | fixup!/squash! 커밋 자동 정렬 |
updateRefs | true | 의존 브랜치 ref 자동 갱신 (Git 2.38+) | |
[merge] | tool | vimdiff | 충돌 해결 도구 |
conflictstyle | zdiff3 | 공통 조상까지 보여주는 충돌 표시 (Git 2.35+) | |
[push] | default | current | 현재 브랜치만 push |
autoSetupRemote | true | 추적 브랜치 자동 설정 (Git 2.38+) |
gitconfig 우선순위와 includeIf
Git 설정은 4단계 우선순위를 가집니다: system → global → local → worktree (뒤로 갈수록 높은 우선순위). git config --list --show-origin으로 각 설정의 출처 파일을 확인할 수 있습니다.
# ~/.gitconfig — includeIf로 업무/개인 계정 분리
[user]
name = Hong Gildong
email = personal@gmail.com # 기본값: 개인 계정
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work # ~/work/ 하위 저장소에만 적용
[includeIf "gitdir:~/work/linuxkernel/"]
path = ~/.gitconfig-kernel # 커널 저장소 전용 설정
# ~/.gitconfig-work
[user]
email = gildong@company.com
[sendemail]
smtpuser = gildong@company.com
# ~/.gitconfig-kernel
[format]
signoff = true
coverLetter = auto
[sendemail]
smtpserver = smtp.company.com
커널 소스 클론 및 원격 저장소 관리
리눅스 커널 개발자는 여러 트리를 동시에 추적합니다. 메인라인(Linus 트리), linux-next, 그리고 작업 중인 서브시스템 트리가 대표적입니다.
# 메인라인 전체 클론 (약 4~5 GB, 상당한 시간 소요)
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
# 얕은 클론 (최근 커밋만, 빠른 시작 — bisect에는 제약)
$ git clone --depth=100 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
# 클론 후 linux-next 원격 추가
$ cd linux
$ git remote add linux-next \
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
# 서브시스템 트리 예: networking 트리
$ git remote add net \
https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git
# 서브시스템 트리 예: net-next (새 기능)
$ git remote add net-next \
https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git
# 원격 저장소 전부 fetch (태그 포함)
$ git fetch --all --tags
# 특정 원격만 fetch
$ git fetch linux-next
# 원격 목록 확인
$ git remote -v
https://mirrors.edge.kernel.org/나 CDN 미러를 사용하세요. 초기 클론 후 원격 URL을 변경해도 됩니다: git remote set-url origin <새 URL>. 또한, GitHub의 torvalds/linux 미러를 통해 더 빠르게 클론할 수도 있습니다 (주의: GitHub 미러는 실제 개발 트리가 아닌 읽기 전용 미러입니다).
clone 주요 옵션
| 옵션 | 효과 | 주의사항 |
|---|---|---|
--depth=N | 최근 N개 커밋만 (얕은 클론) | git bisect에 제약 |
--branch <name> | 특정 브랜치/태그만 클론 | 기본값: 기본 브랜치 |
--single-branch | 지정 브랜치의 이력만 포함 | 다른 브랜치 fetch 불가 |
--filter=blob:none | blobless 클론 (blob 지연 다운로드) | 서버가 부분 클론 지원해야 함 |
--filter=tree:0 | treeless 클론 (tree도 지연) | 더 빠르지만 일부 기능 제한 |
--reference <path> | 로컬 저장소를 alternates로 참조 | 같은 머신 내 클론 속도 향상 |
--no-local | 파일시스템 복사 대신 네트워크 프로토콜 사용 | --reference와 함께 사용 불가 |
# alternates 활용: 기존 클론을 참조해 네트워크 절약
$ git clone --reference ~/linux-main \
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git \
linux-work
# git bundle: 오프라인 환경에서 패치 전달
$ git bundle create linux-v6.14.bundle v6.13..v6.14
$ git bundle verify linux-v6.14.bundle
$ git clone linux-v6.14.bundle linux-from-bundle
# 클론 후 필수 설정 체크리스트
$ git config user.email # Signed-off-by에 사용될 이메일 확인
$ git remote -v # 원격 목록 확인
$ git remote add upstream \ # upstream(Linus 트리) 추가
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
부분 클론과 sparse-checkout
리눅스 커널 전체 클론은 약 4.5 GB(히스토리 포함)이며 80,000개 이상의 파일이 있습니다. 특정 서브시스템만 작업하거나 디스크/네트워크가 제한된 환경이라면 부분 클론(partial clone)과 sparse-checkout을 활용하세요.
| 클론 방식 | 디스크 크기 | 초기 속도 | bisect | 특징 |
|---|---|---|---|---|
| 전체 클론 (full) | ~4.5 GB | 느림 | 완전 지원 | 모든 기능 사용 가능 |
| 얕은 클론 (--depth) | ~200 MB | 매우 빠름 | 제한적 | 이력 탐색 불가 |
| blobless (--filter=blob:none) | ~1.5 GB | 빠름 | 지원 | blob 지연 다운로드 |
| treeless (--filter=tree:0) | ~700 MB | 매우 빠름 | 제한적 | tree+blob 지연 |
| sparse-checkout | 설정에 따라 | 빠름 | 지원 | 특정 경로만 체크아웃 |
# blobless 클론: 이력은 전체, blob은 필요 시 다운로드
$ git clone --filter=blob:none \
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
# sparse-checkout 초기화 (cone mode — 경로 접두사 기반)
$ git sparse-checkout init --cone
# 특정 경로만 체크아웃 (drivers/net/ 서브트리)
$ git sparse-checkout set drivers/net include/linux/netdevice.h net/
# 현재 sparse-checkout 경로 확인
$ git sparse-checkout list
# 경로 추가
$ git sparse-checkout add drivers/usb
# sparse-checkout 해제 (전체 트리 복구)
$ git sparse-checkout disable
# 고급: blobless + sparse-checkout 조합
$ git clone --filter=blob:none --no-checkout \
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
$ cd linux
$ git sparse-checkout init --cone
$ git sparse-checkout set drivers/net net/ include/linux/
$ git checkout master
# non-cone mode: glob 패턴 사용 (더 유연, 더 느림)
$ git sparse-checkout init # cone mode 없이
$ git sparse-checkout set --no-cone \
'drivers/net/**' 'include/linux/net*.h'
기본 워크플로우
커널 개발의 일상적인 Git 명령어 흐름입니다. 변경 전에 항상 git status와 git diff로 작업 트리 상태를 확인하는 습관이 중요합니다.
# 작업 트리 상태 확인
$ git status
$ git status -s # 짧은 형식
# 변경 내용 확인
$ git diff # 작업 트리 vs 스테이지
$ git diff --cached # 스테이지 vs HEAD
$ git diff HEAD # 작업 트리 vs HEAD
# 파일 스테이징
$ git add drivers/net/foo.c
$ git add -p # 대화형으로 일부만 스테이징 (헝크 단위)
# 커밋 (메시지는 규약 따름)
$ git commit -s # -s: Signed-off-by 자동 추가
# 로그 확인
$ git log --oneline -20
$ git log --oneline --graph --decorate --all
$ git log --stat # 변경 파일 목록 포함
$ git log --patch # 전체 diff 포함
# 최신 커밋 빠른 수정 (push 전에만 사용)
$ git commit --amend
# 스테이지 취소
$ git restore --staged drivers/net/foo.c
# 작업 트리 변경 취소 (주의: 복구 불가)
$ git restore drivers/net/foo.c
git add -p 대화형 스테이징
git add -p는 변경 사항을 헝크(hunk) 단위로 선택적으로 스테이징합니다. 하나의 파일에 여러 독립적인 변경이 있을 때 커밋을 논리적으로 분리하는 데 유용합니다.
| 키 | 동작 | 설명 |
|---|---|---|
y | 스테이징 | 이 헝크를 스테이지에 추가 |
n | 건너뜀 | 이 헝크를 스테이지에 추가하지 않음 |
s | 분할 | 헝크를 더 작은 단위로 나눔 |
e | 편집 | 에디터에서 헝크를 직접 편집 |
q | 종료 | 나머지 헝크 건너뛰고 종료 |
? | 도움말 | 전체 명령어 목록 표시 |
a | 이후 전체 | 이 파일의 모든 나머지 헝크 스테이징 |
d | 이후 건너뜀 | 이 파일의 모든 나머지 헝크 건너뜀 |
# git diff 출력 형식 옵션
$ git diff --word-diff # 단어 단위 diff (가독성 향상)
$ git diff --stat # 파일별 변경 통계만
$ git diff --name-only # 변경된 파일명만
$ git diff --name-status # 파일명 + 상태(M/A/D)
# git show: 특정 커밋 또는 특정 시점의 파일 조회
$ git show HEAD # 최신 커밋 상세 내용
$ git show v6.14:drivers/net/foo.c # 태그 v6.14 시점의 파일 내용
$ git show abc1234:Makefile # 특정 커밋 시점의 파일
$ git show --stat HEAD~5 # 5개 전 커밋의 변경 파일 목록
임시 변경 사항을 저장하고 싶다면 git stash를 사용하세요 → git stash 완전 가이드 참고.
git stash 완전 가이드
git stash는 현재 작업 트리와 스테이징 영역의 변경 사항을 임시로 스택에 저장하고 HEAD 상태로 되돌립니다. 긴급 버그 수정이 들어왔을 때 현재 WIP(작업 중) 상태를 잃지 않고 브랜치를 전환할 수 있습니다.
| 명령어 | 동작 | 참고 |
|---|---|---|
git stash / git stash push | 변경 사항을 스택 최상단에 저장 | 추적되는 파일만 저장 |
git stash push -u | 미추적 파일도 함께 저장 | --include-untracked |
git stash push -m "메시지" | 설명 메시지 포함해 저장 | 여러 stash 구분에 유용 |
git stash list | 저장된 stash 목록 조회 | stash@{0}, stash@{1} … |
git stash show | 최근 stash 변경 요약 | -p 옵션으로 전체 diff |
git stash pop | 최근 stash 복원 후 스택에서 제거 | 충돌 시 제거되지 않음 |
git stash apply | 복원하되 스택에서 제거하지 않음 | 여러 번 적용 가능 |
git stash drop | 최근 stash 삭제 | stash@{N}으로 특정 지정 |
git stash clear | 모든 stash 삭제 | 복구 불가 — 주의 |
git stash branch <name> | stash를 새 브랜치로 변환 | 충돌 없이 복원 보장 |
# 커널 개발 활용 예: WIP 저장 → 긴급 패치 → WIP 복구
# 1. 현재 작업 임시 저장
$ git stash push -m "wip: foo 드라이버 rx 경로 개선"
# 2. 긴급 버그 수정 브랜치로 전환
$ git checkout -b fix/urgent-null-deref v6.14
# ... 수정 및 커밋 ...
$ git format-patch -1 HEAD -o patches/urgent/
# 3. 원래 브랜치로 복귀 후 WIP 복구
$ git checkout feature/foo-rx-improve
$ git stash pop
# stash 목록 확인
$ git stash list
# stash@{0}: On feature/foo-rx-improve: wip: foo 드라이버 rx 경로 개선
# 부분 stash: 특정 파일만 또는 특정 헝크만
$ git stash push drivers/net/foo.c # 특정 파일만
$ git stash push -p # 대화형 헝크 선택
$ git stash push --keep-index # 스테이지된 내용은 유지
# 특정 stash 조작
$ git stash show -p stash@{2} # stash@{2} 전체 diff 확인
$ git stash apply stash@{2} # 특정 stash 적용
$ git stash drop stash@{2} # 특정 stash 삭제
# stash를 새 브랜치로 복원 (충돌 없이 안전하게)
$ git stash branch feature/foo-wip stash@{0}
브랜치 관리 전략
커널 개발에서 브랜치 전략은 패치 시리즈 관리의 핵심입니다. 각 패치 시리즈는 별도 브랜치에서 개발하고, 메인테이너 트리의 안정적인 태그나 커밋을 베이스로 사용합니다.
# 안정 릴리스 태그 기반 브랜치 생성 (패치 제출 표준)
$ git checkout -b fix/net-foo-null-deref v6.14
# 서브시스템 트리 기반 브랜치 (net-next 기반 신규 기능)
$ git fetch net-next
$ git checkout -b feature/net-bar-offload net-next/main
# 브랜치 목록 확인
$ git branch -vv # 원격 추적 브랜치 포함
# 원격 브랜치 포함 목록
$ git branch -a
# 브랜치 삭제
$ git branch -d fix/old-patch
# 현재 브랜치 베이스 확인 (공통 조상)
$ git merge-base HEAD v6.14
# 베이스 태그 이후 커밋 목록 확인
$ git log v6.14..HEAD --oneline
linux-next의 특정 커밋이어야 합니다. 임의의 커밋을 베이스로 사용하면 메인테이너가 패치를 적용하기 어렵습니다. git format-patch --base=auto 옵션으로 베이스 정보를 패치에 자동 포함할 수 있습니다.
브랜치 명명 규약
| 접두사 | 용도 | 예시 |
|---|---|---|
fix/ | 버그 수정 패치 | fix/net-foo-null-deref |
feature/ | 새 기능 개발 | feature/net-bar-hw-offload |
wip/ | 작업 중(Work In Progress) | wip/mm-slab-rework |
cleanup/ | 리팩터링·정리 | cleanup/drm-legacy-remove |
stable/ | stable 백포팅 | stable/6.6-net-fixes |
test/ | 실험적 변경 | test/x86-perf-new-algo |
# git switch (최신 권장 명령어) vs git checkout
$ git switch -c fix/net-foo v6.14 # 브랜치 생성+이동 (checkout -b 대체)
$ git switch master # 브랜치 이동 (checkout 대체)
$ git switch - # 이전 브랜치로 이동
# 추적 브랜치 설정
$ git branch --set-upstream-to=origin/master master
$ git push -u origin fix/net-foo # push하며 추적 브랜치 자동 설정
# 오래된 브랜치 정리
$ git branch --merged master # master에 머지된 로컬 브랜치 목록
$ git branch --merged master | grep -v master | xargs git branch -d
$ git remote prune origin # 삭제된 원격 브랜치 참조 정리
$ git fetch --prune # fetch + prune 동시 실행
커밋 메시지 규약
리눅스 커널 커밋 메시지는 엄격한 형식을 따릅니다. 메인테이너가 git log와 git bisect를 통해 변경 이력을 추적하므로 명확하고 일관된 메시지가 필수입니다.
subsystem: 50자 이내 변경 요약 (명령형, 소문자 시작)
본문: 무엇을, 왜 변경했는지 설명 (선택, 필요 시). 한 줄 72자 이내.
'어떻게'는 코드로 보이므로 '왜'에 집중합니다.
재현 방법, 관련 버그, 커밋 참조 등을 여기에 기술.
Fixes: 수정 시 관련 커밋 SHA-1 기재:
Fixes: a1b2c3d4e5f6 ("subsystem: broken change description")
Link: https://lore.kernel.org/r/... (메일링 리스트 링크)
Signed-off-by: Hong Gildong <gildong@example.com>
Reviewed-by: Kim Cheolsu <cheolsu@example.com>
Acked-by: Park Younghee <younghee@example.com>
Co-developed-by: Lee Minsu <minsu@example.com>
Signed-off-by: Lee Minsu <minsu@example.com>
| 태그 | 의미 | 사용 조건 |
|---|---|---|
Signed-off-by | DCO(Developer Certificate of Origin) 서명 | 필수 — 모든 패치 |
Reviewed-by | 코드 리뷰 완료 (기술적 검토) | 리뷰어가 직접 추가 |
Acked-by | 해당 영역 메인테이너의 암묵적 동의 | 메인테이너가 추가 |
Tested-by | 실제 테스트 완료 | 테스터가 추가 |
Reported-by | 버그 최초 보고자 | 버그 수정 패치 |
Co-developed-by | 공동 개발자 (Signed-off-by 쌍으로 필요) | 공동 작성 시 |
Fixes | 회귀를 유발한 커밋 참조 | 버그 수정 시 권장 |
Link | 관련 메일/이슈 URL | 참조용 |
서브시스템 접두사 예시
| 접두사 | 서브시스템 | 접두사 | 서브시스템 |
|---|---|---|---|
net: | 네트워킹 코어 | net/ipv4: | IPv4 스택 |
tcp: | TCP 프로토콜 | udp: | UDP 프로토콜 |
mm: | 메모리 관리 | mm/slab: | 슬랩 할당자 |
fs: | 파일시스템 코어 | ext4: | ext4 파일시스템 |
block: | 블록 레이어 | nvme: | NVMe 드라이버 |
drm: | DRM/GPU 코어 | drm/i915: | Intel GPU |
x86: | x86 아키텍처 | arm64: | ARM64 아키텍처 |
drivers/usb: | USB 서브시스템 | drivers/i2c: | I2C 버스 |
kvm: | KVM 가상화 | bpf: | eBPF 코어 |
sched: | 태스크 스케줄러 | rcu: | RCU 메커니즘 |
# 실제 커밋 메시지 예시
# 제목: <서브시스템>: <요약>
net: fix null pointer dereference in foo_rx_handler
The foo driver dereferences dev->priv without checking for NULL,
which can happen when the interface is brought down while packets
are still in flight.
Add a NULL check before dereferencing. The issue was introduced
by commit a1b2c3d ("net: foo: add rx handler") in v6.12.
Fixes: a1b2c3d4e5f6 ("net: foo: add rx handler")
Reported-by: Kim Cheolsu <cheolsu@example.com>
Signed-off-by: Hong Gildong <gildong@example.com>
# git commit --trailer: 태그를 명령줄에서 추가
$ git commit -s \
--trailer "Fixes: a1b2c3d (\"net: foo: broken\")" \
--trailer "Reported-by: Kim Cheolsu <cheolsu@example.com>"
# Fixes: 태그와 stable 자동 백포팅
# "Fixes: SHA ("msg")" 형식이면 stable 봇이 자동으로 stable 트리에 백포팅 시도
# SHA는 전체 40자리 또는 최소 12자리 이상 사용 권장
git log --oneline은 80자 터미널 기준으로 설계되었습니다. git format-patch는 커밋 제목을 패치 이메일 제목으로 사용하므로, 50자를 초과하면 이메일 클라이언트에서 잘릴 수 있습니다. checkpatch.pl은 75자 초과 시 경고를 출력합니다. 간결한 제목을 위해 관사·조사·접속사를 생략하는 명령형 문체를 사용하세요.
히스토리 정리 (rebase -i)
패치 시리즈 제출 전에는 커밋 히스토리를 깔끔하게 정리해야 합니다. git rebase -i는 커밋 순서 변경, 합치기, 분리, 메시지 수정 등 다양한 작업을 지원합니다.
# 최근 3개 커밋 대화형 재배치
$ git rebase -i HEAD~3
# 베이스 커밋 이후 모든 커밋 재배치
$ git rebase -i v6.14
# 리베이스 에디터 명령어:
# pick p — 그대로 사용
# reword r — 커밋 메시지만 수정
# edit e — 커밋 내용 수정 (중단 후 수정)
# squash s — 이전 커밋과 합치기 (메시지 합산)
# fixup f — 이전 커밋과 합치기 (메시지 버림)
# drop d — 커밋 제거
작업 중 실수로 커밋한 "수정" 커밋을 원래 커밋에 자동으로 합치는 워크플로:
# 특정 커밋(abc1234)에 현재 변경 사항을 fixup으로 연결
$ git add -p
$ git commit --fixup=abc1234
# autosquash로 fixup 커밋을 자동 정리
$ git rebase -i --autosquash v6.14
# 리베이스 충돌 발생 시
$ git status # 충돌 파일 확인
# ... 파일 편집하여 충돌 해결 ...
$ git add <resolved-file>
$ git rebase --continue
# 리베이스 취소 (원래 상태로 복구)
$ git rebase --abort
rebase -i 전체 명령어
| 명령어 (단축) | 동작 | 설명 |
|---|---|---|
pick (p) | 그대로 사용 | 커밋을 변경 없이 적용 |
reword (r) | 메시지 수정 | 커밋 내용은 그대로, 메시지만 에디터에서 편집 |
edit (e) | 커밋 수정 | 적용 후 중단 — amend 또는 reset으로 내용 변경 |
squash (s) | 이전 커밋과 합치기 | 메시지 합산 (에디터 열림) |
fixup (f) | 이전 커밋과 합치기 | 이 커밋 메시지 버림 |
exec (x) | 셸 명령 실행 | 각 커밋 후 빌드/테스트 자동화 |
break (b) | 여기서 중단 | 대화형 작업 후 git rebase --continue |
drop (d) | 커밋 제거 | 커밋과 변경 내용 모두 삭제 |
label (l) | 이름 태그 지정 | 복잡한 머지 rebase용 |
reset (t) | label 위치로 리셋 | 복잡한 머지 rebase용 |
# exec 명령으로 각 커밋 후 빌드 검증 자동화
# rebase -i 에디터에 입력할 내용:
# pick abc1234 net: fix foo initialization
# exec make -j$(nproc) drivers/net/foo.ko 2>&1 | head -20
# pick def5678 net: foo: add bar feature
# exec make -j$(nproc) drivers/net/foo.ko 2>&1 | head -20
# 자동화 방법: --exec 옵션으로 모든 커밋에 적용
$ git rebase -i v6.14 --exec "make -j$(nproc) drivers/net/ 2>&1 | tail -5"
# rebase --onto: 브랜치 기반을 다른 곳으로 이식
# 시나리오: feature가 old-base 기반인데 new-base로 옮기고 싶을 때
$ git rebase --onto new-base old-base feature
# 예: v6.13 기반 브랜치를 v6.14 기반으로 이식
$ git rebase --onto v6.14 v6.13 fix/net-foo
# git filter-repo: 히스토리에서 파일 완전 제거 (filter-branch 대체)
$ pip3 install git-filter-repo
$ git filter-repo --path vmlinux.o --invert-paths # 파일 제거
$ git filter-repo --mailmap mailmap.txt # 작성자 이메일 수정
# 커밋 분리 (edit 명령 사용)
# 1. rebase -i에서 분리할 커밋을 'edit'으로 표시
# 2. 해당 커밋에서 중단되면:
$ git reset HEAD^ # 커밋 취소, 변경사항은 워킹 트리에 유지
$ git add -p # 첫 번째 커밋 내용만 선택
$ git commit -s -m "net: foo: fix null check"
$ git add -p # 두 번째 커밋 내용 선택
$ git commit -s -m "net: foo: add error logging"
$ git rebase --continue
선택적 커밋 이식 (cherry-pick)
git cherry-pick은 다른 브랜치의 특정 커밋(들)을 현재 브랜치에 적용합니다. 커널 개발에서는 주로 stable 브랜치 백포팅과 메인라인의 버그 수정을 서브시스템 트리에 반영하는 데 사용합니다.
# 단일 커밋 cherry-pick
$ git cherry-pick abc1234
# -x 옵션: 원본 커밋 SHA-1을 커밋 메시지에 자동 추가
# "(cherry picked from commit abc1234...)" 라인 추가
$ git cherry-pick -x abc1234
# --no-commit: 적용만 하고 커밋하지 않음 (수정 후 커밋)
$ git cherry-pick --no-commit abc1234 def5678
# ... 수정 후 ...
$ git commit -s
# 범위 cherry-pick: A..B (A는 미포함, B는 포함)
$ git cherry-pick abc1234..def5678
# 충돌 발생 시 처리
$ git status # 충돌 파일 확인
# ... 편집기로 충돌 해결 ...
$ git add <resolved-files>
$ git cherry-pick --continue # 계속 진행
$ git cherry-pick --abort # 취소 (원래 상태로)
# linux-stable 백포팅 실전 예제
# 시나리오: mainline의 버그 수정을 linux-6.6.y에 백포팅
# 1. 메인라인에서 수정 커밋 SHA-1 확인
$ git log --oneline v6.13..v6.14 -- drivers/net/foo.c
# 2. stable 브랜치로 이동
$ git fetch stable
$ git checkout -b backport/6.6-net-fix stable/linux-6.6.y
# 3. cherry-pick (-x로 원본 추적)
$ git cherry-pick -x abc1234def56
# 4. stable 제출 형식: 추가 태그 필요
# Cc: stable@vger.kernel.org (커밋 메시지에 추가)
# cherry-pick 후 commit --amend로 태그 추가
$ git commit --amend # Cc: stable@vger.kernel.org 추가
# git apply와의 차이점:
# - cherry-pick: 커밋 객체를 이식 (이력 포함)
# - git apply: 패치 파일(diff)만 적용 (이력 없음, 별도 커밋 필요)
$ git apply 0001-net-fix-foo.patch # 패치 파일 적용
$ git am 0001-net-fix-foo.patch # 패치 파일 적용 + 커밋 (작성자 정보 유지)
Cc: stable@vger.kernel.org를 추가하거나 별도로 stable@vger.kernel.org에 이메일을 보내 백포팅을 요청합니다. Fixes: 태그가 있는 커밋은 stable 봇이 자동으로 백포팅 후보로 분류합니다.
Git merge 전략 심층
Git의 머지는 단순히 "두 브랜치를 합치는 것"이 아니라, 정교한 알고리즘이 3-way merge(공통 조상·우리쪽·상대쪽)를 수행하는 과정입니다. 커널 개발에서는 기여자 수준에서 rebase가 주로 사용되지만, 메인테이너 수준에서는 merge가 필수이며 Linus의 머지 윈도우에서는 수백 건의 머지가 수행됩니다.
머지 전략 비교
| 전략 | 사용 상황 | 특징 | 옵션 |
|---|---|---|---|
ort (기본, Git 2.33+) | 일반 2-way 머지 | recursive 대체, 3배 빠름, 메모리 효율적 | -s ort |
recursive (Git <2.33) | 일반 2-way 머지 | criss-cross 머지 시 가상 base 생성 | -s recursive |
resolve | 단순 2-way 머지 | 빠르지만 criss-cross 미지원 | -s resolve |
octopus | 3개 이상 브랜치 동시 머지 | Linus 머지 윈도우에서 사용 | -s octopus |
ours | 히스토리만 기록, 상대 변경 무시 | 의도적 무시 기록용 | -s ours |
subtree | 서브트리 머지 | 서브프로젝트 통합 | -s subtree |
# fast-forward vs no-ff 비교
$ git merge feature/foo # FF 가능하면 FF (머지 커밋 없음)
$ git merge --no-ff feature/foo # 항상 머지 커밋 생성 (이력 보존)
$ git merge --ff-only feature/foo # FF 불가능하면 거부
# ort 전략 명시 (Git 2.33+)
$ git merge -s ort feature/foo
# octopus merge: 여러 브랜치 동시 머지 (Linus 스타일)
$ git merge -s octopus branch-a branch-b branch-c
# 충돌이 없을 때만 성공 — 충돌 시 자동 실패
# 충돌 해결 전략 옵션 (ort/recursive)
$ git merge -X ours feature/foo # 충돌 시 우리쪽 자동 채택
$ git merge -X theirs feature/foo # 충돌 시 상대쪽 자동 채택
$ git merge -X patience feature/foo # patience diff 알고리즘 사용
# -X ours/-X theirs는 -s ours와 다름!
# -X: 충돌 헝크만 자동 결정, -s ours: 전체 변경 무시
# zdiff3: 충돌 시 Base 내용도 표시 (강력 권장)
$ git config --global merge.conflictstyle zdiff3
# 충돌 표시 형식:
# <<<<<<< HEAD (ours)
# our changes
# ||||||| BASE (공통 조상 — zdiff3만 표시)
# original code
# =======
# their changes
# >>>>>>> feature/foo (theirs)
Merge vs Rebase: 커널 개발 가이드
| 상황 | 권장 방법 | 이유 |
|---|---|---|
| 기여자: 패치 준비 | rebase | 선형 히스토리 필수, format-patch 호환 |
| 기여자: upstream 동기화 | rebase | git pull --rebase, 머지 커밋 방지 |
| 메인테이너: 서브시스템 통합 | merge (--no-ff) | 기여자 히스토리 보존, 출처 추적 |
| Linus: 머지 윈도우 | merge | 서브시스템 트리별 분리된 머지 커밋 |
| stable: 백포팅 | cherry-pick | 선택적 커밋 이식, 머지 불필요 |
| 긴급 버그 수정 | cherry-pick | 특정 수정만 빠르게 적용 |
# 커널 메인테이너의 Pull Request 생성 (서명된 태그 기반)
$ git tag -s net-next-for-6.15 -m "Networking changes for v6.15"
$ git request-pull v6.14 \
https://git.kernel.org/.../netdev/net-next.git \
net-next-for-6.15
# 출력: Linus에게 보낼 Pull Request 메시지 (변경 요약 + diffstat)
# 머지 커밋 메시지 컨벤션 (Linus 스타일)
# Merge tag 'net-next-for-6.15' of git://git.kernel.org/.../netdev/net-next
#
# Networking changes for v6.15:
# - TCP: add congestion control improvements
# - UDP: fix fragment handling
# - driver foo: add hardware offload support
# 머지 되돌리기 (mainline에서 문제 발견 시)
$ git revert -m 1 <merge-commit>
# -m 1: 첫 번째 부모(mainline)를 기준으로 revert
# -m 2: 두 번째 부모(머지된 브랜치)를 기준으로 revert
# 머지된 브랜치를 다시 머지하려면 (revert 후 재머지)
$ git revert <revert-commit> # revert의 revert
$ git merge feature/foo # 이제 다시 머지 가능
ort(Ostensibly Recursive's Twin) 전략은 recursive를 완전히 대체합니다. 파일명 변경 감지가 O(N²) → O(N log N)으로 개선되었고, 메모리 사용량이 크게 줄었습니다. 커널처럼 8만 개 이상의 파일이 있는 저장소에서 머지 성능이 체감할 수 있을 정도로 향상됩니다. Git 2.33 이상이라면 별도 설정 없이 자동으로 ort가 사용됩니다.
패치 워크플로 (format-patch / send-email)
리눅스 커널은 이메일 기반 패치 제출을 사용합니다. git format-patch로 패치 파일을 만들고, get_maintainer.pl로 수신자를 결정한 뒤, git send-email로 전송합니다.
# 단일 패치 생성 (최신 커밋 1개)
$ git format-patch -1 HEAD
# 베이스 이후 모든 커밋을 패치 시리즈로 생성
$ git format-patch v6.14..HEAD \
--cover-letter \
--base=auto \
-o patches/v1/
# 패치 시리즈 번호 명시 (v2 재제출)
$ git format-patch v6.14..HEAD \
--cover-letter \
--base=auto \
-v 2 \
-o patches/v2/
# checkpatch로 스타일 검사
$ ./scripts/checkpatch.pl patches/v1/*.patch
# 수신자(maintainer) 자동 탐색
$ ./scripts/get_maintainer.pl patches/v1/0001-*.patch
# 패치 전송 (드라이 런으로 먼저 확인)
$ git send-email \
--to=netdev@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--dry-run \
patches/v1/
# 실제 전송
$ git send-email \
--to=netdev@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
patches/v1/
format-patch 고급 옵션
| 옵션 | 설명 | 예시 |
|---|---|---|
-v N | 버전 번호 (v2, v3 …) | -v 2 → [PATCH v2 …] |
--cover-letter | 커버 레터 자동 생성 | 0000-cover-letter.patch |
--base=auto | 베이스 커밋 자동 기록 | 메인테이너가 apply 시 활용 |
--subject-prefix | 제목 접두사 변경 | --subject-prefix="PATCH net-next" |
--in-reply-to | 특정 메시지에 답장 | v2 재제출, 리뷰 응답 시 |
--thread | 패치 시리즈를 스레드로 연결 | 커버 레터가 부모 메시지 |
--notes | git notes를 패치에 포함 | 변경 내역 메모 활용 |
--reroll-count=N | -v N의 긴 형식 | 동일 효과 |
--range-diff=<base> | 이전 버전과 범위 diff 첨부 | 리뷰어가 변경점 파악 용이 |
# v2 재제출: 이전 커버 레터에 답장 형식으로
$ git format-patch v6.14..HEAD \
-v 2 \
--cover-letter \
--base=auto \
--in-reply-to="<20240115.123456.789@lore.kernel.org>" \
--range-diff=v1-base \
-o patches/v2/
# 서브시스템 전용 접두사 사용 (net-next에 제출 시)
$ git format-patch v6.14..HEAD \
--subject-prefix="PATCH net-next" \
--cover-letter \
--base=net-next/main \
-o patches/v1/
# 커버 레터 전체 예시 (0000-cover-letter.patch 편집 내용)
# 제목 형식: [PATCH v1 0/3] net: foo: 드라이버 안정성 개선 시리즈
# 본문 구성:
## 변경 개요
# 이 시리즈는 foo 드라이버의 다음 문제를 수정합니다:
# 1. RX 핸들러의 NULL 포인터 역참조 (패치 1)
# 2. 인터럽트 핸들러 경쟁 조건 (패치 2)
# 3. TX 큐 언더런 처리 개선 (패치 3)
## 테스트
# - Ubuntu 24.04 + 커널 v6.14-rc3, x86_64
# - foo 하드웨어 모델 X200으로 24시간 네트워크 부하 테스트
# - 별도 부하 없는 환경에서 KASAN/KCSAN 활성화 후 테스트
## v2 변경 사항 (이전 v1 대비)
# - 패치 1: Kim Cheolsu의 리뷰 반영 — spin_lock_irqsave 사용
# - 패치 2: 추가 코드 정리 (불필요한 blank line 제거)
# Signed-off-by: Hong Gildong <gildong@example.com>
get_maintainer.pl 활용
# 메인테이너 역할 통계 포함
$ ./scripts/get_maintainer.pl --rolestats patches/v1/0001-*.patch
# 중복 이메일 제거
$ ./scripts/get_maintainer.pl --remove-duplicates patches/v1/*.patch
# 특정 파일의 메인테이너 확인
$ ./scripts/get_maintainer.pl -f drivers/net/ethernet/intel/igb/igb_main.c
# send-email에 자동 통합
$ git send-email \
$(./scripts/get_maintainer.pl --norolestats -f drivers/net/foo.c | \
awk '{print "--cc=" $1}') \
patches/v1/
b4 도구는 git send-email의 현대적 대안으로, 메일링 리스트에서 패치를 내려받거나 전송하는 작업을 자동화합니다:
# b4 설치
$ pip3 install b4
# 메일링 리스트에서 패치 시리즈 내려받아 적용
$ b4 am <메시지 ID 또는 lore URL>
$ b4 am https://lore.kernel.org/netdev/<message-id>/
# shazam: 다운로드 + 자동 적용 (am의 비대화형 버전)
$ b4 shazam https://lore.kernel.org/netdev/<message-id>/
# 패치 시리즈 준비 (b4 prep)
$ b4 prep -n net-foo-fixes # 새 브랜치 생성 + 메타데이터 초기화
$ b4 prep --edit-cover # 커버 레터 편집
$ b4 prep --auto-to-cc # 자동 To/Cc 설정
# 패치 전송 (b4 send)
$ b4 send --dry-run # 드라이 런
$ b4 send # 실제 전송
$ b4 send -v 2 # v2로 전송
# 리뷰어 태그(Reviewed-by 등) 수집
$ b4 trailers --apply # lore에서 태그 수집해 커밋에 적용
# lore.kernel.org에서 패치 검색
# URL 패턴: https://lore.kernel.org/<리스트>/?q=<검색어>
# 예: https://lore.kernel.org/netdev/?q=foo+driver+fix
patches/v1/0000-cover-letter.patch의 *** SUBJECT HERE *** 부분을 [PATCH v1 0/N] 시리즈 요약으로 채우고, 본문에 변경 이유, 테스트 방법, v2 이후 변경 내역을 간략히 작성하세요. 변경 내역은 --- 구분자 뒤에 작성해야 패치 적용 시 커밋 메시지에 포함되지 않습니다.
--- 뒤에 변경 이력을 아래 형식으로 작성합니다:
Changes since v1: - Patch 1: spin_lock_irqsave 사용으로 변경 (Kim Cheolsu 리뷰) - Patch 2: 오타 수정 및 주석 개선이 내용은
git am 적용 시 커밋 메시지에 포함되지 않으므로 자유롭게 작성 가능합니다.
git range-diff와 패치 버전 비교
git range-diff(Git 2.19+)는 두 개의 커밋 범위를 비교하는 명령어로, 패치 시리즈의 v1과 v2 사이의 차이를 정확히 보여줍니다. 리뷰어가 v2에서 무엇이 변경되었는지 한눈에 파악할 수 있어 커널 메일링 리스트에서 매우 유용합니다.
# range-diff 기본 사용법: 3개 인자 (base, old range, new range)
$ git range-diff v6.14..fix-v1 v6.14..fix-v2
# 또는 3-dot 형식 (base가 같을 때)
$ git range-diff fix-v1...fix-v2
# v2 패치에 range-diff 자동 포함
$ git format-patch -v 2 \
--cover-letter \
--range-diff=fix-v1 \
-o patches/v2/ \
v6.14..fix-v2
# 커버 레터에 range-diff가 자동 첨부됨
# range-diff 출력 예시
$ git range-diff v6.14..fix-v1 v6.14..fix-v2
# 1: abc1234 ! 1: def5678 net: fix null deref in foo_rx
# @@ drivers/net/foo.c
# @@ static int foo_rx(struct sk_buff *skb)
# - spin_lock(&dev->lock);
# + spin_lock_irqsave(&dev->lock, flags);
# 2: 111aaaa = 2: 222bbbb net: foo: add error logging
# 패치 파일 간 range-diff (브랜치 없이)
$ git range-diff --no-color \
<(git log --format=%H v6.14..fix-v1) \
<(git log --format=%H v6.14..fix-v2)
# b4 도구에서 range-diff 자동 생성
$ b4 prep --edit-cover # 커버 레터에 자동으로 range-diff 포함
$ b4 send -v 2 # v2 전송 시 v1과의 range-diff 첨부
git format-patch --range-diff= 옵션을 사용하면 자동으로 --- 구분자 아래에 range-diff가 추가되어, git am 적용 시 커밋 메시지에 포함되지 않습니다.
git notes: 커밋 메타데이터 관리
git notes는 커밋 객체를 수정하지 않고 부가 메타데이터를 첨부합니다. 패치 시리즈 버전 간 변경 이력을 기록하거나, 리뷰 코멘트를 커밋에 연결할 때 유용합니다.
# 커밋에 노트 추가
$ git notes add -m "v2: Kim Cheolsu 리뷰 반영 — spin_lock_irqsave 사용"
# 커밋에 노트 표시
$ git log --notes -1 HEAD
# 노트 편집
$ git notes edit HEAD
# format-patch에 notes 포함
$ git format-patch --notes -1 HEAD
# 노트 내용이 --- 아래에 추가됨 (커밋 메시지에 미포함)
# 노트를 원격에 push (기본적으로 push 안 됨)
$ git push origin refs/notes/commits
# 노트 삭제
$ git notes remove HEAD
코드 탐색 (코드 고고학)
대규모 커널 소스에서 특정 변경의 원인을 추적하는 "코드 고고학" 기법입니다. git log -S(pickaxe), git blame, git grep을 조합하면 수십만 커밋 히스토리에서도 원하는 변경점을 빠르게 찾을 수 있습니다.
# 특정 문자열이 추가/제거된 커밋 찾기 (pickaxe)
$ git log -S "foo_rx_handler" --oneline
# 정규식으로 변경 커밋 찾기
$ git log -G "rcu_read_lock\(\)" --oneline drivers/net/
# 파일 이름이 변경된 이력 포함 추적
$ git log --follow -p drivers/net/ethernet/intel/igb/igb_main.c
# 특정 함수가 변경된 커밋 추적
$ git log -L :igb_xmit_frame:drivers/net/ethernet/intel/igb/igb_main.c
# git blame: 각 줄의 마지막 수정 커밋
$ git blame -L 100,120 drivers/net/foo.c
# blame에서 공백 변경/이동 무시
$ git blame -w -M -C drivers/net/foo.c
# git grep: 소스 트리 내 문자열 검색 (빠른 색인 활용)
$ git grep "rcu_read_lock" -- "*.c"
$ git grep -n "spin_lock_irqsave" drivers/
# 기여자별 커밋 요약
$ git shortlog -sn --no-merges v6.13..v6.14
git log 주요 옵션 참조
| 옵션 | 효과 |
|---|---|
--oneline | 한 줄 요약 (짧은 SHA + 제목) |
--graph | 브랜치/머지 그래프 시각화 |
--stat | 변경 파일 목록 + 통계 |
--patch (-p) | 전체 diff 포함 |
-S <string> | 문자열이 추가/제거된 커밋 (pickaxe) |
-G <regex> | 정규식 매칭 변경이 있는 커밋 |
-L :func:file | 함수 단위 히스토리 추적 |
--follow | 파일 이름 변경 이력 추적 |
--author=<name> | 특정 작성자 커밋만 |
--since / --until | 날짜 범위 필터 (예: --since="2024-01-01") |
--merges / --no-merges | 머지 커밋만 / 머지 제외 |
--format="%H %s" | 커스텀 출력 형식 |
# 커스텀 로그 형식: 해시, 날짜, 작성자, 제목
$ git log --format="%h %ad %an: %s" --date=short -20
# TUI 도구 활용
$ tig # ncurses 기반 Git TUI (apt install tig)
$ tig drivers/net/foo.c # 특정 파일 이력
$ tig blame drivers/net/foo.c # 대화형 blame
$ lazygit # 풀기능 Git TUI (Go 기반)
# git grep 성능 활용 (색인 기반)
$ git grep -n --threads=8 "rcu_read_lock" -- "*.c"
$ git grep -l "foo_rx_handler" # 파일명만 출력
$ git grep -c "spin_lock" # 매칭 수 출력
git bisect로 버그 회귀 찾기
git bisect는 이진 탐색으로 버그를 유발한 커밋을 O(log n) 시간에 찾아줍니다. 수만 개의 커밋 사이에서도 15~20회 정도의 테스트만으로 범인을 특정할 수 있습니다.
# bisect 시작
$ git bisect start
# 현재 커밋이 나쁨 (버그 있음)
$ git bisect bad
# 정상 동작하는 커밋 (버그 없음)
$ git bisect good v6.12
# Git이 중간 커밋을 체크아웃함 — 테스트 후 판정
$ make -j$(nproc) && ./test_script.sh
$ git bisect good # 또는 git bisect bad
# 반복 후 범인 커밋 출력
# ... 자동으로 범인 커밋 SHA-1 표시 ...
# bisect 종료 (원래 브랜치로 복귀)
$ git bisect reset
자동화 bisect: 테스트를 스크립트로 자동화하면 사람 개입 없이 완전 자동으로 범인 커밋을 찾습니다:
# bisect_test.sh: 테스트 성공 시 exit 0, 실패 시 exit 1
#!/bin/bash
make -j$(nproc) bzImage 2>/dev/null || exit 125 # 125: 빌드 실패, 이 커밋 건너뜀
# QEMU로 커널 부팅 후 테스트
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd /tmp/initrd.img \
-append "console=ttyS0" \
-nographic \
-serial file:/tmp/klog.txt \
-m 512M -no-reboot \
-timeout 30 || exit 1
grep -q "TEST_PASS" /tmp/klog.txt
exit $?
# 자동 bisect 실행
$ git bisect start
$ git bisect bad HEAD
$ git bisect good v6.12
$ git bisect run ./bisect_test.sh
git bisect skip을 사용하세요. Git이 해당 커밋을 건너뛰고 인접 커밋을 선택합니다.
bisect 예상 테스트 횟수 (이진 탐색)
| 커밋 범위 | 예상 테스트 횟수 | 커널 개발 맥락 |
|---|---|---|
| 100개 | ~7회 | 소규모 서브시스템 변경 |
| 1,000개 | ~10회 | RC 단계 1개 (약 2주) |
| 10,000개 | ~14회 | 메이저 릴리스 사이클 |
| 100,000개 | ~17회 | v6.12 → v6.14 전체 (~2년) |
| 명령어 | 설명 |
|---|---|
git bisect start | 세션 시작 |
git bisect bad [SHA] | 나쁜 커밋 표시 |
git bisect good [SHA] | 좋은 커밋 표시 |
git bisect skip | 현재 커밋 건너뜀 (빌드 실패 등) |
git bisect reset | 세션 종료 (원래 HEAD로 복귀) |
git bisect log | 현재 세션 판정 이력 출력 |
git bisect replay <log> | 저장된 세션 이력으로 재현 |
git bisect run <cmd> | 스크립트로 자동 bisect |
# bisect 세션 저장 및 복구
$ git bisect log > bisect.log # 세션 이력 저장
# ... 나중에 재시작 ...
$ git bisect start
$ git bisect replay bisect.log # 이전 세션 판정 재현
# .git/BISECT_LOG, .git/BISECT_TERMS 등 세션 파일 자동 저장됨
# 빌드 실패(125) vs 버그 없음(0) vs 버그 있음(1~124) 종료 코드 규약
# exit 125 → git bisect skip (이 커밋 건너뜀)
# exit 0 → git bisect good (버그 없음)
# exit 1 → git bisect bad (버그 있음)
git worktree로 병렬 개발
git worktree는 하나의 Git 저장소에서 여러 작업 트리(체크아웃)를 동시에 관리합니다. 커널 개발에서는 메인라인 빌드를 유지하면서 서로 다른 패치 브랜치를 별도 디렉터리에서 병렬로 작업할 때 매우 유용합니다.
# 새 worktree 추가 (브랜치 자동 생성)
$ git worktree add ../linux-net-fix fix/net-foo-null-deref
# 기존 브랜치를 worktree로 추가
$ git worktree add ../linux-next linux-next/main
# worktree 목록 확인
$ git worktree list
# worktree 제거 (디렉터리도 삭제됨)
$ git worktree remove ../linux-net-fix
# 사용 예: 두 브랜치를 동시에 빌드
$ cd ../linux-net-fix && make -j$(nproc) &
$ cd ../linux && make -j$(nproc) &
git gc와 같은 저장소 정리 작업은 메인 worktree에서만 실행하세요. 각 worktree는 독립적인 작업 트리를 가지지만, .git/ 오브젝트 데이터베이스는 공유합니다.
worktree 명령어 전체
| 명령어 | 설명 |
|---|---|
git worktree add <path> [branch] | 새 worktree 추가 (브랜치 자동 생성 또는 기존 브랜치) |
git worktree list | 모든 worktree 목록 (경로, HEAD, 브랜치) |
git worktree lock <path> | worktree 잠금 (prune 방지) |
git worktree unlock <path> | 잠금 해제 |
git worktree move <path> <new> | worktree 디렉터리 이동 |
git worktree prune | 삭제된 worktree 참조 정리 |
git worktree remove <path> | worktree 제거 (변경 없을 때만) |
# ccache로 worktree 간 빌드 캐시 공유
# ~/.config/ccache/ccache.conf
# max_size = 50G
# cache_dir = /home/user/.ccache # 공유 캐시 디렉터리
$ export CC="ccache gcc"
$ export HOSTCC="ccache gcc"
$ make -j$(nproc)
# KBUILD_OUTPUT으로 빌드 결과물 분리 (소스와 빌드 디렉터리 분리)
$ mkdir -p /tmp/build-net-fix
$ export KBUILD_OUTPUT=/tmp/build-net-fix
$ make -j$(nproc) bzImage modules
# 실전: bisect 진행 중 패치 브랜치 동시 작업
$ git worktree add ../linux-bisect # bisect용 별도 트리
$ git worktree add ../linux-patch feature/net-foo-fix # 패치 작업용
# 두 worktree를 동시에 빌드
$ make -C ../linux-bisect KBUILD_OUTPUT=/tmp/build-bisect -j8 bzImage &
$ make -C ../linux-patch KBUILD_OUTPUT=/tmp/build-patch -j8 bzImage &
대규모 저장소 성능 최적화
리눅스 커널 저장소는 100만 개 이상의 커밋과 80,000개 이상의 파일을 담고 있습니다. 기본 설정으로는 git log나 git status가 눈에 띄게 느릴 수 있습니다. 다음 설정과 기능으로 성능을 크게 향상시킬 수 있습니다.
| 설정 | 효과 | 적용 범위 |
|---|---|---|
core.untrackedCache=true | 비추적 파일 목록 캐싱 | git status 속도 향상 |
core.fsmonitor=true | 파일시스템 변경 감시 (inotify/FSEvents) | git status/diff 속도 향상 |
fetch.writeCommitGraph=true | fetch 시 commit-graph 자동 갱신 | 이력 탐색 가속 |
pack.useBitmaps=true | pack 비트맵 인덱스 활성화 | clone/fetch 가속 |
pack.windowMemory=256m | pack 압축 메모리 제한 | gc 성능/메모리 균형 |
pack.deltaCacheSize=256m | delta 캐시 크기 | pack 생성 속도 향상 |
gc.auto=6700 | 느슨한 객체 6700개 이상 시 자동 gc | 기본값 (커널 저장소엔 높이기 가능) |
maintenance.auto=false | 자동 maintenance 비활성화 | 수동 제어 선호 시 |
# git maintenance 자동 설정 (Git 2.31+, 권장)
$ git maintenance start
# cron/systemd timer로 다음 작업 자동 실행:
# - prefetch: 백그라운드 fetch
# - loose-objects: 느슨한 객체 pack
# - incremental-repack: 증분 pack 재구성
# - gc: 불필요한 객체 정리
# - commit-graph: commit-graph 갱신
# - pack-refs: refs 압축
# 수동으로 maintenance 실행
$ git maintenance run --task=commit-graph
$ git maintenance run --task=pack-refs
$ git maintenance run --auto
# commit-graph: 이력 탐색 가속 파일 생성
# git log --graph, git merge-base, bisect 등이 크게 빨라짐
$ git commit-graph write --reachable --changed-paths
# --changed-paths: 각 커밋의 변경 경로를 Bloom 필터로 색인 (git log -- path 가속)
# multi-pack-index (MIDX): 여러 pack 파일을 하나의 색인으로 관리
$ git multi-pack-index write
$ git multi-pack-index verify
# 저장소 현황 확인
$ git count-objects -v
# count: 느슨한 객체 수
# size-pack: pack 크기 (kB)
# in-pack: pack에 들어간 객체 수
# 성능 설정 한 번에 적용
$ git config core.untrackedCache true
$ git config core.fsmonitor true
$ git config fetch.writeCommitGraph true
$ git config pack.useBitmaps true
git fetch --no-auto-gc로 fetch 중 자동 GC를 억제하고, git maintenance로 별도 예약 실행을 권장합니다.
서브시스템 트리 및 linux-next
리눅스 커널 개발은 계층적 트리 구조로 관리됩니다. Linus Torvalds의 메인라인이 최상위이며, 수백 명의 서브시스템 메인테이너가 각자의 트리를 관리합니다. linux-next는 다음 머지 윈도우를 위한 통합 테스트 트리입니다.
커널 개발 사이클 타임라인:
- 머지 윈도우 (~2주): 메인라인 릴리스 직후 약 2주. 각 서브시스템 메인테이너가 Linus에게 Pull Request를 보내 새 기능을 통합합니다. 이 기간 이후엔 버그 수정만 허용됩니다.
- RC 단계 (6~8주): rc1 출시부터 최종 릴리스까지. 보통 rc6~rc8 후 정식 릴리스. 각 rc는 일요일에 출시됩니다.
- 패치 수락 흐름: 기여자 이메일 → 서브시스템 메인테이너 트리 → linux-next(통합 테스트) → 머지 윈도우에서 메인라인
주요 서브시스템 트리 목록:
| 서브시스템 | 메인테이너 | 트리 |
|---|---|---|
| Networking (net-next) | Jakub Kicinski, Paolo Abeni | netdev/net-next.git |
| Networking 버그수정 (net) | Jakub Kicinski, Paolo Abeni | netdev/net.git |
| Memory Management | Andrew Morton | akpm/mm.git |
| DRM/GPU | Dave Airlie | dri-devel/drm.git |
| ARM SoC | Arnd Bergmann, Olof Johansson | soc/soc.git |
| x86 / tip | Thomas Gleixner, Ingo Molnar | tip/tip.git |
| Block Layer | Jens Axboe | axboe/linux-block.git |
| USB | Greg Kroah-Hartman | gregkh/usb.git |
| I2C/SPI | Wolfram Sang | wsa/linux.git |
| Media (V4L2) | Mauro Carvalho Chehab | mchehab/linux-media.git |
| Filesystems | Christian Brauner | vfs/vfs.git |
| linux-next | Stephen Rothwell | next/linux-next.git |
# linux-next 태그 명명 규칙: next-YYYYMMDD
$ git fetch linux-next
$ git tag -l "next-*" | sort | tail -5
# next-20240201, next-20240202, next-20240205 ...
# 특정 날짜의 linux-next 기반으로 브랜치 생성
$ git checkout -b feature/net-bar next-20240205
# Merge Window 이후 제출 패치 처리
# rc1 이후 제출된 신규 기능 패치는 다음 사이클 머지 윈도우까지 대기
# 메인테이너가 자신의 트리에서 대기(queue)시킨 후 다음 머지 윈도우에 제출
Git 훅 설정
Git 훅은 특정 Git 이벤트 전후에 자동으로 실행되는 스크립트입니다. 커널 개발에서는 pre-commit 훅으로 checkpatch.pl을 자동 실행하여 스타일 오류를 커밋 전에 차단합니다.
| 훅 이름 | 실행 시점 | 종료 코드 영향 | 커널 개발 활용 |
|---|---|---|---|
pre-commit | 커밋 직전 | 비0: 커밋 취소 | checkpatch.pl 자동 실행 |
prepare-commit-msg | 메시지 에디터 열기 전 | 비0: 커밋 취소 | 브랜치명을 메시지에 삽입 |
commit-msg | 메시지 작성 후 | 비0: 커밋 취소 | Signed-off-by 존재 확인 |
post-commit | 커밋 완료 후 | 무관 | 커밋 후 checkpatch 리포트 |
pre-push | push 직전 | 비0: push 취소 | push 전 전체 빌드 확인 |
pre-rebase | rebase 시작 전 | 비0: rebase 취소 | 보호 브랜치 rebase 방지 |
post-checkout | checkout/switch 후 | 무관 | 빌드 환경 자동 초기화 |
post-merge | merge 완료 후 | 무관 | 의존성 자동 갱신 |
# .git/hooks/pre-commit 생성
$ cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
set -e
# 스테이지된 변경 사항으로 임시 패치 생성
PATCH=$(git diff --cached)
if [ -z "$PATCH" ]; then
exit 0
fi
# checkpatch.pl 실행 (경고 허용, 에러만 차단)
echo "$PATCH" | ./scripts/checkpatch.pl --no-signoff - || {
echo "checkpatch.pl 실패. 커밋이 취소되었습니다."
exit 1
}
EOF
$ chmod +x .git/hooks/pre-commit
# .git/hooks/commit-msg: Signed-off-by 자동 확인
$ cat > .git/hooks/commit-msg << 'EOF'
#!/bin/bash
MSG_FILE=$1
if ! grep -q "^Signed-off-by:" "$MSG_FILE"; then
echo "오류: Signed-off-by 태그가 없습니다."
exit 1
fi
EOF
$ chmod +x .git/hooks/commit-msg
# post-commit: 커밋 후 자동으로 checkpatch 결과 출력 (정보용, 차단 아님)
$ cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
TMPFILE=$(mktemp /tmp/commit-check.XXXXXX.patch)
git format-patch -1 HEAD --stdout > "$TMPFILE"
if ./scripts/checkpatch.pl --quiet "$TMPFILE" 2>&1 | grep -q .; then
echo "[checkpatch 경고]"
./scripts/checkpatch.pl "$TMPFILE" 2>&1
fi
rm -f "$TMPFILE"
EOF
$ chmod +x .git/hooks/post-commit
# prepare-commit-msg: 브랜치명을 커밋 메시지 첫 줄에 삽입
$ cat > .git/hooks/prepare-commit-msg << 'EOF'
#!/bin/bash
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
# 일반 커밋(message)일 때만 브랜치명 삽입
if [ "$COMMIT_SOURCE" != "message" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
# fix/net-foo → "net: fix" 형식으로 변환 힌트 제공
if [[ "$BRANCH" =~ ^fix/([^-]+)-(.+)$ ]]; then
printf "%s: fix %s\n\n" \
"${BASH_REMATCH[1]}" \
"${BASH_REMATCH[2]//-/ }" | \
cat - "$COMMIT_MSG_FILE" > /tmp/cmsg && \
mv /tmp/cmsg "$COMMIT_MSG_FILE"
fi
fi
EOF
$ chmod +x .git/hooks/prepare-commit-msg
# pre-push: push 전 드라이버 모듈 빌드 확인
$ cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
set -e
REMOTE=$1
echo "[pre-push] $REMOTE 에 push 전 빌드 확인..."
if ! make -j$(nproc) drivers/ 2>&1 | tail -5; then
echo "빌드 실패: push 취소"
exit 1
fi
echo "빌드 성공. push 진행."
EOF
$ chmod +x .git/hooks/pre-push
.git/hooks/는 버전 관리되지 않습니다. 팀 내 훅을 공유하려면 scripts/git-hooks/처럼 저장소 내 디렉터리에 두고 git config core.hooksPath scripts/git-hooks로 경로를 지정하세요.
태그 및 릴리스
리눅스 커널은 릴리스마다 주석 태그(annotated tag)를 사용합니다. 태그를 활용하면 특정 릴리스 기반의 브랜치 생성, 두 릴리스 간 변경 비교, 회귀 버그 추적 등이 간편해집니다.
# 커널 릴리스 태그 목록 확인
$ git tag -l "v6.*" | sort -V | tail -20
# 특정 태그 정보 확인 (주석 태그)
$ git show v6.14
# 태그 간 변경 통계
$ git diff --stat v6.13..v6.14
# 두 릴리스 사이 커밋 수
$ git rev-list --count v6.13..v6.14
# 특정 파일의 두 릴리스 간 변경
$ git diff v6.13..v6.14 -- drivers/net/ethernet/intel/igb/
# 릴리스 태그 기반 브랜치 생성 (패치 제출 표준)
$ git checkout -b my-fix v6.14
# 주석 태그 생성 (내 작업용)
$ git tag -a my-series-v1 -m "패치 시리즈 v1 스냅샷"
# 태그를 원격에 push (개인 포크용)
$ git push origin my-series-v1
커널 릴리스 사이클 상세
리눅스 커널은 약 8~10주 주기로 새 버전을 릴리스합니다. 머지 윈도우 2주 + RC 단계 6~8주가 기본 패턴입니다.
| 태그 유형 | 예시 | 설명 |
|---|---|---|
| 정식 릴리스 | v6.14 | 안정 릴리스. 주석 태그 + GPG 서명 |
| 릴리스 후보 | v6.14-rc3 | 매주 일요일 출시. 기능 동결 상태 |
| stable 릴리스 | v6.6.45 | 장기 지원(LTS) 또는 일반 stable |
| linux-next 스냅샷 | next-20240205 | 통합 테스트용 (날짜 기반 태그) |
# git describe: 커밋을 가장 가까운 태그 기준으로 표현
$ git describe HEAD
# v6.14-rc3-42-g1a2b3c4d5e6f
# = v6.14-rc3 태그로부터 42개 커밋 후, 현재 SHA가 1a2b3c4...
$ git describe --tags --always # 태그 없어도 SHA 출력
# 태그 GPG 서명 검증 (커널 공식 태그는 Linus의 GPG 키로 서명)
$ gpg --locate-keys torvalds@kernel.org # 키 가져오기
$ git tag -v v6.14 # GPG 서명 검증
# LTS 커널 목록 확인 (kernel.org에서)
$ git tag -l "v*" | grep -E "^v[0-9]+\.[0-9]+$" | sort -V | tail -10
Git 서명과 신뢰 체인
리눅스 커널의 모든 공식 릴리스 태그는 Linus Torvalds의 GPG 키로 서명됩니다. 서명은 코드 무결성과 출처 인증을 보장하며, 서브시스템 메인테이너가 Pull Request를 보낼 때도 서명된 태그를 사용합니다. Git 2.34+에서는 SSH 키로도 서명할 수 있습니다.
GPG 서명 설정
# GPG 키 생성 (Ed25519 권장, RSA 4096도 가능)
$ gpg --full-gen-key
# 선택: (9) ECC (sign and encrypt) → Ed25519 → 0 (만료 없음)
# 실명과 커밋에 사용하는 이메일 입력
# 키 목록 확인
$ gpg --list-keys --keyid-format=long
# pub ed25519/1234567890ABCDEF 2024-01-01 [SC]
# AB12CD34EF56789012345678...
# uid Hong Gildong <gildong@example.com>
# Git에 서명 키 등록
$ git config --global user.signingkey 1234567890ABCDEF
$ git config --global commit.gpgsign true # 모든 커밋 자동 서명
$ git config --global tag.gpgsign true # 모든 태그 자동 서명
# GPG 에이전트 설정 (passphrase 캐시)
$ echo 'default-cache-ttl 86400' >> ~/.gnupg/gpg-agent.conf
$ gpgconf --reload gpg-agent
# 서명된 커밋 생성
$ git commit -S -s -m "net: fix null dereference"
# -S: GPG 서명 (대문자 S)
# -s: Signed-off-by (소문자 s)
# 서명된 태그 생성
$ git tag -s v1.0 -m "Release v1.0"
# 서명 검증
$ git tag -v v6.14 # 태그 서명 검증
$ git log --show-signature -1 HEAD # 커밋 서명 확인
$ git verify-commit HEAD # 커밋 서명 검증
$ git verify-tag v6.14 # 태그 서명 검증
SSH 서명 (Git 2.34+)
GPG 인프라가 부담스러운 경우 기존 SSH 키로 서명할 수 있습니다. 설정이 간단하고 별도 도구 설치가 필요 없습니다.
# SSH 서명 설정
$ git config --global gpg.format ssh
$ git config --global user.signingkey ~/.ssh/id_ed25519.pub
$ git config --global commit.gpgsign true
# 허용된 서명자 파일 생성 (검증용)
$ cat > ~/.config/git/allowed_signers << 'EOF'
gildong@example.com ssh-ed25519 AAAA... (키 내용)
cheolsu@example.com ssh-ed25519 BBBB... (키 내용)
EOF
$ git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
# SSH 서명 커밋 생성 및 검증
$ git commit -S -s -m "net: add feature"
$ git log --show-signature -1 HEAD
| 서명 방식 | 설정 복잡도 | 키 관리 | 커널 사용 | Git 최소 버전 |
|---|---|---|---|---|
| GPG (RSA/Ed25519) | 높음 (gpg 필요) | 키서버, WoT | 공식 태그 서명 | Git 1.7.9 |
| SSH | 낮음 (ssh-keygen) | allowed_signers 파일 | 개인/소규모 팀 | Git 2.34 |
| X.509 (S/MIME) | 높음 (인증서 필요) | CA 인증서 체인 | 기업 환경 | Git 2.19 |
ABAF 11C6 5A29 70B1 30AB E3C4 79BE 3E43 0041 1886입니다. gpg --locate-keys torvalds@kernel.org로 키를 가져올 수 있습니다. Greg Kroah-Hartman(stable 메인테이너)의 키는 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E입니다. 릴리스 tarball과 Git 태그 모두 이 키들로 서명됩니다.
git reflog로 실수 복구
git reflog는 모든 HEAD 이동의 로컬 기록입니다. git reset --hard로 잃어버린 커밋, 실수로 삭제한 브랜치, 잘못된 rebase 등을 복구하는 최후의 안전망입니다. reflog는 기본 90일간 보존됩니다.
| 시나리오 | 복구 방법 | 핵심 명령어 |
|---|---|---|
reset --hard로 커밋 분실 | reflog에서 SHA-1 찾아 복구 | git reset --hard HEAD@{1} |
| 브랜치 실수 삭제 | reflog에서 해당 커밋 찾아 브랜치 재생성 | git branch <name> <SHA> |
| rebase 실수 | ORIG_HEAD 또는 reflog로 이전 상태 복구 | git reset --hard ORIG_HEAD |
| 잘못된 amend | reflog에서 amend 직전 SHA 확인 | git checkout HEAD@{1} |
| stash 실수 삭제 | reflog에서 stash SHA 찾아 적용 | git stash apply <SHA> |
# HEAD의 전체 이동 이력 조회
$ git reflog
# abc1234 HEAD@{0}: commit: net: fix foo null check
# def5678 HEAD@{1}: rebase -i (finish): returning to refs/heads/fix/net-foo
# ...
# 특정 브랜치의 이동 이력
$ git reflog show fix/net-foo
# reset --hard 후 복구
$ git reflog # 이전 상태 확인
$ git reset --hard HEAD@{2} # 2번째 이전 상태로 복구
# 잘못된 rebase 취소
$ git reset --hard ORIG_HEAD # rebase 직전 상태 (ORIG_HEAD 자동 저장)
# 삭제된 브랜치 복구
$ git reflog | grep "fix/old-branch" # SHA 찾기
$ git branch fix/old-branch abc1234 # 브랜치 재생성
# 또는 checkout으로 detached HEAD에서 확인 후 브랜치 생성
$ git checkout abc1234
$ git checkout -b fix/old-branch
# reflog 보존 기간 설정 (기본 90일)
$ git config gc.reflogExpire 180 # 180일로 연장
$ git config gc.reflogExpireUnreachable 30 # 도달 불가 항목: 30일
# reflog 수동 만료 처리
$ git reflog expire --expire=90.days refs/heads/master
$ git reflog expire --expire=now --all # 모든 reflog 즉시 만료 (주의!)
# stash가 drop 된 후 복구
$ git fsck --unreachable | grep commit # 미참조 커밋 찾기
$ git show <sha> # 내용 확인
$ git stash apply <sha> # 복구
push나 clone으로 공유되지 않습니다. git gc 실행 시 만료된 reflog 항목이 삭제되므로, 중요한 복구 작업은 git gc 실행 전에 진행하세요.
ORIG_HEAD— rebase, merge, reset 전의 HEADMERGE_HEAD— 진행 중인 merge의 상대 커밋CHERRY_PICK_HEAD— 진행 중인 cherry-pick 커밋REBASE_HEAD— rebase 중 현재 적용 중인 커밋
git reset --hard ORIG_HEAD처럼 바로 활용 가능합니다.
문제 해결
커널 개발 중 자주 발생하는 Git 관련 문제와 해결 방법입니다.
| 오류 메시지 | 원인 | 해결책 |
|---|---|---|
HEAD detached at v6.14-rc3 | 브랜치 없이 커밋에 직접 위치 | git checkout -b <name>으로 브랜치 생성 |
error: failed to push some refs | 원격이 로컬보다 앞서 있음 | git pull --rebase 후 재시도 |
fatal: refusing to merge unrelated histories | 공통 조상 없는 두 저장소 병합 시도 | --allow-unrelated-histories (신중히) |
Patch does not apply (git am) | 패치 베이스와 현재 코드 불일치 | git am --reject 후 .rej 파일 수동 해결 |
cannot lock ref | 다른 Git 프로세스가 ref 잠금 | .git/refs/heads/*.lock 파일 삭제 |
Repository corrupt | 객체 데이터베이스 손상 | git fsck 후 git gc |
fatal: pack file ... pack too large | pack 파일 크기 초과 | pack.packSizeLimit 설정 |
CONFLICT (content): Merge conflict | 동일 줄이 양쪽에서 수정됨 | 에디터로 충돌 마커 해결 후 git add |
error: Your local changes would be overwritten | 비커밋 변경이 체크아웃 충돌 | git stash 후 체크아웃 |
ssh: connect to host ... port 22: Connection refused | SSH 포트 차단 | HTTPS URL로 변경 또는 443 포트 사용 |
detached HEAD 상태
# bisect나 특정 태그 체크아웃 후 detached HEAD 발생
$ git status
# HEAD detached at v6.14-rc3
# 현재 상태에서 새 브랜치 생성 (작업 보존)
$ git checkout -b my-new-branch
# 또는 기존 브랜치로 복귀
$ git checkout master
저장소 무결성 검사
# 저장소 객체 무결성 검사
$ git fsck --full
# dangling blob/commit: 미참조 객체 (정상, git gc로 정리 가능)
# missing blob/tree: 실제 손상 → 원격에서 다시 fetch 필요
# pack 파일 검증
$ git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail # 큰 객체 확인
# git gc 수준별 차이
$ git gc # 일반 정리 (빠름, 권장)
$ git gc --aggressive # 최대 압축 (느림, 저장소 이전 시 1회)
$ git gc --prune=now # 즉시 미참조 객체 제거
git am 패치 적용 실패
# 패치 적용 실패 시 처리
$ git am patches/v1/0001-net-fix.patch
# 실패 시: "Patch failed at 0001 net: fix ..."
# 방법 1: 충돌 수동 해결
$ git am --show-current-patch # 실패한 패치 내용 확인
# ... 수동으로 파일 편집 ...
$ git add <resolved-file>
$ git am --continue
# 방법 2: .rej 파일 방식
$ git am --reject patches/v1/0001-net-fix.patch
# .rej 파일에 실패한 헝크 저장 → 수동 적용 → git add → git am --continue
# 방법 3: 포기 후 처음부터
$ git am --abort # am 세션 취소
실수로 커밋된 대용량 파일 제거
# 최근 커밋에서 파일 제거 (amend로 — push 전에만)
$ git rm --cached vmlinux.o
$ git commit --amend --no-edit
# 이미 push된 경우 — git filter-repo 사용 (filter-branch 대체)
$ pip3 install git-filter-repo
$ git filter-repo --path vmlinux.o --invert-paths
# 전체 이력에서 해당 파일 제거 (팀 협의 필수)
rerere: 반복 충돌 자동 해결
# rerere (Reuse Recorded Resolution) 활성화
# 동일 충돌이 반복될 때(긴 rebase 시리즈) 자동으로 해결책 적용
$ git config rerere.enabled true
$ git config rerere.autoUpdate true # 해결 후 자동 git add
# rerere 동작: 충돌 발생 → 수동 해결 → rerere가 해결책 기록
# 이후 동일 충돌 발생 시 자동 적용
$ git rerere # 기록된 해결책 수동 적용
$ ls .git/rr-cache/ # 기록된 해결책 목록
send-email SMTP 인증 실패
# SSL 인증서 오류 임시 우회 (테스트 환경용)
$ git send-email --smtp-ssl-cert-path="" patches/v1/
# 자격 증명 캐시 초기화
$ git credential reject <<< "protocol=smtps
host=smtp.gmail.com"
# SMTP 연결 디버그 모드
$ git send-email --smtp-debug=1 --dry-run patches/v1/0001-*.patch
# Gmail OAuth2 인증 (git-credential-oauth 사용)
$ pip3 install git-credential-oauth
$ git config credential.helper oauth
# 대안: msmtp를 sendmail로 사용
$ git config sendemail.sendmailcmd "/usr/bin/msmtp --read-envelope-from -t"
$ git config sendemail.smtpserver "/usr/bin/msmtp" # sendmail 모드
대용량 저장소에서 git grep 가속
# 병렬 스레드로 grep 가속
$ git grep -n --threads=$(nproc) "rcu_read_lock"
# 특정 커밋 시점의 전체 트리에서 grep
$ git grep "foo_handler" v6.13
# 여러 패턴 동시 검색 (-e 옵션)
$ git grep -e "rcu_read_lock" --or -e "rcu_read_unlock" -- "*.c"
# 함수 정의 찾기 (C 스타일)
$ git grep -n "^[a-z].*foo_init"
# grep 결과를 파일:줄번호 형식으로 출력 (에디터 연동)
$ git grep -n "spin_lock_bh" drivers/ | head -20
# vim에서: :cfile 또는 :grep으로 quickfix 리스트 활용
병합 충돌 해결
# 충돌 파일 확인
$ git diff --name-only --diff-filter=U
# mergetool로 시각적 해결
$ git mergetool
# 충돌 해결 후
$ git add <resolved-files>
$ git rebase --continue # 또는 git merge --continue
원격에 강제 push된 브랜치 복구
# reflog로 이전 상태 확인 (로컬에 원격 ref 기록이 있을 때)
$ git reflog show origin/my-branch
# 특정 시점으로 로컬 복구
$ git checkout -b recovered-branch <SHA-1>
자주 쓰는 고급 명령어 모음
# 두 브랜치의 공통 조상 찾기
$ git merge-base feature/net-foo master
# 특정 커밋이 어떤 브랜치에 포함되는지 확인
$ git branch --contains abc1234
$ git branch -r --contains abc1234 # 원격 브랜치 포함
# 두 브랜치 간 다른 커밋만 비교 (대칭 차이)
$ git log master...feature/net-foo --oneline
# 파일의 특정 커밋 시점 내용 추출
$ git show v6.13:drivers/net/foo.c > foo-v6.13.c
# 이미 push된 커밋 되돌리기 (revert — 이력 보존)
$ git revert abc1234 -s # 반전 커밋 생성 (히스토리 안전)
$ git revert -m 1 <merge-commit> # 머지 커밋 revert (parent 1 기준)
# 깨끗한 작업 트리 확인 (스크립트에서 활용)
$ git diff --quiet && git diff --cached --quiet || echo "uncommitted changes"
# 원격 저장소의 refs만 조회 (clone 없이)
$ git ls-remote https://git.kernel.org/.../torvalds/linux.git HEAD
# 서브모듈 초기화 (커널은 없지만 드라이버 펌웨어 저장소 등)
$ git submodule update --init --recursive
# format-patch 출력을 mutt로 직접 전송 (mutt 사용자)
# ~/.muttrc 커널 개발 최적화 설정
set realname = "Hong Gildong"
set from = "gildong@example.com"
set smtp_url = "smtps://gildong@smtp.example.com:465"
set smtp_pass = "앱비밀번호"
set edit_headers = yes # 헤더 직접 편집
set send_charset = "us-ascii:utf-8"
set mime_forward = no
set mime_forward_rest = no
set include = yes
set attribution = "On %d, %n wrote:"
# 패치 인라인 회신을 위한 설정
auto_view text/x-patch text/x-diff
alternative_order text/plain text/html
# 실전: 패치 제출부터 머지까지 전체 워크플로 요약
# 1. 베이스 결정 및 브랜치 생성
$ git fetch linux-next && git checkout -b fix/net-foo next-20240205
# 2. 개발 및 커밋
$ git add -p && git commit -s
# 3. 히스토리 정리
$ git rebase -i --autosquash next-20240205
# 4. 패치 생성 및 검사
$ git format-patch --cover-letter --base=auto -o patches/v1/ next-20240205
$ ./scripts/checkpatch.pl patches/v1/*.patch
$ ./scripts/get_maintainer.pl patches/v1/0001-*.patch
# 5. 드라이 런 후 실제 전송
$ git send-email --dry-run --to=netdev@vger.kernel.org patches/v1/
$ git send-email --to=netdev@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org patches/v1/
# 6. 리뷰 후 v2 제출
$ git rebase -i next-20240205 # 피드백 반영
$ git format-patch -v 2 --cover-letter --base=auto \
--in-reply-to="<원본-커버-레터-Message-ID>" \
-o patches/v2/ next-20240205
Git diff 알고리즘과 패치 형식
git diff는 두 스냅샷 사이의 차이를 계산합니다. Git은 4가지 diff 알고리즘을 제공하며, 각각 출력 품질과 성능 특성이 다릅니다. 커널 개발에서는 histogram 알고리즘이 가독성과 정확도 면에서 가장 권장됩니다.
| 알고리즘 | 원리 | 장점 | 단점 | 복잡도 |
|---|---|---|---|---|
myers (기본) | 최소 편집 거리 (LCS) | 최소 diff 크기 보장 | 코드 이동 시 혼란스러운 헝크 | O(ND) |
minimal | Myers + 완전 최적화 | 진짜 최소 diff | 매우 느림 (대규모 파일) | O(ND) |
patience | 고유 줄 LIS(Longest Increasing Subsequence) | 함수 경계 보존 | 고유 줄이 없으면 퇴화 | O(N log N) |
histogram | 줄 빈도 히스토그램 기반 patience | 가독성 최고, 빠름 | 극소수 엣지 케이스 | O(N log N) |
# 알고리즘별 비교 (동일 변경에 대한 diff 출력 차이 확인)
$ git diff --diff-algorithm=myers HEAD~1
$ git diff --diff-algorithm=patience HEAD~1
$ git diff --diff-algorithm=histogram HEAD~1
# histogram을 전역 기본값으로 설정 (권장)
$ git config --global diff.algorithm histogram
# 단어 단위 diff (한 줄 내 변경 확인)
$ git diff --word-diff=color HEAD~1
$ git diff --word-diff=porcelain HEAD~1 # 머신 파싱용
# 이동된 코드 색상 구분 (zebra 모드)
$ git diff --color-moved=zebra HEAD~1
$ git diff --color-moved=dimmed-zebra HEAD~1 # 이동 코드 흐리게
# 공백 변경 무시
$ git diff -w HEAD~1 # 모든 공백 무시
$ git diff --ignore-blank-lines # 빈 줄 변경 무시
$ git diff -b HEAD~1 # 공백 양 변경 무시
unified diff 형식 해부
Git이 생성하는 unified diff 형식은 패치 파일의 표준입니다. 각 구성요소를 이해하면 패치 리뷰와 수동 적용이 수월해집니다.
diff --git a/drivers/net/foo.c b/drivers/net/foo.c ← 비교 대상 파일
index a1b2c3d..e5f6g7h 100644 ← blob SHA-1 + 파일 모드
--- a/drivers/net/foo.c ← 원본 (삭제 마커)
+++ b/drivers/net/foo.c ← 수정본 (추가 마커)
@@ -100,7 +100,8 @@ static int foo_rx(struct sk_buff *skb) ← 헝크 헤더
├─ -100,7 : 원본 100번째 줄부터 7줄
├─ +100,8 : 수정본 100번째 줄부터 8줄
└─ @@ 뒤 텍스트: 가장 가까운 함수 이름 (컨텍스트)
context line (변경 없음) ← 공백 시작: 컨텍스트
- old line (삭제됨) ← - 시작: 삭제
+ new line (추가됨) ← + 시작: 추가
+ another new line ← + 시작: 추가
context line ← 컨텍스트 (기본 3줄)
| diff 옵션 | 효과 | 활용 |
|---|---|---|
-U<n> | 컨텍스트 줄 수 변경 (기본 3) | -U5: 함수 맥락 더 많이 표시 |
--inter-hunk-context=<n> | 인접 헝크 간 최대 간격 | 관련 헝크 합치기 |
--function-context (-W) | 변경을 포함하는 함수 전체 표시 | 리뷰 시 전체 맥락 확인 |
--histogram | histogram 알고리즘 사용 | --diff-algorithm=histogram과 동일 |
--color-words | 단어 단위 색상 diff | 문자열/주석 변경 리뷰 |
--no-index | Git 외부 파일 비교 | git diff --no-index a.c b.c |
--binary | 바이너리 파일도 diff 출력 | 바이너리 패치 생성 |
--diff-filter=ACDMR | 상태별 필터 (Added/Deleted 등) | --diff-filter=M: 수정 파일만 |
# 함수 전체 컨텍스트로 diff (리뷰에 유용)
$ git diff -W HEAD~1 -- drivers/net/foo.c
# 두 커밋 사이 추가된 파일만 표시
$ git diff --diff-filter=A --name-only v6.13..v6.14
# diffstat: 변경 통계 요약
$ git diff --stat v6.13..v6.14
# drivers/net/foo.c | 42 +++++++++++++++++++++++---
# net/core/skbuff.c | 15 ++++++---
# 2 files changed, 47 insertions(+), 10 deletions(-)
# diff를 패치 파일로 저장 (git format-patch 없이)
$ git diff HEAD~3..HEAD > my-changes.patch
$ git apply --check my-changes.patch # 적용 가능 여부만 확인
$ git apply --stat my-changes.patch # 통계만 출력
# 3자 비교 (ours/theirs/base) — 충돌 해결 시
$ git diff :1:file.c :2:file.c # base vs ours
$ git diff :1:file.c :3:file.c # base vs theirs
$ git diff :2:file.c :3:file.c # ours vs theirs
@@ 뒤 텍스트)은 .gitattributes의 diff 드라이버로 제어합니다. 커널 소스는 이미 *.c diff=c 설정이 적용되어 C 함수 이름이 정확히 표시됩니다. 커스텀 파일 형식이라면 diff.<driver>.xfuncname에 정규식을 설정하세요.
Git 전송 프로토콜과 네트워크
Git은 저장소 간 데이터 교환에 4가지 전송 프로토콜을 지원합니다. 커널 개발에서는 HTTPS(읽기)와 SSH(쓰기)가 주로 사용됩니다.
| 프로토콜 | URL 형식 | 포트 | 인증 | 암호화 | 읽기/쓰기 | 속도 |
|---|---|---|---|---|---|---|
https:// | https://git.kernel.org/… | 443 | 사용자명/토큰 | TLS | R/W | 보통 |
ssh:// | git@kernel.org:… | 22 | SSH 키 | SSH | R/W | 빠름 |
git:// | git://git.kernel.org/… | 9418 | 없음 | 없음 | R만 | 가장 빠름 |
file:// | /path/to/repo | — | 파일 권한 | — | R/W | 최고 |
# Protocol v2 활성화 (Git 2.18+, 기본값은 v0)
$ git config --global protocol.version 2
# Protocol v2 확인 (디버그 출력)
$ GIT_TRACE_PACKET=1 git ls-remote https://git.kernel.org/.../torvalds/linux.git 2>&1 | head -5
# "version 2" 문자열이 보이면 v2 사용 중
# Protocol v2의 서버 사이드 필터링 (partial clone 지원)
$ git clone --filter=blob:none \
--single-branch --branch master \
https://git.kernel.org/.../torvalds/linux.git
# SSH 포트 변경 (기업 방화벽 우회)
$ git clone ssh://git@kernel.org:2222/pub/scm/.../linux.git
# ~/.ssh/config에서 설정:
# Host kernel.org
# Port 2222
# IdentityFile ~/.ssh/id_ed25519_kernel
# HTTPS 인증 캐싱 (반복 입력 방지)
$ git config --global credential.helper cache # 메모리 (15분)
$ git config --global credential.helper 'cache --timeout=3600' # 1시간
$ git config --global credential.helper store # 평문 파일 (비권장)
$ git config --global credential.helper libsecret # GNOME 키링
# git bundle: 오프라인 또는 대역폭 제한 환경에서 전송
$ git bundle create kernel-update.bundle v6.13..v6.14 # 증분 번들
$ git bundle create full.bundle --all # 전체 번들
$ git bundle verify kernel-update.bundle # 무결성 확인
$ git fetch kernel-update.bundle master:remote-master # 번들에서 fetch
# 대역폭 절약: fetch 시 특정 ref만
$ git fetch origin master --depth=1 # 최소 데이터
$ git fetch origin +refs/tags/v6.14:refs/tags/v6.14 # 특정 태그만
git-http-backend가 요청을 처리하며, 부분 클론·negotiation·사이드밴드 진행 표시 등을 지원합니다. 구형 서버의 dumb HTTP는 정적 파일만 제공하므로 매우 느리고 partial clone 미지원입니다. GIT_CURL_VERBOSE=1로 HTTP 통신을 디버그할 수 있습니다.
Packfile 내부 구조 심층
Git의 성능 비결은 packfile에 있습니다. 리눅스 커널처럼 100만 개 이상의 객체를 가진 저장소에서 느슨한(loose) 객체를 개별 파일로 저장하면 파일시스템 부하가 커지므로, Git은 git gc 시 모든 객체를 하나 이상의 pack 파일로 압축합니다.
| 객체 타입 | type 번호 | 설명 | 압축 방식 |
|---|---|---|---|
OBJ_COMMIT | 1 | 커밋 객체 | zlib deflate |
OBJ_TREE | 2 | 트리 (디렉터리) | zlib deflate |
OBJ_BLOB | 3 | 파일 내용 | zlib deflate |
OBJ_TAG | 4 | 주석 태그 | zlib deflate |
OBJ_OFS_DELTA | 6 | 오프셋 기반 delta | base 오프셋 + delta 명령 |
OBJ_REF_DELTA | 7 | SHA 기반 delta | base SHA-1 + delta 명령 |
Delta 압축과 체인
Git의 핵심 압축 기술은 delta 인코딩입니다. 유사한 blob 사이의 차이만 저장하여 저장소 크기를 극적으로 줄입니다.
# delta 체인 시각화: 어떤 객체가 어떤 base를 참조하는지
$ git verify-pack -v .git/objects/pack/*.idx | \
sort -k3 -n | tail -20
# SHA-1 type size size-in-pack offset depth base-SHA
# a1b2c3 blob 52480 12340 98765 3 d4e5f6
# └─ depth=3 → 3단계 delta 체인 (base → delta1 → delta2 → 이 객체)
# delta 체인 최대 깊이 설정 (기본 50)
$ git config pack.depth 50 # 깊을수록 작은 pack, 느린 조회
$ git config pack.window 250 # delta 후보 탐색 윈도우 (기본 10)
# pack 재압축 (최적화)
$ git repack -a -d -f --depth=50 --window=250
# -a: 모든 객체를 하나의 pack으로
# -d: 불필요한 팩 삭제
# -f: delta 강제 재계산
# 가장 큰 객체 찾기 (대용량 파일 제거 전 확인)
$ git verify-pack -v .git/objects/pack/*.idx | \
sort -k3 -n | tail -10 | \
while read sha type size rest; do
echo "$sha $size $(git rev-list --objects --all | grep $sha | cut -d' ' -f2)"
done
# bitmap index: clone/fetch 대폭 가속
$ git repack -a -d --write-bitmap-index
# bitmap은 각 커밋에서 도달 가능한 객체를 비트맵으로 기록
# clone 시 서버가 비트맵으로 즉시 필요 객체를 결정 → 10배 이상 가속
# multi-pack-index (MIDX): 여러 pack을 단일 색인으로 관리
$ git multi-pack-index write
$ git multi-pack-index verify
$ git multi-pack-index repack --batch-size=500m # 증분 재압축
.git/objects/pack/은 보통 하나의 거대한 pack 파일(~3.5 GB)과 idx 파일로 구성됩니다. git count-objects -v로 확인하면 약 1,000만 개 이상의 in-pack 객체가 있습니다. git gc --aggressive를 실행하면 --window=250 --depth=50으로 재압축하여 약 10~20%까지 크기를 줄일 수 있지만, 수 시간이 소요됩니다.
CI/CD와 커널 테스트 자동화
리눅스 커널 커뮤니티는 Git 워크플로와 밀접하게 통합된 여러 CI/자동화 시스템을 운영합니다. 패치를 제출하면 자동으로 빌드·테스트가 실행되고 결과가 메일링 리스트에 보고됩니다.
| 시스템 | 운영 주체 | 테스트 범위 | 결과 전달 |
|---|---|---|---|
| 0-day / LKP | Intel | 200+ 아키텍처/설정 빌드, 성능 회귀 감지 | 메일링 리스트에 자동 회신 |
| KernelCI | Linux Foundation | ARM/x86/RISC-V 실제 하드웨어 부팅 테스트 | kernelci.org 대시보드 + 메일 |
| syzbot | syzkaller 기반 커널 퍼징 (버그 자동 발견) | syzbot 메일 + syzkaller.appspot.com | |
| Patchwork | 각 서브시스템 | 패치 상태 추적 (New/Under Review/Accepted) | patchwork.kernel.org |
| LKFT | Linaro | ARM 디바이스 LTS 커널 회귀 테스트 | qa-reports.linaro.org |
| CKI | Red Hat | RHEL 관련 커널 빌드/부팅/기능 테스트 | 내부 대시보드 |
# Patchwork에서 패치 상태 확인 (API)
$ curl -s "https://patchwork.kernel.org/api/1.3/patches/?project=1&q=foo+driver" | \
python3 -m json.tool | head -30
# b4 도구로 patchwork 연동
$ b4 am -P https://patchwork.kernel.org/patch/12345/
# syzbot 재현기(reproducer) 테스트
# syzbot이 버그 보고 시 C 재현기를 첨부함
$ wget https://syzkaller.appspot.com/text?tag=ReproC&x=...
$ gcc -o repro repro.c -lpthread
$ ./repro # QEMU 환경에서 실행하여 버그 재현
# 로컬 CI 자동화: pre-push 훅으로 빌드 + checkpatch
$ cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
set -e
echo "[pre-push] Running build check..."
make -j$(nproc) W=1 drivers/net/ 2>&1 | tail -10
echo "[pre-push] Running checkpatch..."
git format-patch -1 HEAD --stdout | ./scripts/checkpatch.pl -
echo "[pre-push] All checks passed."
EOF
$ chmod +x .git/hooks/pre-push
# GitHub Actions / GitLab CI에서 커널 빌드 (개인 포크용)
# .github/workflows/kernel-build.yml 예시:
# jobs:
# build:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# with: { fetch-depth: 0 }
# - run: |
# sudo apt-get install -y build-essential flex bison libelf-dev
# make defconfig
# make -j$(nproc) 2>&1 | tail -20
[PATCH] kernel test robot: ... 형식으로 보고합니다. 빌드 경고(W=1), sparse 경고, smatch 경고, Coccinelle 패턴 매칭 결과가 포함됩니다. 보고서의 .config 파일을 다운로드하여 동일 환경에서 재현할 수 있습니다.
흔한 실수와 안티패턴
커널 개발자, 특히 처음 패치를 제출하는 기여자가 자주 저지르는 Git 관련 실수와 올바른 대처법을 정리합니다.
| # | 실수 | 증상 | 올바른 방법 |
|---|---|---|---|
| 1 | master 브랜치에서 직접 개발 | rebase 충돌, 패치 베이스 불분명 | 항상 릴리스 태그에서 토픽 브랜치 분기:git checkout -b fix/foo v6.14 |
| 2 | 잘못된 베이스 커밋 | 메인테이너가 패치 적용 불가 | --base=auto로 베이스 명시, 서브시스템 트리의 최신 태그 사용 |
| 3 | 머지 커밋을 포함한 패치 시리즈 | format-patch 출력이 비정상 |
rebase로 선형 히스토리 유지, 절대로 git merge 사용 금지 |
| 4 | 하나의 커밋에 여러 변경 혼합 | 리뷰어가 부분 승인 불가, bisect 어려움 | git add -p로 논리 단위 분리, 각 커밋은 하나의 논리적 변경만 |
| 5 | Signed-off-by 누락 | 메인테이너가 즉시 거부 | git commit -s 또는 [format] signoff = true |
| 6 | 커밋 메시지에 접두사 누락 | 메인테이너 검토 지연, 수정 요청 | git log --oneline -- <파일>로 기존 접두사 패턴 확인 |
| 7 | 이미 push된 브랜치를 force push | 다른 개발자의 작업 파손 | --force-with-lease 사용, 공유 브랜치에는 절대 force push 금지 |
| 8 | v2 재제출 시 새 스레드 시작 | 리뷰어가 이전 논의와 연결 불가 | --in-reply-to로 v1 커버 레터에 답장 형식으로 전송 |
| 9 | checkpatch 경고 무시 | 메인테이너가 스타일 수정 요청 반복 | 제출 전 반드시 checkpatch.pl 통과, 경고도 가능한 해결 |
| 10 | 얕은 클론에서 bisect 시도 | 이력 부족으로 bisect 실패 | 전체 클론 또는 git fetch --unshallow |
| 11 | 대용량 바이너리 커밋 | 저장소 영구 비대화 | .gitignore에 빌드 산출물 등록, git filter-repo로 제거 |
| 12 | git pull (merge) 사용 | 불필요한 머지 커밋, 히스토리 오염 | git pull --rebase 또는 git fetch + git rebase |
# 안티패턴 방지를 위한 글로벌 설정
# ❌ git pull의 머지 커밋 방지
$ git config --global pull.rebase true
# ❌ 실수로 master에 직접 커밋 방지 (pre-commit 훅)
$ cat > .git/hooks/pre-commit << 'HOOK'
#!/bin/bash
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" = "master" ] || [ "$BRANCH" = "main" ]; then
echo "❌ master/main 브랜치에 직접 커밋할 수 없습니다."
echo "토픽 브랜치를 먼저 생성하세요: git checkout -b fix/my-fix v6.14"
exit 1
fi
HOOK
$ chmod +x .git/hooks/pre-commit
# ❌ force push 사고 방지
$ git config --global push.default current
$ git config --global alias.pushf "push --force-with-lease"
# ❌ 커밋 접두사 확인 (기존 패턴 참조)
$ git log --oneline -20 -- drivers/net/foo.c
# 출력에서 접두사 패턴 확인 후 동일하게 사용:
# a1b2c3d net: foo: fix null check
# d4e5f6g net: foo: add bar feature
# → 접두사는 "net: foo:" 사용
# ❌ 패치 제출 전 최종 체크리스트 스크립트
$ cat > ~/bin/kernel-patch-check.sh << 'EOF'
#!/bin/bash
set -e
BASE=${1:-v6.14}
echo "=== 커밋 수 확인 ==="
git log --oneline $BASE..HEAD
echo "=== 머지 커밋 확인 ==="
MERGES=$(git log --merges --oneline $BASE..HEAD | wc -l)
[ "$MERGES" -gt 0 ] && echo "❌ 머지 커밋 $MERGES개 발견! rebase 필요" && exit 1
echo "=== Signed-off-by 확인 ==="
git log $BASE..HEAD --format=%B | grep -c "Signed-off-by:" || echo "❌ Signed-off-by 누락"
echo "=== format-patch + checkpatch ==="
git format-patch --base=auto -o /tmp/patch-check/ $BASE
./scripts/checkpatch.pl /tmp/patch-check/*.patch
echo "=== ✅ 모든 검사 통과 ==="
rm -rf /tmp/patch-check/
EOF
$ chmod +x ~/bin/kernel-patch-check.sh
rebase한 뒤 git push --force하면, 다른 개발자가 해당 브랜치를 추적 중일 때 모든 참조가 깨집니다. 커널 개발에서는 원칙적으로 force push를 하지 않으며, 패치를 수정하려면 새로운 버전(v2, v3)으로 format-patch를 재생성합니다.
관련 문서
- 커널 패치 제출 — format-patch, send-email, 메일링 리스트 완전 가이드
- 커널 개발 도구 — Git 워크플로, GCC/Clang, QEMU, sparse, KUnit 통합 도구
- 개발 환경 설정 — Git 설치부터 QEMU 기반 테스트 환경 구성
- 소스 코드 읽기 — git grep, git log를 활용한 커널 소스 탐색
- 코딩 스타일 — checkpatch.pl, 커밋 메시지 규약
- 디버깅 & 트러블슈팅 — git bisect와 커널 디버깅 도구 연계
- GCC 완전 가이드 — 커널 빌드와 크로스 컴파일
관련 문서
- 크래시 분석 심화 — panic/oops/call trace 해석, crashkernel/kdump vmcore
- C 언어 완전 가이드 & 커널 C 관용어 — GNU C 확장과 커널 관용어를 중심으로 container_of·READ_ONCE·barr
- 커널 필수 함수·매크로·심볼 레퍼런스 — 커널 개발에서 반복적으로 쓰는 함수·매크로·심볼을 컨텍스트별로 무제한 확장 정리한 실전 레