에듀테크 프로젝트 대시보드
Git 내부 동작
Git은 "파일의 변경분(diff)"을 저장하는 도구처럼 보이지만, 속을 열어보면 내용 그 자체를 통째로 저장하는 작은 데이터베이스다. 모든 것에 SHA 해시라는 이름표를 붙여서, 단 한 글자만 바뀌어도 다른 물건이 된다. 아래에서 해시를 직접 계산해 보고, 커밋 그래프를 손으로 만들어 보자.
git add 는 "지금 이 내용을 다음 스냅샷에 포함해줘",
git commit 은 "장바구니에 담긴 스냅샷을 도장 찍어 영구 보관해줘" 라는 뜻이다.
커밋을 되돌려도 객체는 한동안 .git 안에 남아있어서 git reflog 로 복구할 수 있다.
커밋은 스냅샷이다. 변경된 파일은 새 blob으로, 그 파일이 든 디렉터리는 새 tree로 만들어지고, 안 바뀐 파일·폴더는 이전 해시를 그대로 재사용한다. 그래서 저장 공간이 폭발하지 않는다.
Git은 객체를 저장할 때 "blob <내용길이>\0" + 내용
을 SHA-1로 해싱한 값을 그 객체의 이름(주소)으로 쓴다. 아래 칸에 아무 내용이나 입력해 보자.
같은 내용을 넣으면 언제나 같은 해시가 나온다. 한 글자만 바꿔 보면 해시가 완전히 달라진다 — 이것으로 Git은 파일이 손상·변조되지 않았음을 확인한다.
각 원은 커밋이고, 옆의 6자리는 그 커밋의 짧은 해시다. HEAD 는 "지금 내가 보고 있는 커밋", 색 라벨은 브랜치 포인터일 뿐이다 — 브랜치는 그저 커밋 하나를 가리키는 41바이트짜리 파일이라서 만들기가 공짜다. merge 는 두 부모를 가진 새 커밋을 만든다.
개념적으로는 매 커밋이 전체 스냅샷이다. 다만 저장소가 커지면 Git이 배경에서
packfile 로 압축하면서 비슷한 객체들을 델타(차이)로 묶어 디스크를 절약한다.
우리가 보는 git diff 는 그때그때 두 스냅샷을 비교해 계산해서 보여주는 결과일 뿐,
저장 형식이 아니다.
커밋 해시는 루트 tree + 부모 해시 + 작성자 + 시각 + 메시지 전부를 넣고 계산한다. 부모가 바뀌면 내 해시도 바뀌고, 그러면 내 자식의 해시도 바뀐다. 그래서 과거 커밋을 조작하면 그 뒤 모든 커밋 해시가 도미노처럼 변한다. 이 성질이 Git 히스토리의 무결성을 보장한다 (블록체인과 같은 아이디어다).
브랜치 전환은 파일을 복사하는 게 아니라 HEAD 가 가리키는 커밋을 바꾸고
작업 디렉터리를 그 스냅샷에 맞추는 일이다. 대부분의 객체는 이미 .git 에 있으므로
바뀐 파일만 교체하면 된다. 브랜치 자체는 .git/refs/heads/main 같은
한 줄짜리 텍스트 파일(커밋 해시 하나)이다.
2017년 충돌 공격(SHAttered)이 나온 뒤 Git은 충돌 탐지 코드를 넣었고, 최신 Git은
SHA-256 저장소도 지원한다. 게다가 Git의 해시는 암호학적 서명이 아니라
주소·무결성 체크섬 용도라서, 실무에서 히스토리를 진짜로 보증하려면
git commit -S 로 GPG 서명을 함께 쓴다.