Git은 분산 버전 관리 시스템으로서 내부적으로 작업 디렉터리, 스테이징 영역(인덱스), 그리고 커밋 히스토리라는 세 가지 주요 공간을 사용한다. 이 글에서는 각 영역의 역할과 이들을 오가는 명령어의 흐름을 시각적으로 풀어내고, 브랜치 병합과 리베이스의 차이점까지 도식화하여 설명한다.
Git의 세 가지 데이터 공간
Git은 프로젝트 상태를 크게 세 가지 공간에 나누어 관리한다. 이 구조를 이해하면 명령어의 효과를 정확히 예측할 수 있다.
1. 작업 디렉터리 (Working Directory)
실제로 코드를 편집하는 파일 시스템 공간이다. 여기서 수정된 파일은 ‘추적 대상’(tracked)이거나 ‘추적되지 않은’(untracked) 상태일 수 있다. Git은 이 디렉터리에서 변경 사항을 감지할 뿐, 자동으로 버전 관리를 수행하지 않는다.
2. 스테이징 영역 (Staging Area / Index)
커밋으로 확정하기 전에 원하는 변경 사항만 골라 담을 수 있는 중간 버퍼이다. .git/index 파일에 이진 형태로 저장되며, 파일의 해시와 메타정보를 들고 있다. ‘쇼핑 카트’에 비유할 수 있다.
3. 커밋 히스토리 (Commit History)
.git/objects 디렉터리에 SHA-1 해시로 식별되는 불변 객체들(blob, tree, commit)로 구성된 영구 기록이다. 모든 커밋은 프로젝트의 완전한 스냅숏을 가리키며, 부모 커밋을 참조하는 방향성 비순환 그래프로 연결된다.
기본 명령어의 흐름 시각화
git add: 작업 디렉터리 → 스테이징 영역
수정된 파일을 스테이징 영역으로 옮겨 커밋 후보로 만든다. 부분 스테이징이나 패치 단위 추가도 가능하다.
# 특정 파일만 스테이징
git add src/app.py
# 현재 디렉터리 내 모든 변경 사항 추가
git add .
# hunk 단위 대화식 추가
git add -p
git commit: 스테이징 영역 → 커밋 히스토리
스테이징된 내용을 묶어 하나의 커밋 객체를 생성한다. 커밋 메시지는 일관된 규칙을 따르면 히스토리 탐색이 쉬워진다.
| 구분 | 접두어 | 설명 | 예시 |
|---|---|---|---|
| 새 기능 | feat: | 새로운 기능 추가 | feat: OAuth 로그인 구현 |
| 버그 수정 | fix: | 문제 해결 | fix: 결제 금액 소수점 오류 수정 |
| 문서 | docs: | 문서 변경 | docs: API 엔드포인트 설명 보강 |
| 리팩터링 | refactor: | 동작 변경 없는 코드 개선 | refactor: 인증 모듈 함수 분리 |
| 테스트 | test: | 테스트 코드 | test: 사용자 생성 단위 테스트 추가 |
# 메시지와 함께 커밋
git commit -m "feat: 검색 자동완성 추가"
# 추적 대상 파일의 변경을 한 번에 스테이징 후 커밋
git commit -am "fix: 레이아웃 겹침 수정"
git status: 상태 점검
작업 디렉터리와 스테이징 영역 사이의 파일 상태를 보여준다. 커밋 전 상태 확인에 필수적이다.
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: config.yaml
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: src/main.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
temp.log
브랜치 병합과 리베이스의 내부 동작
브랜치 생성과 전환
브랜치는 특정 커밋을 가리키는 가벼운 포인터일 뿐이다. 새 브랜치를 만들고 전환하는 비용은 거의 없다.
# feature/user-auth 브랜치 생성 후 전환
git checkout -b feature/user-auth
# 기존 브랜치로 이동
git checkout main
# 로컬 브랜치 삭제
git branch -d feature/user-auth
Fast-forward 병합
main 분기에 새로운 커밋이 없고, 대상 브랜치가 main의 직접 후속인 경우 병합 커밋 없이 포인터만 앞으로 이동한다. 이력이 선형으로 유지된다.
git checkout main
git merge feature/user-auth
# Fast-forward 병합이 자동 수행됨
3-way 병합
두 브랜치 모두 독립적인 커밋을 갖고 있을 때, 공통 조상 커밋을 기준으로 양쪽 변경을 하나로 합친 병합 커밋이 생성된다. 이때 충돌이 발생하면 수동 해결이 필요하다.
git merge feature/payment
# 충돌 발생 시
git status # 충돌 파일 확인
# 충돌 해결 후
git add .
git commit -m "Merge branch 'feature/payment'"
리베이스(Rebase)
한 브랜치의 커밋들을 다른 브랜치의 최신 커밋 뒤로 재배치한다. 결과적으로 마치 브랜치를 새로 딴 것처럼 선형 이력이 만들어진다. 각 커밋은 새 해시값을 얻기 때문에 공유된 브랜치에서는 사용을 주의해야 한다.
# feature/payment-gateway 브랜치에서
git rebase main
# 충돌 시 수정 후 계속
git add .
git rebase --continue
# 리베이스 중단
git rebase --abort
대화식 리베이스
최근 몇 개의 커밋을 재정렬, 합치기, 메시지 수정 등을 할 수 있다.
git rebase -i HEAD~4
# 편집기에서 명령어 선택
pick a1b2c3d 결제 모듈 초안
fixup d4e5f6h 초안 수정
reword g7h8i9j API 연동 완료
병합 vs 리베이스 비교
| 특성 | 병합 (Merge) | 리베이스 (Rebase) |
|---|---|---|
| 이력 형태 | 분기와 병합 커밋이 보존된 가지 구조 | 커밋이 재배치되어 단일 선형 이력 |
| 커밋 해시 | 기존 커밋의 해시 유지, 병합 커밋 추가 | 모든 커밋의 해시가 변경됨 |
| 충돌 해결 | 병합 시점에 한 번 수행 | 커밋마다 순차적으로 해결 가능 |
| 이력 추적 | 어떤 브랜치에서 작업했는지 명확 | 개별 커밋의 소속 브랜치 정보 소멸 |
| 주요 용도 | 공개 브랜치 통합, 팀 협업 | 개인 브랜치 정리, 풀 리퀘스트 전 이력 정돈 |
| 안전성 | 공유 브랜치에서도 안전 | 공유 브랜치에서는 피해야 함 (force push 유발) |
팀 정책에 따라 두 방식을 혼용하는 것이 일반적이다. 예를 들어 main에는 병합만 사용하고, 피처 브랜치는 git rebase main으로 최신 상태를 반영한 뒤 병합하는 전략이 깔끔한 이력을 만든다.