commit 은 노드, branch 는 pointer
commit 이 snapshot 이라는 걸 받아들였으면 다음은 'snapshot 들이 어떻게 연결되는가' 야. 모든 commit (첫 번째 제외) 은 자기 parent 의 hash 를 기록해. merge commit 은 parent 가 둘이야. 그래서 결과는 Directed Acyclic Graph 야: directed (link 는 과거 한 방향), acyclic (link 따라가다 출발점 못 돌아옴), graph (병렬 branch + merge 가 진짜 graph topology 만들어, line 이 아니라).
그림 그려봐. A ← B ← C ← D 가 main 의 linear chain 이야. B 에서 feature 분기해서 E ← F 추가. graph 가 갈라져. feature 를 main 으로 merge 하면 parent 둘인 G 가 생겨 — D 와 F. 미래의 history walk 는 G 에서 양쪽 parent 어느 쪽으로든 모든 과거 작업에 닿을 수 있어. 지워지는 거 없고 덮어쓰는 거 없어. graph 가 그냥 자라.
branch 와 tag 는 hash 하나 든 41 byte 파일이야. feature 만들기 = 파일 하나 쓰기. switch 하면 HEAD 가 그걸 가리키게 돼. 지우면 pointer 가 사라지지만 commit 은 남아 있고, 다른 ref 가 안 가리키면 결국 Git GC 가 정리해. 'branch 가 왜 그렇게 싸지?' 의 답이야 — 복사할 게 거의 없어.
강력한 Git 명령들 — log, blame, bisect, rebase, cherry-pick — 다 graph 연산이야. 명령을 graph instruction 으로 읽기 시작하면 이름이 임의롭지 않게 보여. git log feature..main 은 'main 에서 도달 가능하지만 feature 에서는 도달 불가능한 commit 들' 이야. DAG 위 set difference 인 거지.