commit은 노드, branch는 pointer
commit이 snapshot이라는 걸 받아들였다면 이제 snapshot들이 어떻게 연결되는지 볼 차례야. 첫 commit을 뺀 모든 commit은 parent의 hash를 기록하고, merge commit은 parent를 둘 가져. 그 결과가 Directed Acyclic Graph, 줄여서 DAG야. 연결은 과거를 향한 한 방향이고(directed), 따라가도 출발점으로 되돌아오지 않으며(acyclic), 나란히 갈라진 branch와 merge가 선이 아닌 진짜 graph를 만들어(graph).
직접 그려봐. A ← B ← C ← D가 main의 직선 이력이라고 하자. B에서 feature가 갈라져 E ← F를 더하면 graph가 두 갈래가 돼. feature를 main에 merge하면 새 G가 생기고, 그 parent는 D와 F야. 이제 G에서 어느 parent를 따라가도 과거 작업에 닿을 수 있어. 지워지거나 덮어써지는 게 아니라 graph가 자라는 거야.
branch와 tag는 hash 하나를 담은 41 byte 파일이야. feature를 만든다는 건 파일 하나를 쓰는 일이고, switch하면 HEAD가 그 파일을 가리켜. branch를 지우면 pointer는 사라지지만 commit은 남아 있어. 다른 ref도 그 commit을 가리키지 않게 되면 결국 Git GC가 정리해. branch가 값싼 까닭은 복사할 것이 거의 없기 때문이야.
log, blame, bisect, rebase, cherry-pick 같은 강력한 Git 명령은 모두 graph 연산이야. 명령을 graph 지시문으로 읽기 시작하면 이름도 덜 자의적으로 보여. git log feature..main은 'main에서는 도달할 수 있지만 feature에서는 도달할 수 없는 commit들'을 뜻해. DAG 위의 집합 차이인 셈이지.