Git — 리눅스 커널 개발을 위한 Git 완전 가이드

리눅스 커널 커뮤니티의 메일 기반 협업에 맞춘 Git 운용 절차를 전체 흐름으로 다룹니다. upstream 추적과 토픽 브랜치 분리, 커밋 단위 분해와 메시지 작성 규약, `format-patch`/`send-email` 리뷰 루프, v2/v3 재전송 관리, `rebase` 및 충돌 해결, `bisect` 회귀 원인 탐색, `worktree` 병렬 작업, stable 백포트·서브시스템 트리 머지 정책까지 실제 유지보수 업무 기준으로 상세히 설명합니다.

전제 조건: 개발 환경 설정 문서를 먼저 읽으세요. Git 기본 명령어(add, commit, push, pull)에 어느 정도 익숙하다고 가정합니다. 커널 패치 제출 전체 절차는 패치 제출 문서에서 다룹니다.
일상 비유: Git은 타임머신 + 협업 우체국과 같습니다. 모든 변경 이력을 사진처럼 저장해 과거 어느 시점으로도 돌아갈 수 있고(타임머신), 변경 내용을 패치(편지) 형태로 메일링 리스트에 보내 리뷰를 받을 수 있습니다(우체국). 리눅스 커널은 GitHub 같은 Pull Request 방식이 아닌 이메일 기반 패치 워크플로를 사용하기 때문에 Git의 이메일 관련 기능이 특히 중요합니다.

핵심 요약

  • 저장소(Repository) — 프로젝트의 모든 파일과 변경 이력을 담는 컨테이너. .git/ 디렉터리에 저장됩니다.
  • 커밋(Commit) — 특정 시점의 스냅샷. SHA-1/SHA-256 해시로 식별되며, 변경 내용·작성자·날짜·부모 커밋 정보를 포함합니다.
  • 브랜치(Branch) — 커밋 그래프에서의 이동 포인터. 커널 개발에서는 패치 시리즈마다 전용 브랜치를 만드는 것이 관례입니다.
  • 원격(Remote) — 네트워크 상의 저장소 참조. origin(자신의 포크)과 upstream(Linus 트리)을 동시에 등록하는 것이 일반적입니다.
  • 패치(Patch)git format-patch로 생성되는 이메일 형식의 차이점 파일. 커널 메일링 리스트에 git send-email로 전송합니다.

단계별 이해

  1. 설치 및 초기 설정
    이름, 이메일, 에디터, send-email SMTP 설정을 ~/.gitconfig에 등록합니다. 커널 개발에 최적화된 설정을 한 번만 구성해 두면 이후 모든 작업이 편리해집니다.
  2. 커널 소스 클론과 원격 저장소 관리
    Linus 메인라인 트리나 서브시스템 트리를 클론하고, linux-next 추적 원격을 추가합니다. 얕은 클론(shallow clone)은 네트워크 비용을 줄여주지만 bisect에 제약이 있습니다.
  3. 패치 브랜치 생성과 커밋
    안정적인 베이스 태그(예: v6.14)에서 브랜치를 만들고 변경 사항을 커밋합니다. 커밋 메시지는 커널 규약(subsystem: 요약, Signed-off-by)을 철저히 따릅니다.
  4. format-patch와 send-email
    git format-patch로 패치 파일을 생성하고, checkpatch.pl로 스타일을 검사한 뒤, git send-email로 적합한 메인테이너에게 전송합니다.
  5. 리뷰 피드백 반영과 재제출
    리뷰어 의견을 반영해 git rebase -i로 커밋을 정리하고, 버전 번호를 올려(v2, v3) 재제출합니다. 이 과정이 리눅스 커널 패치 기여의 핵심입니다.

Git 개요 및 커널 개발에서의 역할

Git은 2005년 리눅스 커널 개발자들이 기존 버전 관리 시스템(BitKeeper)과의 계약 문제로 대안이 필요해지자 리누스 토르발즈(Linus Torvalds)가 직접 설계한 분산 버전 관리 시스템(DVCS)입니다. 설계 목표는 ① 속도, ② 단순한 설계, ③ 비선형 개발(수천 개의 병렬 브랜치) 지원, ④ 완전한 분산 운영, ⑤ 리눅스 커널 규모(수만 개 파일, 수백만 줄)의 프로젝트 처리였습니다.

