34장에서 “브랜치·태그는 객체가 아니라 참조"라고 짧게 정리했다. 이 장은 그 참조(ref) 모델을 정면으로 다루며, 11장의 브랜치·22장의 태그·12장의 HEAD가 사실은 모두 같은 메커니즘의 변형이라는 것을 확인한다.
개요
Git의 참조는 .git/refs/ 아래에 흩어져 있는 일반 텍스트 파일이며, 각 파일의 내용은 커밋 해시 하나(또는 다른 참조를 가리키는 표기)뿐이다.
| |
브랜치와 원격 추적 브랜치, 태그는 저장 위치(디렉터리)만 다를 뿐 구조 자체는 동일하다. 이 통일성 덕분에 git log <아무 참조 이름>, git diff <아무 참조 이름>처럼 이 컬렉션 전체에서 다룬 명령들이 브랜치든 태그든 구분 없이 똑같은 문법으로 받아들일 수 있다.
기본 개념
HEAD는 이 참조 체계에서 특별한 역할을 하는, “지금 내가 어디에 있는가"를 가리키는 참조다. 03장에서 살펴본 대로 .git/HEAD 파일을 열어보면 보통 다음과 같은 한 줄이 들어 있다.
| |
이것이 symbolic ref다 — HEAD가 커밋 해시를 직접 담는 것이 아니라, “다른 참조 파일을 가리킨다"는 한 단계 간접 참조를 담고 있다. git switch main(12장)을 실행하면 이 .git/HEAD 파일의 내용이 ref: refs/heads/main으로 다시 쓰이는 것이 전부다. 새 커밋을 만들면 Git은 HEAD가 가리키는 참조(refs/heads/main)의 내용을 새 커밋 해시로 갱신하고, 그 결과로 “현재 브랜치가 앞으로 나아갔다"는 상태가 만들어진다.
종류/세부
Detached HEAD와의 연결
12장에서 다룬 detached HEAD 상태는 이 구조로 정확히 설명된다. git switch --detach <커밋>을 실행하면 .git/HEAD가 symbolic ref(다른 참조를 가리키는 형태) 대신 커밋 해시를 직접 담는다.
| |
이 상태에서 새 커밋을 만들면 HEAD 파일의 해시 값만 갱신될 뿐, 어떤 브랜치 참조도 그 새 커밋을 가리키지 않는다. 12장에서 설명했던 “detached HEAD에서 만든 커밋은 다른 브랜치로 전환하면 유실 위험이 있다"는 경고가 바로 이 구조에서 나온 것이다 — 그 커밋을 가리키는 브랜치 참조가 애초에 없기 때문이다.
참조를 직접 조회하는 명령
| |
git rev-parse --abbrev-ref HEAD는 셸 스크립트나 CI 설정에서 “지금 어느 브랜치에서 실행 중인가"를 프로그래밍적으로 확인할 때 자주 쓰인다.
Packed refs
브랜치·태그 수가 아주 많아지면 .git/refs/ 아래 개별 파일이 수천 개까지 늘어날 수 있다. Git은 이런 경우를 위해 여러 참조를 .git/packed-refs라는 파일 하나로 압축해 관리하는 최적화를 제공한다. 이는 참조의 논리적 동작에는 영향을 주지 않는 저장 효율화이며, 36장에서 다루는 packfile과 유사한 발상이다.
주의사항·함정
.git/HEAD나 .git/refs/ 파일을 직접 편집하지 않는다: 34장에서 객체 디렉터리를 직접 건드리지 말라고 경고한 것과 같은 이유다. 참조 구조를 실수로 손상시키면 저장소가 일관성을 잃을 수 있으므로, 이 파일들을 바꾸고 싶다면 git switch, git branch, git tag, 필요하다면 git update-ref 같은 정식 명령을 거쳐야 한다.
참조 이름에 슬래시(/)를 쓰면 디렉터리 구조가 생긴다: feature/login처럼 슬래시를 포함한 브랜치 이름은 .git/refs/heads/feature/login이라는 실제 하위 디렉터리 구조로 저장된다. 이 때문에 feature라는 이름의 브랜치와 feature/login이라는 이름의 브랜치를 동시에 만들 수 없다 — 파일 시스템 관점에서 feature가 파일이면서 동시에 디렉터리일 수는 없기 때문이다.
원격 추적 브랜치(refs/remotes/)를 로컬 브랜치와 혼동하지 않는다: 18장에서 다룬 것처럼 refs/remotes/origin/main은 마지막 fetch 시점의 원격 상태를 반영하는 읽기 전용에 가까운 참조다. 이 참조를 직접 git commit의 대상으로 삼아 작업할 수는 없으며, 반드시 로컬 브랜치(refs/heads/)를 통해야 한다.
![Featured image of post [Git] 35. Git refs와 HEAD](/post/git/git-refs-and-head-reference-model/wordcloud_hu_5c368194433cb50c.webp)
![[Git] 33. git bisect — 버그 커밋 이분 탐색](/post/git/git-bisect-command-binary-search-bug-commit/wordcloud_hu_8196480e8b21de7a.webp)
![[Git] 34. Git 객체 모델 — blob/tree/commit](/post/git/git-object-model-blob-tree-commit/wordcloud_hu_bd4ee27759fca634.webp)
![[Git] 35. Git refs와 HEAD](/post/git/git-refs-and-head-reference-model/wordcloud_hu_7b81aa541bd4941d.webp)
![[Git] 36. Packfile과 git gc](/post/git/packfile-and-git-gc/wordcloud_hu_b3276dfd3f3cb2be.webp)
![[Git] 37. Git submodule](/post/git/git-submodule-nested-repository/wordcloud_hu_6eb075e5709f5159.webp)
![[Git] 10. .gitignore — 추적 제외 규칙](/post/git/gitignore-file-exclude-patterns/wordcloud_hu_cd51113edaa1c4db.webp)
![[Git] 12. git switch/checkout — 브랜치 전환](/post/git/git-switch-checkout-branch-transition/wordcloud_hu_cfa8ddb8839c8503.webp)
![[Git] 25. git restore와 git checkout -- <file>](/post/git/git-restore-checkout-file-discard-changes/wordcloud_hu_cd1210130e27a947.webp)
![[Git] 30. git clean — 미추적 파일 정리](/post/git/git-clean-command-remove-untracked-files/wordcloud_hu_73da61b3df7aabd.webp)