한양과학기술고등학교

에듀테크 프로젝트 대시보드

Git 내부 동작

커밋은 어떻게 저장되고, 해시로 무엇을 지킬까?

Git은 "파일의 변경분(diff)"을 저장하는 도구처럼 보이지만, 속을 열어보면 내용 그 자체를 통째로 저장하는 작은 데이터베이스다. 모든 것에 SHA 해시라는 이름표를 붙여서, 단 한 글자만 바뀌어도 다른 물건이 된다. 아래에서 해시를 직접 계산해 보고, 커밋 그래프를 손으로 만들어 보자.

1 파일이 거쳐가는 3개의 공간

📝 작업 디렉터리

내가 지금 편집하는 실제 파일들. 아직 Git은 신경 쓰지 않는다.
git add ▸

📦 스테이징 영역 (index)

다음 커밋에 담을 스냅샷을 미리 담아두는 장바구니.
git commit ▸

🗄️ 저장소 (.git)

커밋·트리·블롭 객체가 영구히 쌓이는 곳. 해시로 주소가 매겨진다.
git checkout ◂

git add 는 "지금 이 내용을 다음 스냅샷에 포함해줘", git commit 은 "장바구니에 담긴 스냅샷을 도장 찍어 영구 보관해줘" 라는 뜻이다. 커밋을 되돌려도 객체는 한동안 .git 안에 남아있어서 git reflog 로 복구할 수 있다.

2 Git을 이루는 3가지 객체

🟢 blob

파일 내용 1개.
파일 이름도, 날짜도 없다.
# 오직 바이트 덩어리

🟡 tree

디렉터리 1개.
이름 → blob/tree 해시 목록.
100644 blob a1b2… main.py

🔵 commit

루트 tree 해시 +
부모 커밋 해시 +
작성자·시각·메시지

커밋은 스냅샷이다. 변경된 파일은 새 blob으로, 그 파일이 든 디렉터리는 새 tree로 만들어지고, 안 바뀐 파일·폴더는 이전 해시를 그대로 재사용한다. 그래서 저장 공간이 폭발하지 않는다.

3 내용 주소 지정 — 해시를 직접 계산해보기

Git은 객체를 저장할 때 "blob <내용길이>\0" + 내용 을 SHA-1로 해싱한 값을 그 객체의 이름(주소)으로 쓴다. 아래 칸에 아무 내용이나 입력해 보자.

계산 중…

같은 내용을 넣으면 언제나 같은 해시가 나온다. 한 글자만 바꿔 보면 해시가 완전히 달라진다 — 이것으로 Git은 파일이 손상·변조되지 않았음을 확인한다.

4 커밋 그래프를 직접 만들어보기

각 원은 커밋이고, 옆의 6자리는 그 커밋의 짧은 해시다. HEAD 는 "지금 내가 보고 있는 커밋", 색 라벨은 브랜치 포인터일 뿐이다 — 브랜치는 그저 커밋 하나를 가리키는 41바이트짜리 파일이라서 만들기가 공짜다. merge 는 두 부모를 가진 새 커밋을 만든다.

# .git 안을 직접 들여다보기 $ git cat-file -t a1b2c3d # 객체 종류 → blob / tree / commit $ git cat-file -p HEAD # 커밋 내용 출력 (tree·parent·author·message) # 손으로 해시 만들어보기 (실제 Git과 동일한 규칙) $ printf 'hello git' | git hash-object --stdin 8d0e41234f24b6da002d962a26c2495ea16a425f $ echo 'blob 9\0hello git' | sha1sum # ← 위와 같은 값
💡 Git은 정말 diff(변경분)를 저장하지 않나?

개념적으로는 매 커밋이 전체 스냅샷이다. 다만 저장소가 커지면 Git이 배경에서 packfile 로 압축하면서 비슷한 객체들을 델타(차이)로 묶어 디스크를 절약한다. 우리가 보는 git diff 는 그때그때 두 스냅샷을 비교해 계산해서 보여주는 결과일 뿐, 저장 형식이 아니다.

💡 왜 커밋 해시는 되돌릴 수 없나?

커밋 해시는 루트 tree + 부모 해시 + 작성자 + 시각 + 메시지 전부를 넣고 계산한다. 부모가 바뀌면 내 해시도 바뀌고, 그러면 내 자식의 해시도 바뀐다. 그래서 과거 커밋을 조작하면 그 뒤 모든 커밋 해시가 도미노처럼 변한다. 이 성질이 Git 히스토리의 무결성을 보장한다 (블록체인과 같은 아이디어다).

💡 브랜치를 바꿔도 왜 순식간인가?

브랜치 전환은 파일을 복사하는 게 아니라 HEAD 가 가리키는 커밋을 바꾸고 작업 디렉터리를 그 스냅샷에 맞추는 일이다. 대부분의 객체는 이미 .git 에 있으므로 바뀐 파일만 교체하면 된다. 브랜치 자체는 .git/refs/heads/main 같은 한 줄짜리 텍스트 파일(커밋 해시 하나)이다.

💡 SHA-1은 깨졌다던데 안전한가?

2017년 충돌 공격(SHAttered)이 나온 뒤 Git은 충돌 탐지 코드를 넣었고, 최신 Git은 SHA-256 저장소도 지원한다. 게다가 Git의 해시는 암호학적 서명이 아니라 주소·무결성 체크섬 용도라서, 실무에서 히스토리를 진짜로 보증하려면 git commit -S 로 GPG 서명을 함께 쓴다.