목차11개 항목
출처와 참고
팔만코딩경(42Seoul 커뮤니티 위키)의 「그림으로 Git의 내부구조 파악하기」를 바탕으로 재구성한 노트다. 바탕 자료는 『팀 개발을 위한 Git, GitHub 시작하기』(한빛미디어)와 Pro Git의 「Git의 내부」 장이다. 「직접 들여다보기」의 일부 명령은 mingrammer의 「Git Tips」를 바탕으로 보강했다. 「객체 파일의 위치」, 「직접 객체 만들어 보기」, 「커밋 그래프」는 「.git - 쩜깃의 이해」와 「git tutorial」을 바탕으로 보강했다.
왜 내부를 알아야 하나
add, commit만 쓸 때는 몰라도 되지만, reset·rebase·복구처럼 이력을 다루는 명령은 내부 구조를 알면 훨씬 덜 무섭다. Git이 하는 일은 단순하다. 내용을 해시로 이름 붙인 객체를 쌓아 두고, 그 객체를 가리키는 포인터를 옮긴다.
작업 폴더의 세 부분
my-project/ ← 작업 폴더
├─ .git/ ← Git 저장소(로컬 저장소). 모든 이력이 여기에
│ ├─ objects/ ← 객체 보관소 (blob, tree, commit, tag)
│ ├─ index ← 스테이지(Staging Area)의 실체
│ ├─ refs/heads/main ← 브랜치: 커밋 해시 하나를 적은 파일
│ └─ HEAD ← 지금 체크아웃한 브랜치를 가리킴
└─ src/, README.md ... ← 워킹 트리: .git을 뺀 나머지 작업 공간git init은 이.git폴더를 만드는 일이다. 이미 파일이 있는 폴더에서 해도 기존 파일은 건드리지 않는다.ls -a로 확인한다.- 흔히 작업 폴더 전체를 "로컬 저장소"라고 부르지만, 정확히는
.git이 저장소이고 나머지는 워킹 트리다. - 파일 상태와 세 영역의 흐름은 Git 기본 명령과 파일 상태에 있다.
인덱스: 스테이지의 정체
"파일을 스테이지에 올린다"고 하지만 파일이 어디로 이동하는 것은 아니다. .git/index는 다음 커밋에 들어갈 파일 목록표다. 경로마다 그 순간 내용의 blob 해시가 적혀 있다. 처음 git add할 때 생기고, add할 때마다 해당 줄이 새 해시로 갱신된다.
객체 네 가지
.git/objects에는 모든 데이터가 객체로 저장된다. 객체의 이름은 내용을 해시한 값이다.
| 객체 | 언제 생기나 | 담는 것 |
|---|---|---|
| blob | git add 할 때 파일마다 | 파일 내용만. 파일 이름은 없다 |
| tree | git commit 할 때 디렉터리마다 | 이름 → blob/하위 tree 해시 목록. 폴더 구조의 사진 |
| commit | git commit 할 때 | 최상위 tree 해시, 부모 커밋 해시, 작성자·커미터, 시각, 메시지 |
| tag | git tag -a 할 때 | 특정 커밋을 가리키는 주석 달린 이름표 |
git commit은 인덱스를 바탕으로 tree를 만들고, 그 tree를 가리키는 commit 객체를 만든 다음, 현재 브랜치 파일(refs/heads/main)의 해시를 새 커밋으로 바꾼다.
flowchart LR
main["main"] --> c3["commit c3"]
c3 --> t1["tree"]
t1 -->|"README.md"| a1["blob a1"]
t1 -->|"src/"| t2["tree"]
t2 -->|"app.js"| b7["blob b7"]
c3 -->|"parent"| c2["commit c2"]
c2 --> t3["tree ..."]객체 파일의 위치
객체는 해시 앞 두 글자를 폴더 이름, 나머지를 파일 이름으로 해서 .git/objects/ab/cdef…에 zlib으로 압축해 저장된다. 수많은 객체가 한 폴더에 몰리지 않게 나눈 것이다. 시간이 지나 git gc가 돌면 이 낱개 객체들이 .git/objects/pack/의 pack 파일로 묶인다.
직접 객체 만들어 보기
Git은 결국 내용을 넣으면 해시를 돌려주는 키-값 저장소다. 빈 저장소에서 확인할 수 있다.
echo "hello" | git hash-object -w --stdin # 저장하고 해시를 받는다 (-w가 없으면 계산만)
git cat-file -p <해시> # 해시로 내용을 꺼낸다 → hello
git cat-file -p HEAD # 커밋 객체: tree, parent, author, committer, 메시지서명한 커밋이면 커밋 객체 안에 gpgsig 헤더로 서명이 함께 들어 있다.
체크섬: 내용으로 이름 짓기
78981922613b2afb... 같은 40자 16진수는 객체 내용의 SHA-1 해시(체크섬) 다. Git은 무엇이든 저장하기 전에 해시를 구하고, 그 해시를 키로 모든 데이터를 찾는다.
- 해시는 이름·시각이 아니라 내용만으로 결정된다. 내용이 같은 파일 세 개는 blob 하나를 공유하고, 바뀌지 않은 파일은 다음 커밋에서도 같은 blob을 다시 가리킨다.
- 내용이 1비트라도 바뀌면 해시가 달라지므로, 저장된 데이터가 손상되거나 몰래 바뀌면 바로 드러난다.
- 커밋 해시는 부모 해시를 포함해 계산되므로, 과거 커밋 하나를 고치면 그 뒤 모든 커밋의 해시가 바뀐다. rebase가 "커밋을 새로 만든다"고 하는 이유다.
- Git은 SHA-256 저장소도 지원하며, 다음 메이저 버전인 Git 3.0부터 새 저장소의 기본값을 SHA-256으로 바꿀 예정이다. 2026년 11월 기준 최신 버전은 2.56이고 3.0의 출시일은 정해지지 않았다(Git BreakingChanges). GitHub 등 호스팅 서비스의 SHA-256 저장소 지원도 아직 제한적이라 실무 저장소는 SHA-1이다.
커밋 그래프와 참조
커밋은 부모만 가리키고 자식은 모른다. 그래서 이력 전체는 방향 비순환 그래프(DAG) 가 된다. 일반 커밋은 부모가 하나, merge 커밋은 둘 이상이다.
| 참조 | 파일 | 움직이나 |
|---|---|---|
| 브랜치 | .git/refs/heads/<이름> | 그 브랜치에서 커밋할 때마다 새 커밋으로 |
| 태그 | .git/refs/tags/<이름> | 움직이지 않는다 |
| 원격 추적 브랜치 | .git/refs/remotes/origin/<이름> | fetch할 때 |
| HEAD | .git/HEAD | 보통 ref: refs/heads/main처럼 브랜치를 가리키고, 커밋을 직접 가리키면 detached HEAD |
각 참조가 지나온 위치는 .git/logs/의 reflog에 남는다. 이것이 잘못된 reset·rebase 뒤에도 커밋을 되찾을 수 있는 이유다(되돌리기와 이력 다루기).
변경분이 아니라 스냅숏
Git은 커밋마다 "이전 대비 바뀐 줄"을 저장하지 않고, 그 시점 전체 파일 구조의 스냅숏(tree) 을 가리킨다. 바뀌지 않은 파일은 같은 blob을 재사용하므로 공간을 낭비하지 않는다. 덕분에 열 커밋 전 상태도 변경분을 하나씩 거슬러 적용할 필요 없이 그 커밋의 tree를 바로 펼치면 된다. (용량을 줄이려고 나중에 pack 파일로 묶을 때는 내부적으로 델타 압축을 쓰지만, 이는 저장 최적화일 뿐 모델은 스냅숏이다.)
직접 들여다보기
git cat-file -t <hash> # 객체 종류 (blob, tree, commit)
git cat-file -p <hash> # 객체 내용 출력
git cat-file -p HEAD^{tree} # 현재 커밋의 최상위 tree
git ls-files --stage # 인덱스에 적힌 경로와 blob 해시
git hash-object README.md # 이 파일이 blob이 되면 받을 해시
git ls-tree -r --name-only HEAD # 이 커밋의 tree에 든 모든 파일 경로
git rev-parse HEAD # 참조(브랜치·태그·HEAD~2 등)를 커밋 해시로 풀기
git rev-parse --abbrev-ref HEAD # 현재 브랜치 이름
git rev-parse --show-toplevel # 저장소 최상위 폴더 경로 (스크립트에서 유용)
git count-objects -vH # 느슨한 객체·pack 개수와 디스크 사용량
git gc # 느슨한 객체를 pack으로 묶고 도달할 수 없는 객체 정리git gc는 보통 Git이 알아서(gc --auto) 돌린다. 직접 git gc --prune=now를 돌리면 reflog에도 없는 객체가 즉시 사라져 복구할 길이 없어지므로, 이력을 지운 직후가 아니면 손으로 돌릴 일은 드물다.
정리
.git이 저장소이고, 그 밖은 워킹 트리다. 스테이지의 실체는.git/index파일이다.- add는 blob을, commit은 tree와 commit 객체를 만들고 브랜치 포인터를 옮긴다.
- 모든 객체는 내용의 해시로 이름 붙고, 같은 내용은 한 번만 저장된다.
- Git은 변경분이 아니라 스냅숏을 기록한다.
관련 노트: Git 기본 명령과 파일 상태, Git 브랜치, 되돌리기와 이력 다루기, The Missing Semester: CS 교육에서 빠진 도구 학기