Blob 파일 내용 Tree 디렉터리 구조 Tree 서브디렉터리 Commit author/committer parent SHA-1 Tag 주석 태그 (v6.x) Branch 이동 포인터 Git 객체 모델: Blob → Tree → Commit ← Tag / Branch

커널 개발에서 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 해시 체인리비전 번호 (위조 가능)
SHA-256 전환: 리눅스 커널은 현재 SHA-1 객체 모델을 사용합니다. Git 2.29부터 --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) 위에 버전 관리 인터페이스를 얹은 구조입니다.

.git/ objects/ blob/tree/commit/tag refs/ heads/ remotes/ tags/ HEAD 현재 브랜치 포인터 index 스테이징 영역(캐시) config / hooks/ blob (파일 내용) tree (디렉터리) commit (스냅샷) tag (주석 태그) pack/*.pack+.idx .git/ 디렉터리 구조와 Git 객체 유형
파일/디렉터리역할비고
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_HEADrebase/merge 전 HEAD 저장 (복구용)git reset ORIG_HEAD로 되돌리기

4종 Git 객체

Git의 모든 데이터는 4종류의 불변 객체로 표현됩니다. 각 객체는 type + 크기 + 내용의 SHA-1 해시로 식별됩니다.

# 객체 타입과 내용 조회
$ 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 내용 확인
packed-refs 파일: refs 수가 많아지면 Git은 .git/refs/ 개별 파일 대신 .git/packed-refs 단일 파일에 모든 ref를 저장합니다. git pack-refs --all로 수동 압축할 수 있습니다. 커널 저장소처럼 태그가 수천 개인 경우 이 최적화가 중요합니다.

설치 및 초기 설정

커널 개발을 시작하기 전에 Git을 올바르게 설정해야 합니다. 특히 user.nameuser.emailSigned-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
팁 — Gmail 앱 비밀번호: Gmail 2단계 인증을 사용하는 경우 일반 비밀번호는 사용할 수 없습니다. Google 계정 보안 설정에서 앱 비밀번호를 생성하거나, git credentialgnome-keyring 또는 pass를 연동하여 안전하게 저장하세요.

gitconfig 주요 섹션 참조

섹션옵션권장값설명
[core]editorvim커밋 메시지 에디터
autocrlfinputLF 유지 (커널은 LF만 허용)
whitespacetrailing-space,space-before-tab공백 오류 감지
[diff]algorithmhistogrampatience보다 가독성 좋은 diff
colorMovedzebra이동한 코드 블록 색상 구분
[rebase]autoSquashtruefixup!/squash! 커밋 자동 정렬
updateRefstrue의존 브랜치 ref 자동 갱신 (Git 2.38+)
[merge]toolvimdiff충돌 해결 도구
conflictstylezdiff3공통 조상까지 보여주는 충돌 표시 (Git 2.35+)
[push]defaultcurrent현재 브랜치만 push
autoSetupRemotetrue추적 브랜치 자동 설정 (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
kernel.org mirrors: kernel.org가 느릴 경우 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:noneblobless 클론 (blob 지연 다운로드)서버가 부분 클론 지원해야 함
--filter=tree:0treeless 클론 (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'
sparse-checkout과 빌드: 커널 빌드는 전체 소스 트리가 필요합니다. sparse-checkout은 소스 탐색·grep·blame 목적으로 사용하고, 실제 빌드는 전체 클론 또는 blobless 클론 환경에서 진행하세요. blobless 클론은 빌드 시 필요한 blob을 자동으로 다운로드합니다.

기본 워크플로우

커널 개발의 일상적인 Git 명령어 흐름입니다. 변경 전에 항상 git statusgit 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
메인라인 Linus 트리 (master) net-next 서브시스템 트리 mm-stable 서브시스템 트리 feature/net-offload fix/net-null-deref wip/mm-slab-rework 기여자 패치 이메일 커널 브랜치 계층: 기여자 브랜치 → 서브시스템 트리 → 메인라인
# 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 loggit 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-byDCO(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자리 이상 사용 권장
커밋 제목 50자 규칙: 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       # 패치 파일 적용 + 커밋 (작성자 정보 유지)
stable 백포팅 기준: Greg Kroah-Hartman의 stable 커널 규칙에 따르면, 백포팅 커밋은 ① 명확한 버그 수정, ② 메인라인에 이미 포함, ③ 회귀 유발 가능성이 낮아야 합니다. 커밋 메시지에 Cc: stable@vger.kernel.org를 추가하거나 별도로 stable@vger.kernel.org에 이메일을 보내 백포팅을 요청합니다. Fixes: 태그가 있는 커밋은 stable 봇이 자동으로 백포팅 후보로 분류합니다.

Git merge 전략 심층

Git의 머지는 단순히 "두 브랜치를 합치는 것"이 아니라, 정교한 알고리즘이 3-way merge(공통 조상·우리쪽·상대쪽)를 수행하는 과정입니다. 커널 개발에서는 기여자 수준에서 rebase가 주로 사용되지만, 메인테이너 수준에서는 merge가 필수이며 Linus의 머지 윈도우에서는 수백 건의 머지가 수행됩니다.

3-Way Merge: 공통 조상(Base) + Ours + Theirs Base 공통 조상 Ours 현재 브랜치 Theirs 머지 대상 Merge 3-Way Merge 판정 규칙 Base = Ours ≠ Theirs → Theirs 채택 Base = Theirs ≠ Ours → Ours 채택 Base ≠ Ours = Theirs → 동일 변경, 채택 Base ≠ Ours ≠ Theirs → ⚠ 충돌! Base = Ours = Theirs → 변경 없음 zdiff3: 충돌 시 Base 내용도 표시 → merge.conflictstyle = zdiff3 권장 Git이 양쪽 변경을 자동 병합, 충돌 시 수동 해결 필요

머지 전략 비교

전략사용 상황특징옵션
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
octopus3개 이상 브랜치 동시 머지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 동기화rebasegit 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 vs recursive: Git 2.33(2021)에서 도입된 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패치 시리즈를 스레드로 연결커버 레터가 부모 메시지
--notesgit 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 이후 변경 내역을 간략히 작성하세요. 변경 내역은 --- 구분자 뒤에 작성해야 패치 적용 시 커밋 메시지에 포함되지 않습니다.
패치 버전 관리 관례: 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: 패치 시리즈 v1 ↔ v2 비교 v1 시리즈 (v6.14..fix-v1) 1/3: fix A 2/3: fix B 3/3: fix C v2 시리즈 (v6.14..fix-v2) 1/3: fix A' 2/3: fix B 3/3: fix C' 변경됨 동일 변경됨 range-diff 출력 1: abc1234 ! 1: def5678 fix A 2: 111aaaa = 2: 222bbbb fix B 3: 333cccc ! 3: 444dddd fix C = : 커밋 동일 (변경 없음) ! : 커밋 수정됨 (diff 출력) + : 새로 추가된 커밋 - : 삭제된 커밋 range-diff로 리뷰어에게 v1→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 첨부
리뷰어를 위한 배려: v2 이후 재제출 시 커버 레터에 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
skip 명령어: 빌드 오류나 무관한 이유로 특정 커밋을 테스트할 수 없을 때 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) &
주의: 동일한 브랜치를 두 worktree에서 동시에 체크아웃할 수 없습니다. 또한, 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 loggit status가 눈에 띄게 느릴 수 있습니다. 다음 설정과 기능으로 성능을 크게 향상시킬 수 있습니다.

설정효과적용 범위
core.untrackedCache=true비추적 파일 목록 캐싱git status 속도 향상
core.fsmonitor=true파일시스템 변경 감시 (inotify/FSEvents)git status/diff 속도 향상
fetch.writeCommitGraph=truefetch 시 commit-graph 자동 갱신이력 탐색 가속
pack.useBitmaps=truepack 비트맵 인덱스 활성화clone/fetch 가속
pack.windowMemory=256mpack 압축 메모리 제한gc 성능/메모리 균형
pack.deltaCacheSize=256mdelta 캐시 크기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
fetch 시 GC 억제: 커널처럼 큰 저장소에서 빈번한 fetch 후 자동 GC가 실행되면 지연이 발생합니다. git fetch --no-auto-gc로 fetch 중 자동 GC를 억제하고, git maintenance로 별도 예약 실행을 권장합니다.

서브시스템 트리 및 linux-next

리눅스 커널 개발은 계층적 트리 구조로 관리됩니다. Linus Torvalds의 메인라인이 최상위이며, 수백 명의 서브시스템 메인테이너가 각자의 트리를 관리합니다. linux-next는 다음 머지 윈도우를 위한 통합 테스트 트리입니다.

머지 윈도우 (~2주) RC 단계 rc1 → rc2 → ... → rc8 릴리스 (v6.x) net-next networking mm-stable memory drm/drm-next GPU/DRM arm-soc ARM SoC linux-next 통합 테스트 기여자 패치 (format-patch) 서브시스템 트리 → linux-next 통합 → 머지 윈도우에서 메인라인으로

커널 개발 사이클 타임라인:

주요 서브시스템 트리 목록:

서브시스템메인테이너트리
Networking (net-next)Jakub Kicinski, Paolo Abeninetdev/net-next.git
Networking 버그수정 (net)Jakub Kicinski, Paolo Abeninetdev/net.git
Memory ManagementAndrew Mortonakpm/mm.git
DRM/GPUDave Airliedri-devel/drm.git
ARM SoCArnd Bergmann, Olof Johanssonsoc/soc.git
x86 / tipThomas Gleixner, Ingo Molnartip/tip.git
Block LayerJens Axboeaxboe/linux-block.git
USBGreg Kroah-Hartmangregkh/usb.git
I2C/SPIWolfram Sangwsa/linux.git
Media (V4L2)Mauro Carvalho Chehabmchehab/linux-media.git
FilesystemsChristian Braunervfs/vfs.git
linux-nextStephen Rothwellnext/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-pushpush 직전비0: push 취소push 전 전체 빌드 확인
pre-rebaserebase 시작 전비0: rebase 취소보호 브랜치 rebase 방지
post-checkoutcheckout/switch 후무관빌드 환경 자동 초기화
post-mergemerge 완료 후무관의존성 자동 갱신
# .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 키로도 서명할 수 있습니다.

커널 태그 서명 신뢰 체인 Linus Torvalds GPG: ABAF11C6… v6.x 태그 서명 v6.14 태그 GPG 서명 포함 git tag -v 서명 검증 ✓ 서브시스템 메인테이너 자체 GPG/SSH 키 PR 태그 서명 net-next-6.15-rc1 서명된 PR 태그 git pull --verify 서명 확인 후 머지 SSH 서명 Git 2.34+ 대안 GPG 없이 가능 태그 서명 → 검증: 코드 무결성 + 출처 인증의 신뢰 체인

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
Linus의 GPG 키: Linus Torvalds의 커널 서명 키는 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
잘못된 amendreflog에서 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>                  # 복구
reflog는 로컬 전용: reflog 데이터는 pushclone으로 공유되지 않습니다. git gc 실행 시 만료된 reflog 항목이 삭제되므로, 중요한 복구 작업은 git gc 실행 전에 진행하세요.
ORIG_HEAD / MERGE_HEAD / CHERRY_PICK_HEAD: Git은 위험한 작업 전에 자동으로 이전 상태를 특수 ref에 저장합니다.
  • ORIG_HEAD — rebase, merge, reset 전의 HEAD
  • MERGE_HEAD — 진행 중인 merge의 상대 커밋
  • CHERRY_PICK_HEAD — 진행 중인 cherry-pick 커밋
  • REBASE_HEAD — rebase 중 현재 적용 중인 커밋
이 ref들은 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 fsckgit gc
fatal: pack file ... pack too largepack 파일 크기 초과pack.packSizeLimit 설정
CONFLICT (content): Merge conflict동일 줄이 양쪽에서 수정됨에디터로 충돌 마커 해결 후 git add
error: Your local changes would be overwritten비커밋 변경이 체크아웃 충돌git stash 후 체크아웃
ssh: connect to host ... port 22: Connection refusedSSH 포트 차단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 알고리즘이 가독성과 정확도 면에서 가장 권장됩니다.

diff 알고리즘 비교: 동일 입력에 대한 출력 차이 원본 (a/file.c) void foo(void) { int x = 1; bar(x); return; } void baz(void) { 수정 (b/file.c) void foo(void) { int x = 2; log(x); bar(x); } void baz(void) { Myers (기본) • 최소 편집 거리(LCS) 기반 • 빠르지만 코드 이동 시 혼란스러운 diff • O(ND) — N=파일 크기, D=차이 수 • 함수 경계를 넘는 헝크 발생 가능 Patience • 고유한 줄을 앵커로 LIS 계산 • 함수 경계 보존에 유리 • 빈 줄/중괄호만 있는 영역에서 약점 Histogram (권장) • Patience 개선 — 저빈도 줄 우선 매칭 • 코드 이동/복사 시 가독성 최고 • JGit에서 유래, Git 1.7.7+ • 커널 개발 표준 (diff.algorithm=histogram) 커널 개발 권장 설정 [diff] algorithm = histogram diff 알고리즘 선택이 패치 가독성에 직접 영향
알고리즘원리장점단점복잡도
myers (기본)최소 편집 거리 (LCS)최소 diff 크기 보장코드 이동 시 혼란스러운 헝크O(ND)
minimalMyers + 완전 최적화진짜 최소 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)변경을 포함하는 함수 전체 표시리뷰 시 전체 맥락 확인
--histogramhistogram 알고리즘 사용--diff-algorithm=histogram과 동일
--color-words단어 단위 색상 diff문자열/주석 변경 리뷰
--no-indexGit 외부 파일 비교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
xfuncname 커스터마이즈: diff 헝크 헤더의 함수명(@@ 뒤 텍스트)은 .gitattributesdiff 드라이버로 제어합니다. 커널 소스는 이미 *.c diff=c 설정이 적용되어 C 함수 이름이 정확히 표시됩니다. 커스텀 파일 형식이라면 diff.<driver>.xfuncname에 정규식을 설정하세요.

Git 전송 프로토콜과 네트워크

Git은 저장소 간 데이터 교환에 4가지 전송 프로토콜을 지원합니다. 커널 개발에서는 HTTPS(읽기)와 SSH(쓰기)가 주로 사용됩니다.

Git 클라이언트 fetch / push clone / ls-remote HTTPS (443) 암호화 ✓ 인증 ✓ 방화벽 통과 ✓ SSH (22) 암호화 ✓ 인증 ✓ 키 기반 git:// (9418) 읽기 전용 ✓ 인증 ✗ file:// (로컬) 네트워크 불필요 kernel.org git-http-backend git-daemon / sshd Protocol v2 Git 2.18+ (2018) 서버 사이드 필터링 Git 전송 프로토콜: 클라이언트 → 프로토콜 선택 → 서버
프로토콜URL 형식포트인증암호화읽기/쓰기속도
https://https://git.kernel.org/…443사용자명/토큰TLSR/W보통
ssh://git@kernel.org:…22SSH 키SSHR/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      # 특정 태그만
smart HTTP vs dumb HTTP: 현대 Git 서버(kernel.org 포함)는 smart HTTP를 사용합니다. 서버 측 git-http-backend가 요청을 처리하며, 부분 클론·negotiation·사이드밴드 진행 표시 등을 지원합니다. 구형 서버의 dumb HTTP는 정적 파일만 제공하므로 매우 느리고 partial clone 미지원입니다. GIT_CURL_VERBOSE=1로 HTTP 통신을 디버그할 수 있습니다.

Packfile 내부 구조 심층

Git의 성능 비결은 packfile에 있습니다. 리눅스 커널처럼 100만 개 이상의 객체를 가진 저장소에서 느슨한(loose) 객체를 개별 파일로 저장하면 파일시스템 부하가 커지므로, Git은 git gc 시 모든 객체를 하나 이상의 pack 파일로 압축합니다.

Packfile (.pack) 과 Index (.idx) 구조 .pack 파일 Header: PACK + version(2) + object count OBJ_COMMIT (type=1) zlib 압축된 커밋 데이터 OBJ_TREE (type=2) zlib 압축된 트리 데이터 OBJ_BLOB (type=3) zlib 압축된 파일 내용 OBJ_OFS_DELTA (type=6) base 오프셋 + delta 명령어 (copy/insert) SHA-1 Checksum (20 bytes) .idx 파일 (v2) Fanout Table (256 × 4 bytes) SHA 첫 바이트별 누적 객체 수 — 이진 탐색 가속 SHA-1 정렬 목록 (20 bytes × N) CRC32 체크섬 (4 bytes × N) Pack 내 오프셋 (4 bytes × N) Large Offset (8 bytes, 2GB 초과 pack용) Pack SHA-1 + Index SHA-1 offset .idx의 오프셋이 .pack 내 객체 위치를 가리킴
객체 타입type 번호설명압축 방식
OBJ_COMMIT1커밋 객체zlib deflate
OBJ_TREE2트리 (디렉터리)zlib deflate
OBJ_BLOB3파일 내용zlib deflate
OBJ_TAG4주석 태그zlib deflate
OBJ_OFS_DELTA6오프셋 기반 deltabase 오프셋 + delta 명령
OBJ_REF_DELTA7SHA 기반 deltabase 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  # 증분 재압축
커널 저장소 pack 통계: 리눅스 커널의 .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/자동화 시스템을 운영합니다. 패치를 제출하면 자동으로 빌드·테스트가 실행되고 결과가 메일링 리스트에 보고됩니다.

커널 패치 CI/CD 파이프라인 개발자 git send-email 메일링 리스트 lkml / netdev / … Patchwork 패치 추적 시스템 0-day (Intel) 빌드 + 성능 테스트 KernelCI 다중 아키텍처 테스트 syzbot (Google) 퍼징 테스트 메인테이너 리뷰 + 적용 서브시스템 트리 git pull request linux-next 통합 빌드 테스트 자동 테스트 결과 피드백 패치 제출 → 자동 CI → 리뷰 → 서브시스템 트리 → linux-next
시스템운영 주체테스트 범위결과 전달
0-day / LKPIntel200+ 아키텍처/설정 빌드, 성능 회귀 감지메일링 리스트에 자동 회신
KernelCILinux FoundationARM/x86/RISC-V 실제 하드웨어 부팅 테스트kernelci.org 대시보드 + 메일
syzbotGooglesyzkaller 기반 커널 퍼징 (버그 자동 발견)syzbot 메일 + syzkaller.appspot.com
Patchwork각 서브시스템패치 상태 추적 (New/Under Review/Accepted)patchwork.kernel.org
LKFTLinaroARM 디바이스 LTS 커널 회귀 테스트qa-reports.linaro.org
CKIRed HatRHEL 관련 커널 빌드/부팅/기능 테스트내부 대시보드
# 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
0-day bot 결과 읽기: Intel 0-day bot이 패치에 문제를 발견하면 메일링 리스트에 [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
가장 치명적인 실수 — 히스토리 재작성 후 force push: 이미 메일링 리스트에 보낸 패치의 원본 브랜치를 rebase한 뒤 git push --force하면, 다른 개발자가 해당 브랜치를 추적 중일 때 모든 참조가 깨집니다. 커널 개발에서는 원칙적으로 force push를 하지 않으며, 패치를 수정하려면 새로운 버전(v2, v3)으로 format-patch를 재생성합니다.
이 페이지와 함께 읽으면 좋은 문서들: