- 들어가며
- 1. Git 내부 구조 이해하기
- 2. 필수 명령어 치트시트
- 3. 브랜칭 전략 비교
- 4. Merge vs Rebase
- 5. 고급 명령어 마스터
- 6. .gitconfig 최적화
- 7. GitHub PR 리뷰 베스트 프랙티스
- 8. 커밋 메시지 컨벤션
- 9. 모노레포 관리
- 10. 위기 상황 대처법
- 마무리
들어가며
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 Flow | GitHub Flow | Trunk-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의 조건
- 크기: 200-400줄 이하. 그 이상이면 쪼개라.
- 단일 목적: 하나의 PR은 하나의 변경 사항만.
- 설명: 왜 이 변경이 필요한지, 어떻게 테스트했는지 기술.
- 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 | 빌드, 설정 변경 |
| ci | CI 설정 변경 |
# 예시
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은 단순한 버전 관리 도구가 아니다. 팀의 협업 방식을 정의하고, 코드 품질을 보증하며, 배포 파이프라인의 근간이 되는 핵심 인프라이다.
이 글에서 다룬 내용을 요약하면 다음과 같다.
- 내부 구조를 이해하면 명령어가 직관적으로 이해된다.
- 브랜칭 전략은 팀 상황에 맞게 선택하라 -- 정답은 없다.
- rebase는 로컬에서, merge는 공유 브랜치에서 사용하라.
- cherry-pick, bisect, worktree, reflog는 실전에서 빛을 발한다.
- PR 리뷰 문화가 코드 품질을 결정한다.
- Conventional Commits로 커밋 히스토리를 깔끔하게 유지하라.
- 위기 상황에서 reflog와 BFG는 생명줄이다.
Git을 마스터하는 것은 하루아침에 되지 않는다. 하지만 매일 한 가지씩 새로운 명령어를 써보고, 내부 구조를 이해해 나간다면, 어느새 어떤 상황에서도 흔들리지 않는 Git 마스터가 되어 있을 것이다.
현재 단락 (1/305)
Git은 개발자가 매일 사용하지만, 대부분은 add-commit-push 루프에서 벗어나지 못한다. merge 충돌이 발생하면 당황하고, rebase는 두렵고, bisect나 wo...