git clone이 실제로 작동하는 방식: Git 객체 데이터베이스 심층 탐구

발행: (2025년 12월 11일 오후 03:22 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

git clone가 실제로 하는 일

Git은 다음과 같은 단계들을 수행합니다:

  1. 원격과 협상을 하여 사용 가능한 레퍼런스(브랜치, 태그)를 탐색합니다.
  2. 전체 객체 그래프—해당 레퍼런스에서 도달 가능한 모든 커밋, 트리, 블롭—를 효율적으로 패킹하고 델타 압축하여 다운로드합니다.
  3. 이 객체들을 .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이 파일을 만드는 방식

체크아웃 과정은 데이터베이스 객체를 실제 파일로 변환합니다:

  1. HEAD를 읽고 → 브랜치를 해결 → 커밋을 해결
  2. 해당 커밋의 루트 트리를 읽음
  3. 트리를 순회하면서 각 블롭을 작업 디렉터리에 기록
  4. 경로‑블롭 매핑을 인덱스에 캐시
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의 보안 모델은 수학적이며, 해시 연결을 통해 무결성이 보장됩니다.

예시: 최소 저장소 흐름

  1. 초기 커밋 C0 → 트리 T0 → 블롭 B1(README)
  2. 다음 커밋 C1 → README 수정 → 블롭 B2
  3. 서버가 {C1, C0, T1, T0, B2, B1}을 팩함
  4. 클라이언트가 팩을 기록하고 → 레퍼런스를 설정 → C1을 체크아웃 → 파일이 나타남

시각적 요약

refs/heads/main -> C3 -> C2 -> C1 -> C0

각 커밋은 자신의 루트 트리를 가리키고, 트리는 블롭에 연결되며, 레퍼런스는 커밋을 가리켜 하나의 콘텐츠 주소 지정 DAG를 형성합니다.

핵심 사고 모델

  • Git은 파일 시스템이 아니라 데이터베이스이다. 모든 파일, 디렉터리, 커밋은 키‑값 저장소의 불변 객체다.
  • 클론 = 그래프 다운로드 + 레퍼런스 바인딩. 객체 그래프를 받아온 뒤 인간이 읽을 수 있는 이름(브랜치, 태그)을 할당한다.
  • 작업 트리 = 하나의 트리 객체에 대한 뷰. 브랜치를 전환한다는 것은 어떤 트리 객체를 보는지를 바꾸는 것이다.
  • 인덱스 = 성능 캐시. 파일 통계와 블롭 ID를 추적해 diff와 스테이징을 빠르게 만든다.

마무리 생각

git clone은 단순히 파일을 복사하는 것이 아니라 스냅샷, 해시, 관계들로 이루어진 그래프 기반 데이터베이스를 재구성합니다. 이 과정을 이해하면 Git이 코드를 실제로 어떻게 관리하는지, 그리고 왜 그렇게 효율적인지 더 예측 가능하고 투명하게 파악할 수 있습니다.

Link to original article

Back to Blog

관련 글

더 보기 »