Skip to content

Split View: Git 마스터 가이드 — 기초부터 rebase, cherry-pick, bisect, worktree까지

|

Git 마스터 가이드 — 기초부터 rebase, cherry-pick, bisect, worktree까지

들어가며

Git은 개발자가 매일 사용하지만, 대부분은 add-commit-push 루프에서 벗어나지 못한다. merge 충돌이 발생하면 당황하고, rebase는 두렵고, bisect나 worktree의 존재조차 모르는 경우가 많다.

이 글에서는 Git의 내부 구조부터 시작해서 실무에서 반드시 알아야 할 고급 명령어, 브랜칭 전략, PR 리뷰 문화, 모노레포 관리, 위기 상황 대처법까지 Git의 모든 것을 350줄 이상에 걸쳐 총정리한다.


1. Git 내부 구조 이해하기

Git을 제대로 쓰려면 내부 구조를 이해해야 한다. Git은 본질적으로 **키-값 저장소(content-addressable filesystem)**이다.

1.1 네 가지 핵심 객체

Git의 모든 데이터는 네 가지 객체 타입으로 저장된다.

객체역할설명
blob파일 내용파일의 스냅샷 (이름 없이 내용만)
tree디렉토리blob과 다른 tree를 참조하는 목록
commit스냅샷tree + 부모 커밋 + 메타데이터
tag이름표특정 커밋에 붙이는 annotated tag

각 객체는 SHA-1 해시로 식별된다. 같은 내용이면 같은 해시가 나오므로, Git은 자연스럽게 중복을 제거한다.

# 객체 타입 확인
git cat-file -t HEAD

# 커밋 객체 내용 보기
git cat-file -p HEAD

# tree 객체 탐색
git ls-tree HEAD

1.2 세 가지 영역

Git에는 세 가지 핵심 영역이 있다.

  • Working Directory -- 실제 파일이 있는 디렉토리. 우리가 편집하는 공간이다.
  • Staging Area (Index) -- 다음 커밋에 포함될 변경 사항을 준비하는 영역이다.
  • Repository (.git) -- 커밋 히스토리와 모든 객체가 저장되는 공간이다.
# 세 영역 간 파일 이동 흐름
# Working -> Staging
git add file.txt

# Staging -> Repository
git commit -m "feat: add file"

# Repository -> Working (특정 파일 되돌리기)
git checkout HEAD -- file.txt

1.3 HEAD와 ref

  • HEAD -- 현재 체크아웃된 커밋을 가리키는 포인터이다. 보통 브랜치를 가리킨다.
  • branch -- 특정 커밋을 가리키는 이동 가능한 포인터이다.
  • tag -- 특정 커밋을 가리키는 고정 포인터이다.
# HEAD가 가리키는 곳 확인
cat .git/HEAD
# ref: refs/heads/main

# 브랜치가 가리키는 커밋 확인
cat .git/refs/heads/main

# Detached HEAD 상태 만들기
git checkout HEAD~2

2. 필수 명령어 치트시트

매일 쓰는 명령어를 상황별로 정리했다.

2.1 기본 워크플로우

# 저장소 초기화 / 클론
git init
git clone https://github.com/user/repo.git

# 변경 사항 확인
git status
git diff                    # unstaged 변경
git diff --staged           # staged 변경
git diff HEAD               # 전체 변경

# 스테이징 & 커밋
git add -p                  # 대화형 부분 스테이징
git commit -m "feat: login" # 커밋
git commit --amend          # 마지막 커밋 수정

# 원격 동기화
git fetch origin            # 원격 변경만 가져오기
git pull --rebase origin main  # fetch + rebase
git push origin feature/login  # 푸시

2.2 브랜치 관리

# 브랜치 생성 & 이동
git branch feature/auth
git checkout -b feature/auth  # 생성 + 이동
git switch -c feature/auth    # Git 2.23+ 추천

# 브랜치 목록
git branch -a               # 로컬 + 원격
git branch --merged          # 병합 완료된 브랜치
git branch -d feature/old    # 안전 삭제
git branch -D feature/old    # 강제 삭제

# 원격 브랜치 정리
git remote prune origin
git fetch -p

2.3 stash -- 작업 임시 저장

작업 중 급하게 다른 브랜치로 이동해야 할 때 stash를 사용한다.

# 현재 변경 사항 저장
git stash
git stash push -m "WIP: login form"

# stash 목록 확인
git stash list

# 복원
git stash pop               # 꺼내면서 삭제
git stash apply stash@{0}  # 꺼내되 유지

# 특정 파일만 stash
git stash push -m "partial" -- src/auth.ts

# stash를 브랜치로 변환
git stash branch new-branch stash@{0}

