git clone이 실제로 작동하는 방식: Git 객체 데이터베이스 심층 탐구
Source: Dev.to
git clone가 실제로 하는 일
Git은 다음과 같은 단계들을 수행합니다:
- 원격과 협상을 하여 사용 가능한 레퍼런스(브랜치, 태그)를 탐색합니다.
- 전체 객체 그래프—해당 레퍼런스에서 도달 가능한 모든 커밋, 트리, 블롭—를 효율적으로 패킹하고 델타 압축하여 다운로드합니다.
- 이 객체들을
.git/objects/pack/에 기록하고, 로컬 레퍼런스와HEAD를 설정한 뒤, 체크아웃된 커밋의 루트 트리에서 작업 디렉터리를 생성합니다.
핵심은 다음과 같습니다:
clone = 객체 그래프 복사 + 레퍼런스 설정 + 작업 트리 체크아웃
Git 객체 모델: 핵심 구성 요소
Git은 전통적인 파일 시스템이 아니라 콘텐츠 주소 지정 데이터베이스입니다. 모든 파일, 디렉터리, 커밋, 태그는 암호학적 해시(SHA‑1 또는 SHA‑256)로 식별되는 불변 객체로 존재합니다. 이 덕분에 Git의 데이터 모델은 변조 방지, 중복 제거, 검증 가능성을 가집니다.
| 타입 | 목적 | 포함 내용 |
|---|---|---|
| Blob | 파일 데이터 | 원시 바이트와 헤더 |
| Tree | 디렉터리 스냅샷 | 자식들의 모드, 이름, 객체 ID |
| Commit | 스냅샷 메타데이터 | 작성자, 메시지, 부모 커밋, 루트 트리 |
| Tag | 주석이 달린 레퍼런스 | 태그 메시지와 포인터 |
객체 그래프
commit C
│ tree -> T_root
│ ├── mode 100644 "README.md" -> blob B1
│ ├── mode 100755 "build.sh" -> blob B2
│ └── mode 040000 "src" -> tree T_src
│ ├── "main.go" -> blob B3
│ └── "util.go" -> blob B4
│
└── parent -> commit P
│ tree -> T_prev
└── parent -> ...
핵심 아이디어
- 커밋은 트리를 가리키며, 이는 저장소의 스냅샷을 나타냅니다.
- 트리는 블롭(파일)이나 다른 서브트리(디렉터리)를 가리킵니다.
- 커밋은 부모 레퍼런스를 통해 방향성 비순환 그래프(DAG) 를 형성합니다.
- 동일한 내용은 동일한 해시를 만들기 때문에 Git은 자동으로 객체를 재사용합니다.
git clone이 원격과 통신하는 방식
클론 작업은 Git 클라이언트와 원격 서버 사이의 구조화된 대화입니다.
광고 단계
원격 서버는 다음을 광고합니다:
- 사용 가능한 레퍼런스(예:
refs/heads/main,refs/tags/v1.0) - 지원되는 기능(예:
side-band,ofs-delta,multi_ack)
협상 단계
클라이언트는 다음을 응답합니다:
- Wants: 필요로 하는 커밋들
- Haves: 이미 가지고 있는 커밋들(증분 클론용)
서버는 커밋 그래프를 분석하여 클라이언트가 부족한 객체를 정확히 판단합니다.
팩파일 전송 단계
서버는:
- 요청된 커밋들로부터 도달 가능한 모든 객체를 수집하고
- 효율적인 전송을 위해 델타 압축을 수행한 뒤
- 단일
.pack파일을 스트리밍합니다.
클라이언트는 이 팩을 다음 경로에 기록합니다:
.git/objects/pack/pack-XXXX.pack
.git/objects/pack/pack-XXXX.idx
프로토콜 흐름 개요
Client Server
| ls-refs |
|------------------------------>|
| refs + capabilities |
||
| have(s) |
|------------------------------>|
| ACK/NAK + pack |
| "ref: refs/heads/main"
├── config -> [remote "origin"]
├── refs
│ ├── heads/main
│ ├── remotes/origin/main
│ └── tags/
└── objects
├── pack/
│ ├── pack-XYZ.pack
│ └── pack-XYZ.idx
└── info/
핵심 구성 요소
.git/objects/pack: 패킹된 객체 저장소.git/refs/heads: 로컬 브랜치.git/refs/remotes/origin: 원격 추적 브랜치.git/index: 스테이징 캐시.git/HEAD: 현재 브랜치를 가리키는 심볼릭 레퍼런스
Git Checkout이 파일을 만드는 방식
체크아웃 과정은 데이터베이스 객체를 실제 파일로 변환합니다:
HEAD를 읽고 → 브랜치를 해결 → 커밋을 해결- 해당 커밋의 루트 트리를 읽음
- 트리를 순회하면서 각 블롭을 작업 디렉터리에 기록
- 경로‑블롭 매핑을 인덱스에 캐시
HEAD -> refs/heads/main -> commit C -> tree T_root
|-> blobs -> files
Working tree base OBJ_A]
[OBJ_C full]
...
[checksum]
이 메커니즘은 디스크 사용량과 네트워크 전송 크기를 크게 줄여줍니다.
데이터 무결성 및 보안
- 모든 객체의 해시는 헤더와 내용 모두를 포함합니다—바이트 하나만 바뀌어도 해시가 달라집니다.
- 커밋은 부모 해시를 통해 연결되어 검증 가능한 신뢰 체인을 형성합니다.
git fsck,git verify-pack같은 도구가 손상을 감지합니다.- 서명된 커밋과 태그는 암호학적 진위성을 추가합니다.
Git의 보안 모델은 수학적이며, 해시 연결을 통해 무결성이 보장됩니다.
예시: 최소 저장소 흐름
- 초기 커밋
C0→ 트리T0→ 블롭B1(README) - 다음 커밋
C1→ README 수정 → 블롭B2 - 서버가
{C1, C0, T1, T0, B2, B1}을 팩함 - 클라이언트가 팩을 기록하고 → 레퍼런스를 설정 →
C1을 체크아웃 → 파일이 나타남
시각적 요약
refs/heads/main -> C3 -> C2 -> C1 -> C0
각 커밋은 자신의 루트 트리를 가리키고, 트리는 블롭에 연결되며, 레퍼런스는 커밋을 가리켜 하나의 콘텐츠 주소 지정 DAG를 형성합니다.
핵심 사고 모델
- Git은 파일 시스템이 아니라 데이터베이스이다. 모든 파일, 디렉터리, 커밋은 키‑값 저장소의 불변 객체다.
- 클론 = 그래프 다운로드 + 레퍼런스 바인딩. 객체 그래프를 받아온 뒤 인간이 읽을 수 있는 이름(브랜치, 태그)을 할당한다.
- 작업 트리 = 하나의 트리 객체에 대한 뷰. 브랜치를 전환한다는 것은 어떤 트리 객체를 보는지를 바꾸는 것이다.
- 인덱스 = 성능 캐시. 파일 통계와 블롭 ID를 추적해 diff와 스테이징을 빠르게 만든다.
마무리 생각
git clone은 단순히 파일을 복사하는 것이 아니라 스냅샷, 해시, 관계들로 이루어진 그래프 기반 데이터베이스를 재구성합니다. 이 과정을 이해하면 Git이 코드를 실제로 어떻게 관리하는지, 그리고 왜 그렇게 효율적인지 더 예측 가능하고 투명하게 파악할 수 있습니다.