[2026] Git 완전 가이드 — 객체 모델·세 그루 나무·머지·리베이스 내부까지
이 글의 핵심
Git이 파일을 어떻게 저장하는지(객체), 스테이징이 왜 존재하는지(세 그루 나무), merge와 rebase가 DAG에서 무엇을 바꾸는지, 실무에서 어떤 패턴으로 쓰는지까지 한 번에 정리합니다.
들어가며: Git은 “파일 모음”이 아니라 객체 그래프
많은 튜토리얼이 add → commit → push 흐름에서 멈춥니다. 실무에서 충돌·리베이스·“갑자기 사라진 커밋”을 다루려면 Git이 무엇을 저장하고, 어디에 기록하는지를 아는 편이 안전합니다. 이 글은 다음을 다룹니다.
- 객체 저장소: blob, tree, commit, tag가 디스크에 어떻게 붙는지
- 세 그루 나무(three-tree): 작업 디렉터리, 인덱스(스테이징),
HEAD가 각각 무엇을 가리키는지 - 머지 전략과 충돌: 삼방향 병합, 전략(ort/recursive 등), 충돌 마커의 의미
- 리베이스 vs 머지 내부: DAG 관점에서 무엇이 달라지는지
- 프로덕션 패턴: 브랜치 보호, 훅, 서명, 운영 시나리오
기초 명령은 Git 기초 입문과 브랜치·머지를 참고하면 이 글의 내부 설명이 더 잘 연결됩니다.
1. 객체 저장소: blob, tree, commit, tag
Git은 콘텐츠 주소 저장소(content-addressable store)입니다. 객체는 대부분 SHA-1(구버전) 또는 SHA-256(extensions.objectFormat 설정 시) 해시로 식별되며, .git/objects 아래에 저장됩니다.
1.1 객체의 네 가지 타입
| 타입 | 역할 |
|---|---|
| blob | 단일 파일 내용의 불변 스냅샷. 파일명·경로는 blob에 없음 |
| tree | 디렉터리 엔트리: 이름, 모드, 하위 blob/tree SHA 목록 |
| commit | 최상위 tree SHA, 부모 커밋(들), 작성자·커밋자, 타임스탬프, 메시지 |
| tag | 보통 특정 커밋을 가리키는 annotated tag(객체로 저장). 가벼운 lightweight tag는 ref만 있음 |
커밋 하나는 “프로젝트 전체 디렉터리의 스냅샷”을 가리키는 tree 루트와 메타데이터로 구성됩니다. 같은 파일 내용을 여러 경로·커밋에서 쓰면 blob 객체는 공유됩니다(중복 저장이 줄어듦).
1.2 디스크에 저장되는 방식(느슨한 객체 vs 팩)
- Loose object:
objects/ab/cdef...처럼 zlib으로 압축된 단일 파일 - Packfile:
git gc등으로 여러 객체가 델타 압축되어.git/objects/pack/*.pack에 모임
그래서 저장소 크기는 “파일 개수”보다 변경의 패턴에 더 민감합니다.
1.3 직접 확인해 보기
아래는 로컬에서 객체를 열어보는 전형적인 흐름입니다(해시는 저장소마다 다름).
# 최신 커밋
git rev-parse HEAD
# 커밋 객체 타입 확인
git cat-file -t HEAD
# 커밋 내용(트리·부모·메시지)
git cat-file -p HEAD
# 루트 트리 SHA 확인 후 트리 나열
git ls-tree HEAD
# 특정 blob 내용 미리보기
git show HEAD:README.md
핵심: git show HEAD:path는 현재 브랜치 끝 커밋이 가리키는 트리에서 path에 해당하는 blob을 꺼내 보여 줍니다. 즉 “작업 중인 파일”이 아니라 마지막 커밋 스냅샷입니다.
1.4 태그: lightweight vs annotated
git tag v1.0.0 # ref만 — 객체 없음에 가깝게 가벼움
git tag -a v1.0.1 -m "release" # tag 객체 생성 — 서명·메모에 유리
git show v1.0.1
릴리스·감사 추적이 필요하면 annotated tag가 적합합니다.
2. 세 그루 나무: working tree, index, HEAD
Git 내부 문서와 고급 튜토리얼에서 말하는 three trees는 다음 세 층입니다.
flowchart LR
subgraph WT[Working tree]
W[편집 중인 파일]
end
subgraph IX[Index / Stage]
I[다음 커밋에 들어갈 스냅샷]
end
subgraph HD[HEAD]
H[현재 브랜치가 가리키는 커밋 스냅샷]
end
W -->|git add| I
I -->|git commit| H
| 트리 | 위치/의미 | 비고 |
|---|---|---|
| Working tree | 작업 디렉터리의 실제 파일 | 편집기·빌드 도구가 만지는 층 |
| Index | .git/index — 다음 커밋에 포함될 스테이징 영역 | git add/git rm --cached 등으로 갱신 |
| HEAD | 보통 현재 브랜치의 최신 커밋이 가리키는 트리 | git checkout으로 브랜치를 바꾸면 함께 이동 |
2.1 diff의 세 가지 관점
git diff # Working vs Index — “스테이징에 안 올린 변경”
git diff --cached # Index vs HEAD — “다음 커밋에 들어갈 변경”
git diff HEAD # Working vs HEAD — “통째로의 미커밋 변경”
왜 스테이징이 있나? 한 번의 논리적 커밋에 포함할 부분 스냅샷을 고를 수 있게 하기 위함입니다. “파일 단위가 아니라 훅(hunk) 단위 스테이징”(git add -p)도 같은 철학입니다.
2.2 checkout과 reset이 건드리는 층(개념)
git checkout -- file: 인덱스(또는 HEAD) 쪽 내용으로 작업 트리를 되돌림(옵션에 따라 동작이 달라질 수 있음 — Git 버전별로restore권장).git reset:--soft/--mixed/--hard에 따라 HEAD·인덱스·작업 트리 중 어디까지 되돌릴지가 갈립니다.
세부 동작은 reset·revert 가이드와 함께 보는 것이 좋습니다.
3. 머지 전략과 충돌 해결
3.1 삼방향 병합(three-way merge)
브랜치 A와 B를 합칠 때, Git은 보통 공통 조상(merge base) O와 두 팁 커밋 A, B의 트리를 비교합니다. 한쪽만 변경된 라인은 취하고, 양쪽에서 다르게 바뀐 라인은 충돌로 표시합니다.
O (merge-base)
/ \
A B
\ /
M (merge commit)
3.2 머지 전략(대표)
Git 버전에 따라 기본 구현이 recursive → ort로 이행되는 흐름이었습니다. 개념적으로 자주 쓰이는 구분은 다음과 같습니다.
| 전략/옵션 | 요지 |
|---|---|
| ort (최신 기본에 가까움) | 다중 베이스·충돌 처리 등에서 recursive 계열의 후속으로 널리 사용 |
| recursive | 전통적인 기본 병합 알고리즘(복잡한 트리에서도 동작하도록 설계) |
| ours / theirs | 특정 트리 전체를 한쪽으로 맞추는 식의 병합에 사용(의미가 “전략 이름”과 다를 수 있어 문서 확인 권장) |
실제로는 아래처럼 브랜치별로 우선순위를 둡니다.
git merge feature/x
# 충돌 시 한 파일을 한쪽 버전으로 통째로 선택(예시)
git checkout --ours path/to/file # 현재 브랜치(머지 수행 중인 쪽) 쪽
# 또는
git checkout --theirs path/to/file # 머지해 들어오는 쪽
ours/theirs는 리베이스 중에는 직관과 반대로 느껴질 수 있습니다. 리베이스는 “현재 브랜치의 커밋을 재적용”하는 과정이라 역할이 바뀌어 보이는 경우가 있어, 반드시 git status 메시지와 문서를 함께 확인하는 것이 안전합니다.
3.3 충돌 마커와 해결 절차
충돌 파일에는 다음과 같은 마커가 생깁니다.
<<<<<<< HEAD
현재 브랜치 쪽 내용
=======
들어오는 쪽 내용
>>>>>>> branch-name
권장 절차는 다음과 같습니다.
git status로 충돌 파일 목록 확인- 각 파일에서 마커를 제거하고 의도한 최종 코드로 정리
git add로 해결됨을 표시git merge --continue(머지 중) 또는 커밋 완료
git mergetool로 GUI 병합을 쓰면 동일한 과정을 시각적으로 처리할 수 있습니다.
3.4 충돌이 “싫다”가 아니라 “비용”이다
충돌은 나쁜 것이 아니라 동시에 같은 줄을 바꾼 결과입니다. 비용을 줄이려면 파일 경계·모듈 경계로 작업을 나누고, 짧은 수명의 브랜치와 자주 리베이스/머지하는 흐름이 도움이 됩니다(팀 규칙과 맞출 것).
4. 리베이스 vs 머지: 내부적으로 무엇이 다른가
4.1 머지: 분기를 한 점에서 다시 합침
머지는 보통 머지 커밋을 만들어 두 부모를 연결합니다(패스트포워드가 되면 머지 커밋 없이 포인터만 이동).
- 장점: 공유 브랜치에 안전하게 기록, 이미 푸시된 히스토리를 바꾸지 않음
- 단점: 분기·머지 커밋이 많아지면 그래프가 복잡해질 수 있음
4.2 리베이스: 커밋을 “다시 쌓음”
리베이스는 새 커밋을 만들어 패치를 재적용합니다. 그래서 커밋 해시가 바뀝니다.
git checkout feature
git rebase main
내부적으로는 main..feature 구간의 커밋들을 꺼내어 main 위에 하나씩 적용(replay)합니다. 충돌이 나면 커밋마다 멈추고, 해결 후 git rebase --continue로 진행합니다.
- 장점: 선형에 가까운 깔끔한 히스토리, 리뷰·
git bisect에 유리한 경우가 많음 - 단점: 이미 다른 사람이 기반으로 삼은 커밋을 덮어쓰면 혼란 — 공유 브랜치에서는
--force-with-lease여부를 팀 규칙으로 정해야 함
4.3 언제 무엇을 쓰나(실무 규칙 예시)
| 상황 | 흔한 선택 |
|---|---|
공유 main에 통합 | PR 머지(머지 커밋 또는 squash — 호스팅 설정) |
| 로컬·개인 브랜치 정리 | rebase로 커밋 정리 후 PR |
| 이미 푸시된 브랜치 | 가능하면 추가 커밋으로 수정; rewrite는 합의 후 |
인터랙티브 리베이스 예시는 실전 리베이스 가이드를 참고하세요.
5. 프로덕션 Git 패턴
5.1 브랜치 보호와 리뷰
- protected branch:
main에 직접 push 금지, PR 필수, CI 통과 필수 - 최소 승인 수·CODEOWNERS: 민감 경로는 소유 팀 리뷰 강제
5.2 서명과 출처
- Signed commits / signed tags: GPG 또는 SSH 서명으로 출처 확인
- 릴리스 태그는 annotated + 서명이 감사·규정 대응에 유리
5.3 서버 훅과 로컬 훅
- pre-receive / update: 서버에서 거절 규칙(비밀 키 패턴 등)
- pre-commit / commit-msg: 포맷터·린터·커밋 메시지 규칙
훅은 “개발자 PC를 믿지 않는다”는 전제에서 서버 측 검증을 병행하는 것이 안전합니다.
5.4 대용량·바이너리
- Git LFS, 아티팩트 저장소(S3 등), 빌드 산출물은 저장소 밖
- monorepo라면 부분 클론·sparse checkout 등 운영 이슈를 사전에 검토
5.5 장애 대응에 쓰는 명령
git reflog # HEAD가 과거에 어디를 가리켰는지 — “되살리기” 단서
git fsck --full # 끊긴 객체·손상 탐지
git gc --prune=now # 정리(운영 정책에 따라 주의)
force push는 팀 규칙 없이 사용하면 동료의 작업 기반을 깰 수 있습니다. --force-with-lease는 “원격이 내가 알던 것과 같을 때만” 덮어쓰게 해 줍니다.
5.6 운영 체크리스트(요약)
main은 보호, PR·CI·리뷰 필수- 커밋은 작고 읽기 쉬운 단위, 메시지 규약(예: Conventional Commits)
- 공유 브랜치 rewrite는 합의 +
--force-with-lease - 릴리스는 서명 태그, 배포 파이프라인과 연계
- 장애 시
reflog·fsck·백업 원격을 알고 있을 것
워크플로 전반은 Git 워크플로우 가이드와 맞춰 읽으면 좋습니다.
내부 동작과 핵심 메커니즘
이 글의 주제는 「[2026] Git 완전 가이드 — 객체 모델·세 그루 나무·머지·리베이스 내부까지」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
경계에서의 지연·실패(시퀀스 관점)
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(프로세스·런타임·게이트웨이) participant D as 의존성(외부 API·DB·큐) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
알고리즘·프로토콜·리소스 관점 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
- 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.
프로덕션 운영 패턴
실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.
확장 예시: 엔드투엔드 미니 시나리오
「[2026] Git 완전 가이드 — 객체 모델·세 그루 나무·머지·리베이스 내부까지」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.
의사코드 스케치(프레임워크 무관)
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request) // 경계에서 거절
authorize(validated, ctx) // 권한·테넌트
result = domainCore(validated) // 순수에 가까운 규칙
persistOrEmit(result, idempotentKey) // I/O: 멱등·재시도 정책
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성 불안정, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정이 로컬과 다름 | 프로필·시크릿·기본값, 지역 리전 | 단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
정리
Git은 객체(blob/tree/commit/tag)로 이력을 저장하고, 작업 디렉터리·인덱스·HEAD로 다음 커밋까지의 변화를 다룹니다. 머지는 공통 조상을 포함한 병합으로 수렴하고, 리베이스는 커밋을 재적용해 선형에 가깝게 만듭니다. 프로덕션에서는 브랜치 보호·리뷰·CI·서명·훅으로 저장소를 제품처럼 운영하는 것이 핵심입니다. 내부를 이해하면 reset이 왜 위험한지, 리베이스가 왜 공유 브랜치와 충돌하는지, 머지 충돌 마커가 왜 그 모양인지가 한꺼번에 선명해집니다.