3. 브랜칭 전략 비교

3.1 Git Flow

가장 전통적인 브랜칭 모델로, Vincent Driessen이 2010년에 제안했다.

구조:

  • main -- 프로덕션 코드
  • develop -- 다음 릴리스 개발
  • feature/* -- 기능 개발
  • release/* -- 릴리스 준비
  • hotfix/* -- 긴급 버그 수정

적합한 팀: 릴리스 주기가 긴 프로젝트 (월 1-2회), 여러 버전을 동시에 유지해야 하는 경우.

단점: 브랜치가 많아서 복잡하고, CI/CD와 잘 맞지 않는다.

3.2 GitHub Flow

GitHub가 실제로 사용하는 단순한 모델이다.

구조:

  • main -- 항상 배포 가능한 상태
  • feature/* -- main에서 분기, PR로 병합

적합한 팀: 지속적 배포 환경, 소규모 팀, 웹 서비스.

규칙: main은 항상 배포 가능해야 하며, feature 브랜치에서 작업 후 PR을 통해 merge한다.

3.3 Trunk-Based Development

Google, Meta 등 대형 IT 기업이 사용하는 전략이다.

구조:

  • main (trunk) -- 모든 개발자가 직접 커밋하거나 short-lived 브랜치로 작업
  • 브랜치 수명: 최대 1-2일

적합한 팀: Feature Flag 인프라가 있는 팀, 대규모 조직, 빠른 피드백 루프.

3.4 비교 요약

항목Git FlowGitHub FlowTrunk-Based
복잡도높음낮음중간
배포 빈도월 1-2회매일하루 수회
팀 규모대규모소-중규모모든 규모
Feature Flag 필요아니오아니오
CI/CD 궁합보통좋음매우 좋음

4. Merge vs Rebase

Git에서 가장 논쟁적인 주제 중 하나이다.

4.1 Merge -- 히스토리 보존

# feature 브랜치를 main에 merge
git checkout main
git merge feature/auth

merge는 두 브랜치의 공통 조상을 찾아 3-way merge를 수행하고, merge commit을 생성한다. 히스토리가 그대로 보존되므로 "언제 어떤 브랜치에서 작업했는지"를 추적할 수 있다.

4.2 Rebase -- 깔끔한 히스토리

# feature 브랜치에서 main의 최신 변경을 가져오기
git checkout feature/auth
git rebase main

rebase는 feature 브랜치의 커밋들을 main의 끝에 하나씩 재적용한다. 결과적으로 직선형 히스토리가 만들어진다.

4.3 Interactive Rebase -- 커밋 정리의 핵심

# 최근 5개 커밋 정리
git rebase -i HEAD~5

인터랙티브 리베이스 편집기에서 사용할 수 있는 명령어는 다음과 같다.

  • pick -- 커밋 유지
  • reword -- 커밋 메시지 수정
  • edit -- 커밋 내용 수정
  • squash -- 이전 커밋과 합치기 (메시지 합침)
  • fixup -- 이전 커밋과 합치기 (메시지 버림)
  • drop -- 커밋 삭제
# 실제 사용 예시: PR 전에 커밋 정리
# 에디터에서 아래처럼 수정
# pick abc1234 feat: add login page
# squash def5678 fix: typo in login
# squash ghi9012 style: adjust padding
# -> 3개 커밋이 1개로 합쳐짐

4.4 언제 어떤 것을 쓸까

상황추천
PR 머지Squash Merge 또는 Rebase Merge
로컬 커밋 정리Interactive Rebase
공유 브랜치Merge (rebase 금지)
개인 feature 브랜치Rebase로 main 동기화

황금률: 이미 push된 커밋은 rebase하지 마라. 다른 사람이 참조하고 있을 수 있다.


5. 고급 명령어 마스터

5.1 cherry-pick -- 특정 커밋만 가져오기

다른 브랜치의 특정 커밋 하나를 현재 브랜치로 복사한다.

# 특정 커밋 가져오기
git cherry-pick abc1234

# 여러 커밋 한번에
git cherry-pick abc1234 def5678

# 범위로 가져오기 (abc는 미포함, def는 포함)
git cherry-pick abc1234..def5678

# 커밋하지 않고 변경만 적용
git cherry-pick --no-commit abc1234

사용 사례:

  • hotfix 브랜치의 버그 수정을 develop에도 적용
  • 잘못된 브랜치에 커밋한 것을 올바른 브랜치로 이동
  • 릴리스 브랜치에 특정 기능만 선별 포함

5.2 bisect -- 이진 검색으로 버그 찾기

수백 개의 커밋 중 어디서 버그가 생겼는지 이진 검색으로 찾는다.

# bisect 시작
git bisect start

# 현재(버그 있음)를 bad로 표시
git bisect bad

# 정상이었던 커밋을 good으로 표시
git bisect good v2.0.0

# Git이 중간 커밋을 체크아웃 -> 테스트 -> 판별
git bisect good  # 또는 git bisect bad

# 원인 커밋을 찾으면 리셋
git bisect reset

자동화도 가능하다.

# 테스트 스크립트로 자동 bisect
git bisect start HEAD v2.0.0
git bisect run npm test

이렇게 하면 Git이 자동으로 각 커밋에서 테스트를 돌리고, 처음으로 실패하는 커밋을 찾아준다.

5.3 worktree -- 여러 브랜치 동시 작업

하나의 저장소에서 여러 작업 디렉토리를 만들어 동시에 다른 브랜치를 작업할 수 있다.

# 새 worktree 생성
git worktree add ../project-hotfix hotfix/critical-bug

# worktree 목록 확인
git worktree list

# worktree 제거
git worktree remove ../project-hotfix

# 새 브랜치를 만들면서 worktree 생성
git worktree add -b feature/new-ui ../project-ui

사용 사례:

  • main에서 코드 리뷰하면서 feature에서 개발 계속하기
  • 긴급 hotfix와 현재 작업을 동시에 진행
  • CI 빌드를 기다리는 동안 다른 작업 시작

5.4 reflog -- 실수 복구의 최후 보루

reflog는 HEAD가 가리킨 모든 곳의 기록이다. 실수로 커밋을 잃어버려도 reflog로 복구할 수 있다.

# reflog 확인
git reflog

# 실수로 reset --hard 했을 때 복구
git reflog
# HEAD@{2}: commit: important feature 를 찾으면
git reset --hard HEAD@{2}

# 삭제된 브랜치 복구
git reflog
git checkout -b recovered-branch HEAD@{5}

# 특정 기간 내 기록
git reflog --since="2 days ago"

주의: reflog는 로컬에만 존재하고, 기본 90일 후 만료된다.


6. .gitconfig 최적화

생산성을 높이는 Git 설정을 소개한다.

6.1 유용한 alias

[alias]
    # 상태 & 로그
    st = status -sb
    lg = log --oneline --graph --decorate --all
    last = log -1 HEAD --stat

    # 브랜치
    co = checkout
    sw = switch
    br = branch -vv
    brd = branch -d

    # 커밋
    cm = commit -m
    ca = commit --amend --no-edit
    undo = reset HEAD~1 --mixed

    # diff
    df = diff --stat
    dfc = diff --cached

    # stash
    sl = stash list
    sp = stash pop
    ss = stash push -m

    # 정리
    cleanup = "!git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d"

6.2 core 설정

[core]
    editor = code --wait
    autocrlf = input          # macOS/Linux
    ignorecase = false
    pager = delta              # delta pager 사용

[pull]
    rebase = true              # pull 시 항상 rebase

[push]
    default = current          # 현재 브랜치만 push
    autoSetupRemote = true     # push 시 자동 upstream 설정

[init]
    defaultBranch = main

[diff]
    tool = vscode
    colorMoved = default

[merge]
    conflictstyle = diff3      # 3-way 충돌 표시
    tool = vscode

[rerere]
    enabled = true             # 충돌 해결 기억하기

6.3 delta -- 더 나은 diff 도구

delta는 Git diff 출력을 보기 좋게 바꿔주는 도구이다.

# 설치
brew install git-delta

# .gitconfig에 추가
[core]
    pager = delta

[interactive]
    diffFilter = delta --color-only

[delta]
    navigate = true
    line-numbers = true
    side-by-side = true

7. GitHub PR 리뷰 베스트 프랙티스

7.1 좋은 PR의 조건

  1. 크기: 200-400줄 이하. 그 이상이면 쪼개라.
  2. 단일 목적: 하나의 PR은 하나의 변경 사항만.
  3. 설명: 왜 이 변경이 필요한지, 어떻게 테스트했는지 기술.
  4. Self-Review: 올리기 전에 본인이 먼저 리뷰.

7.2 PR 템플릿

## 변경 사항
- 무엇을 왜 변경했는지

## 테스트
- [ ] 단위 테스트 추가/수정
- [ ] 로컬에서 동작 확인
- [ ] 엣지 케이스 검증

## 스크린샷 (UI 변경 시)

## 관련 이슈
- closes #123

7.3 리뷰 에티켓

리뷰어:

  • 코드의 "의도"를 먼저 이해하라. 스타일보다 로직에 집중.
  • 질문 형태로 피드백하라: "이 부분은 왜 이렇게 했나요?" 가 "이건 틀렸습니다"보다 낫다.
  • nit, suggestion, question 등 코멘트 유형을 구분하라.
  • Approve, Request Changes, Comment를 명확히 사용하라.

작성자:

  • 리뷰어의 시간을 존중하라. PR을 작게 유지하라.
  • 모든 코멘트에 응답하라 (최소한 리액션이라도).
  • force push 후에는 리뷰어에게 알려라.

7.4 CODEOWNERS

# .github/CODEOWNERS

# 전체 코드베이스
* @team-lead

# 프론트엔드
/src/components/ @frontend-team
/src/pages/ @frontend-team

# 백엔드 API
/src/api/ @backend-team

# 인프라
/terraform/ @devops-team
/k8s/ @devops-team

# 보안 민감 파일
/src/auth/ @security-team @team-lead

8. 커밋 메시지 컨벤션

8.1 Conventional Commits

type(scope): description

body (선택)

footer (선택)

type 종류:

type설명
feat새 기능
fix버그 수정
docs문서 변경
style코드 포맷팅 (기능 변경 없음)
refactor리팩토링
perf성능 개선
test테스트 추가/수정
chore빌드, 설정 변경
ciCI 설정 변경
# 예시
git commit -m "feat(auth): add Google OAuth login"
git commit -m "fix(api): handle null response from payment gateway"
git commit -m "docs(readme): update installation instructions"

# Breaking Change
git commit -m "feat(api)!: change response format to JSON:API"

8.2 gitmoji

이모지로 커밋 유형을 시각적으로 구분한다.

git commit -m ":sparkles: add user profile page"
git commit -m ":bug: fix login redirect loop"
git commit -m ":recycle: refactor database connection pool"
git commit -m ":white_check_mark: add unit tests for auth module"

8.3 commitlint로 강제하기

# 설치
npm install -D @commitlint/cli @commitlint/config-conventional

# commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style',
      'refactor', 'perf', 'test', 'chore', 'ci'
    ]],
    'subject-max-length': [2, 'always', 72],
  },
};

9. 모노레포 관리

9.1 git sparse-checkout

대규모 모노레포에서 필요한 디렉토리만 체크아웃한다.

# sparse-checkout 활성화
git sparse-checkout init --cone

# 필요한 디렉토리만 지정
git sparse-checkout set packages/frontend packages/shared

# 디렉토리 추가
git sparse-checkout add packages/backend

# 설정 확인
git sparse-checkout list

# 전체 복원
git sparse-checkout disable

9.2 git subtree

외부 저장소를 서브디렉토리로 통합 관리한다.

# 외부 저장소 추가
git subtree add --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main --squash

# 변경 사항 가져오기
git subtree pull --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main --squash

# 변경 사항 내보내기
git subtree push --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main

9.3 Nx / Turborepo와의 연동

모노레포 빌드 도구와 Git을 연동하면 변경된 패키지만 빌드/테스트할 수 있다.

# Nx: 영향받는 프로젝트만 테스트
npx nx affected --target=test --base=main --head=HEAD

# Turborepo: 변경된 패키지만 빌드
npx turbo run build --filter=...[HEAD~1]

# GitHub Actions에서 변경 감지
# CI에서 main과 비교하여 변경된 패키지만 처리

10. 위기 상황 대처법

10.1 force push 복구

누군가 실수로 force push를 했을 때 대처법이다.

# 1. reflog에서 원래 커밋 찾기
git reflog show origin/main

# 2. 원래 커밋으로 복구
git push origin HEAD@{1}:main --force

# 3. 다른 팀원에게 알리기
# "main 브랜치가 복구되었습니다. git pull --rebase를 실행해주세요"

예방책:

# main 브랜치 force push 차단 (GitHub Settings)
# Settings -> Branches -> Branch protection rules
# "Restrict force pushes" 활성화

10.2 민감 정보 제거 (BFG Repo-Cleaner)

실수로 비밀번호, API 키 등을 커밋했을 때이다.

# BFG 설치
brew install bfg

# 특정 파일 히스토리에서 완전 제거
bfg --delete-files secrets.env

# 특정 텍스트 치환
bfg --replace-text passwords.txt

# 정리
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# 강제 푸시
git push --force

주의사항:

  • BFG 실행 전에 저장소를 반드시 백업하라.
  • 이미 clone한 팀원들에게 fresh clone을 요청해야 한다.
  • GitHub에서 캐시 삭제를 요청해야 할 수 있다.

10.3 대용량 파일 관리 (Git LFS)

바이너리 파일, 미디어 파일 등 대용량 파일은 Git LFS를 사용한다.

# Git LFS 설치 & 초기화
brew install git-lfs
git lfs install

# 추적할 파일 패턴 지정
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/videos/*"

# .gitattributes가 자동 생성됨 - 커밋 필요
git add .gitattributes
git commit -m "chore: configure Git LFS tracking"

# LFS 파일 목록 확인
git lfs ls-files

# 이미 커밋된 대용량 파일을 LFS로 마이그레이션
git lfs migrate import --include="*.psd" --everything

10.4 잘못된 merge 되돌리기

# merge 커밋 자체를 revert
git revert -m 1 MERGE_COMMIT_SHA

# -m 1: 첫 번째 부모(main)를 기준으로 되돌림
# -m 2: 두 번째 부모(feature)를 기준으로 되돌림

10.5 모든 것을 잃었을 때의 최후 수단

# 1. reflog 확인 (90일 이내)
git reflog

# 2. dangling 객체에서 복구
git fsck --lost-found

# 3. 최악의 경우: 팀원의 로컬 저장소에서 복구
# 팀원에게 push를 요청하거나, bundle로 받기
git bundle create backup.bundle --all

마무리

Git은 단순한 버전 관리 도구가 아니다. 팀의 협업 방식을 정의하고, 코드 품질을 보증하며, 배포 파이프라인의 근간이 되는 핵심 인프라이다.

이 글에서 다룬 내용을 요약하면 다음과 같다.

  1. 내부 구조를 이해하면 명령어가 직관적으로 이해된다.
  2. 브랜칭 전략은 팀 상황에 맞게 선택하라 -- 정답은 없다.
  3. rebase는 로컬에서, merge는 공유 브랜치에서 사용하라.
  4. cherry-pick, bisect, worktree, reflog는 실전에서 빛을 발한다.
  5. PR 리뷰 문화가 코드 품질을 결정한다.
  6. Conventional Commits로 커밋 히스토리를 깔끔하게 유지하라.
  7. 위기 상황에서 reflog와 BFG는 생명줄이다.

Git을 마스터하는 것은 하루아침에 되지 않는다. 하지만 매일 한 가지씩 새로운 명령어를 써보고, 내부 구조를 이해해 나간다면, 어느새 어떤 상황에서도 흔들리지 않는 Git 마스터가 되어 있을 것이다.

Git Mastery Guide — From Basics to rebase, cherry-pick, bisect, and worktree

Introduction

Git is the tool developers use every single day, yet most never move beyond the add-commit-push loop. Merge conflicts cause panic, rebase feels scary, and many have never even heard of bisect or worktree.

This guide starts from Git internals and covers everything you truly need in practice: advanced commands, branching strategies, PR review culture, monorepo management, and crisis recovery -- all in one comprehensive reference.


1. Understanding Git Internals

To use Git properly, you need to understand its internal structure. At its core, Git is a content-addressable filesystem -- essentially a key-value store.

1.1 Four Core Objects

All data in Git is stored as four object types.

ObjectRoleDescription
blobFile contentA snapshot of file contents (no filename)
treeDirectoryA list referencing blobs and other trees
commitSnapshottree + parent commit + metadata
tagLabelAn annotated tag pointing to a specific commit

Each object is identified by its SHA-1 hash. Identical content always produces the same hash, so Git naturally deduplicates data.

# Check object type
git cat-file -t HEAD

# View commit object contents
git cat-file -p HEAD

# Explore the tree object
git ls-tree HEAD

1.2 Three Areas

Git operates across three core areas.

  • Working Directory -- Where your actual files live. This is where you edit code.
  • Staging Area (Index) -- A preparation area for changes to include in the next commit.
  • Repository (.git) -- Where commit history and all objects are permanently stored.
# File movement flow between the three areas
# Working -> Staging
git add file.txt

# Staging -> Repository
git commit -m "feat: add file"

# Repository -> Working (restore a specific file)
git checkout HEAD -- file.txt

1.3 HEAD and refs

  • HEAD -- A pointer to the currently checked-out commit. Usually points to a branch.
  • branch -- A movable pointer to a specific commit.
  • tag -- A fixed pointer to a specific commit.
# Check where HEAD points
cat .git/HEAD
# ref: refs/heads/main

# Check which commit a branch points to
cat .git/refs/heads/main

# Enter detached HEAD state
git checkout HEAD~2

2. Essential Commands Cheat Sheet

Here are everyday commands organized by scenario.

2.1 Basic Workflow

# Initialize / clone a repository
git init
git clone https://github.com/user/repo.git

# Check changes
git status
git diff                    # unstaged changes
git diff --staged           # staged changes
git diff HEAD               # all changes

# Stage & commit
git add -p                  # interactive partial staging
git commit -m "feat: login" # commit
git commit --amend          # amend last commit

# Remote sync
git fetch origin            # fetch remote changes only
git pull --rebase origin main  # fetch + rebase
git push origin feature/login  # push

2.2 Branch Management

# Create & switch branches
git branch feature/auth
git checkout -b feature/auth  # create + switch
git switch -c feature/auth    # recommended (Git 2.23+)

# List branches
git branch -a               # local + remote
git branch --merged          # fully merged branches
git branch -d feature/old    # safe delete
git branch -D feature/old    # force delete

# Clean up remote branches
git remote prune origin
git fetch -p

2.3 stash -- Temporarily Save Work

Use stash when you need to urgently switch branches mid-work.

# Save current changes
git stash
git stash push -m "WIP: login form"

# List stashes
git stash list

# Restore
git stash pop               # pop and remove
git stash apply stash@{0}  # apply but keep

# Stash specific files only
git stash push -m "partial" -- src/auth.ts

# Convert a stash to a branch
git stash branch new-branch stash@{0}

3. Branching Strategy Comparison

3.1 Git Flow

The most traditional branching model, proposed by Vincent Driessen in 2010.

Structure:

  • main -- Production code
  • develop -- Next release development
  • feature/* -- Feature development
  • release/* -- Release preparation
  • hotfix/* -- Emergency bug fixes

Best for: Projects with long release cycles (1-2 per month), or when maintaining multiple versions simultaneously.

Drawbacks: Too many branches make it complex, and it does not fit CI/CD workflows well.

3.2 GitHub Flow

The simple model that GitHub actually uses.

Structure:

  • main -- Always deployable
  • feature/* -- Branch from main, merge via PR

Best for: Continuous deployment environments, small teams, web services.

Rule: main must always be deployable. Work on feature branches and merge through PRs.

3.3 Trunk-Based Development

The strategy used by large tech companies like Google and Meta.

Structure:

  • main (trunk) -- All developers commit directly or use short-lived branches
  • Branch lifetime: 1-2 days maximum

Best for: Teams with feature flag infrastructure, large organizations, fast feedback loops.

3.4 Comparison Summary

AspectGit FlowGitHub FlowTrunk-Based
ComplexityHighLowMedium
Deploy frequency1-2x/monthDailyMultiple/day
Team sizeLargeSmall-MediumAny size
Feature flags neededNoNoYes
CI/CD compatibilityFairGoodExcellent

4. Merge vs Rebase

One of the most debated topics in Git.

4.1 Merge -- Preserve History

# Merge feature branch into main
git checkout main
git merge feature/auth

Merge performs a 3-way merge by finding the common ancestor of both branches and creates a merge commit. History is preserved exactly as it happened, so you can trace "when and on which branch work was done."

4.2 Rebase -- Clean History

# Rebase feature branch onto latest main
git checkout feature/auth
git rebase main

Rebase replays the feature branch commits one by one on top of main. The result is a linear history.

4.3 Interactive Rebase -- The Key to Commit Cleanup

# Clean up the last 5 commits
git rebase -i HEAD~5

Available commands in the interactive rebase editor:

  • pick -- Keep the commit
  • reword -- Edit the commit message
  • edit -- Modify the commit content
  • squash -- Combine with previous commit (merge messages)
  • fixup -- Combine with previous commit (discard message)
  • drop -- Delete the commit
# Practical example: clean up before PR
# In the editor, modify like this:
# pick abc1234 feat: add login page
# squash def5678 fix: typo in login
# squash ghi9012 style: adjust padding
# -> 3 commits become 1

4.4 When to Use Which

ScenarioRecommendation
PR mergeSquash Merge or Rebase Merge
Local commit cleanupInteractive Rebase
Shared branchesMerge (never rebase)
Personal feature branchRebase to sync with main

The Golden Rule: Never rebase commits that have already been pushed. Others may be referencing them.


5. Mastering Advanced Commands

5.1 cherry-pick -- Copy Specific Commits

Copy a single commit from another branch to your current branch.

# Pick a specific commit
git cherry-pick abc1234

# Pick multiple commits at once
git cherry-pick abc1234 def5678

# Pick a range (abc excluded, def included)
git cherry-pick abc1234..def5678

# Apply changes without committing
git cherry-pick --no-commit abc1234

Use cases:

  • Apply a hotfix bug fix to develop as well
  • Move a commit from the wrong branch to the correct one
  • Selectively include specific features in a release branch

5.2 bisect -- Binary Search for Bugs

Find the exact commit that introduced a bug using binary search across hundreds of commits.

# Start bisect
git bisect start

# Mark current (buggy) as bad
git bisect bad

# Mark a known good commit
git bisect good v2.0.0

# Git checks out a midpoint commit -> test -> mark
git bisect good  # or git bisect bad

# Once found, reset
git bisect reset

You can also automate the process.

# Automated bisect with a test script
git bisect start HEAD v2.0.0
git bisect run npm test

Git will automatically run tests on each commit and pinpoint the first failing one.

5.3 worktree -- Work on Multiple Branches Simultaneously

Create multiple working directories from a single repository to work on different branches at the same time.

# Create a new worktree
git worktree add ../project-hotfix hotfix/critical-bug

# List worktrees
git worktree list

# Remove a worktree
git worktree remove ../project-hotfix

# Create a new branch while adding a worktree
git worktree add -b feature/new-ui ../project-ui

Use cases:

  • Review code on main while continuing development on feature
  • Work on an urgent hotfix and current feature simultaneously
  • Start new work while waiting for a CI build

5.4 reflog -- The Last Resort for Mistake Recovery

reflog records everywhere HEAD has pointed. Even if you lose commits by accident, reflog can bring them back.

# View reflog
git reflog

# Recover after accidental reset --hard
git reflog
# Find: HEAD@{2}: commit: important feature
git reset --hard HEAD@{2}

# Recover a deleted branch
git reflog
git checkout -b recovered-branch HEAD@{5}

# View records within a time range
git reflog --since="2 days ago"

Note: reflog exists only locally and expires after 90 days by default.


6. Optimizing .gitconfig

Here are Git settings that boost productivity.

6.1 Useful Aliases

[alias]
    # Status & log
    st = status -sb
    lg = log --oneline --graph --decorate --all
    last = log -1 HEAD --stat

    # Branch
    co = checkout
    sw = switch
    br = branch -vv
    brd = branch -d

    # Commit
    cm = commit -m
    ca = commit --amend --no-edit
    undo = reset HEAD~1 --mixed

    # Diff
    df = diff --stat
    dfc = diff --cached

    # Stash
    sl = stash list
    sp = stash pop
    ss = stash push -m

    # Cleanup
    cleanup = "!git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d"

6.2 Core Settings

[core]
    editor = code --wait
    autocrlf = input          # macOS/Linux
    ignorecase = false
    pager = delta              # use delta pager

[pull]
    rebase = true              # always rebase on pull

[push]
    default = current          # push only current branch
    autoSetupRemote = true     # auto set upstream on push

[init]
    defaultBranch = main

[diff]
    tool = vscode
    colorMoved = default

[merge]
    conflictstyle = diff3      # 3-way conflict display
    tool = vscode

[rerere]
    enabled = true             # remember conflict resolutions

6.3 delta -- A Better Diff Tool

delta transforms Git diff output into a readable, beautifully formatted display.

# Install
brew install git-delta

# Add to .gitconfig
[core]
    pager = delta

[interactive]
    diffFilter = delta --color-only

[delta]
    navigate = true
    line-numbers = true
    side-by-side = true

7. GitHub PR Review Best Practices

7.1 What Makes a Good PR

  1. Size: Keep it under 200-400 lines. Split if larger.
  2. Single purpose: One PR should address one change.
  3. Description: Explain why the change is needed and how it was tested.
  4. Self-review: Review your own PR before submitting.

7.2 PR Template

## Changes
- What changed and why

## Testing
- [ ] Added/updated unit tests
- [ ] Verified locally
- [ ] Edge cases validated

## Screenshots (for UI changes)

## Related Issues
- closes #123

7.3 Review Etiquette

As a reviewer:

  • Understand the "intent" of the code first. Focus on logic over style.
  • Frame feedback as questions: "Why was this approach chosen?" works better than "This is wrong."
  • Distinguish comment types: nit, suggestion, question, blocker.
  • Use Approve, Request Changes, and Comment deliberately.

As an author:

  • Respect the reviewer's time. Keep PRs small.
  • Respond to every comment (at minimum with a reaction).
  • Notify reviewers after a force push.

7.4 CODEOWNERS

# .github/CODEOWNERS

# Entire codebase
* @team-lead

# Frontend
/src/components/ @frontend-team
/src/pages/ @frontend-team

# Backend API
/src/api/ @backend-team

# Infrastructure
/terraform/ @devops-team
/k8s/ @devops-team

# Security-sensitive files
/src/auth/ @security-team @team-lead

8. Commit Message Conventions

8.1 Conventional Commits

type(scope): description

body (optional)

footer (optional)

type reference:

typeDescription
featNew feature
fixBug fix
docsDocumentation changes
styleCode formatting (no functional change)
refactorCode refactoring
perfPerformance improvement
testAdd/update tests
choreBuild/config changes
ciCI configuration changes
# Examples
git commit -m "feat(auth): add Google OAuth login"
git commit -m "fix(api): handle null response from payment gateway"
git commit -m "docs(readme): update installation instructions"

# Breaking Change
git commit -m "feat(api)!: change response format to JSON:API"

8.2 gitmoji

Visually distinguish commit types using emojis.

git commit -m ":sparkles: add user profile page"
git commit -m ":bug: fix login redirect loop"
git commit -m ":recycle: refactor database connection pool"
git commit -m ":white_check_mark: add unit tests for auth module"

8.3 Enforcing with commitlint

# Install
npm install -D @commitlint/cli @commitlint/config-conventional

# commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style',
      'refactor', 'perf', 'test', 'chore', 'ci'
    ]],
    'subject-max-length': [2, 'always', 72],
  },
};

9. Monorepo Management

9.1 git sparse-checkout

Check out only the directories you need from a large monorepo.

# Enable sparse-checkout
git sparse-checkout init --cone

# Specify only needed directories
git sparse-checkout set packages/frontend packages/shared

# Add a directory
git sparse-checkout add packages/backend

# Check current configuration
git sparse-checkout list

# Restore full checkout
git sparse-checkout disable

9.2 git subtree

Integrate and manage an external repository as a subdirectory.

# Add an external repository
git subtree add --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main --squash

# Pull changes
git subtree pull --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main --squash

# Push changes back
git subtree push --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main

9.3 Integration with Nx / Turborepo

Combining monorepo build tools with Git lets you build and test only the changed packages.

# Nx: test only affected projects
npx nx affected --target=test --base=main --head=HEAD

# Turborepo: build only changed packages
npx turbo run build --filter=...[HEAD~1]

# In GitHub Actions: detect changes
# Compare against main in CI to process only changed packages

10. Crisis Recovery Playbook

10.1 Recovering from a Force Push

When someone accidentally force-pushes over shared history.

# 1. Find the original commit in reflog
git reflog show origin/main

# 2. Restore to the original commit
git push origin HEAD@{1}:main --force

# 3. Notify the team
# "main branch has been restored. Please run git pull --rebase."

Prevention:

# Block force push on main (GitHub Settings)
# Settings -> Branches -> Branch protection rules
# Enable "Restrict force pushes"

10.2 Removing Sensitive Data (BFG Repo-Cleaner)

When passwords, API keys, or other secrets are accidentally committed.

# Install BFG
brew install bfg

# Completely remove a file from all history
bfg --delete-files secrets.env

# Replace specific text patterns
bfg --replace-text passwords.txt

# Clean up
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# Force push
git push --force

Important notes:

  • Always back up your repository before running BFG.
  • Team members who have cloned the repo will need a fresh clone.
  • You may need to request cache clearing from GitHub.

10.3 Managing Large Files (Git LFS)

Use Git LFS for binary files, media assets, and other large files.

# Install & initialize Git LFS
brew install git-lfs
git lfs install

# Specify file patterns to track
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/videos/*"

# .gitattributes is auto-generated -- commit it
git add .gitattributes
git commit -m "chore: configure Git LFS tracking"

# List LFS-tracked files
git lfs ls-files

# Migrate already-committed large files to LFS
git lfs migrate import --include="*.psd" --everything

10.4 Reverting a Bad Merge

# Revert the merge commit itself
git revert -m 1 MERGE_COMMIT_SHA

# -m 1: revert relative to the first parent (main)
# -m 2: revert relative to the second parent (feature)

10.5 The Absolute Last Resort

# 1. Check reflog (available for 90 days)
git reflog

# 2. Recover from dangling objects
git fsck --lost-found

# 3. Worst case: recover from a teammate's local repo
# Ask them to push, or receive a bundle
git bundle create backup.bundle --all

Conclusion

Git is not merely a version control tool. It defines how teams collaborate, guarantees code quality, and forms the backbone of deployment pipelines -- it is core infrastructure.

Here is a summary of what this guide covered.

  1. Understanding internals makes commands intuitive.
  2. Choose a branching strategy that fits your team -- there is no one right answer.
  3. Use rebase locally, merge on shared branches.
  4. cherry-pick, bisect, worktree, and reflog shine in real-world scenarios.
  5. PR review culture determines code quality.
  6. Conventional Commits keep commit history clean and navigable.
  7. In a crisis, reflog and BFG are your lifelines.

Mastering Git does not happen overnight. But if you try one new command each day and gradually build your understanding of the internals, you will eventually become the kind of developer who stays calm and effective in any Git situation.