Git

Refs

헬로우월드 2024. 7. 24. 20:00

💡 Git refs 시스템

Git은 객체 기반의 분산 버전 관리 시스템이며, 모든 객체는 불변의 해시값(SHA-1 혹은 SHA-256)으로 식별됩니다. 이때 refs는 이러한 해시를 사람이 기억할 수 있고 관리할 수 있는 식별자(브랜치, 태그 등)로 연결해 주는 이름-값 매핑 시스템입니다.

 

📁 1. refs의 위치와 구조

Git 저장소는 .git 디렉토리 안에 아래와 같은 구조를 가집니다.

.git/
├── HEAD
├── refs/
│   ├── heads/
│   │   ├── main
│   │   └── feature/login
│   ├── tags/
│   │   └── v1.0
│   └── remotes/
│       └── origin/
│           └── main
├── packed-refs

 

각 경로의 의미

디렉토리 설명
refs/heads/ 로컬 브랜치 이름들이 저장됨 (refs/heads/main)
refs/remotes/ 원격 추적 브랜치들이 저장됨 (refs/remotes/origin/main)
refs/tags/ 태그들이 저장됨 (refs/tags/v1.0)
packed-refs 다량의 refs를 하나의 파일로 저장한 최적화된 구조

 

🔗 2. Ref = 커밋 객체를 가리키는 포인터

Git에서 모든 ref커밋 객체를 가리킵니다. Git의 커밋은 해시값(SHA-1 또는 SHA-256)으로 식별되며, refs는 다음 중 하나의 역할을 합니다.

  1. 직접 참조 (Direct ref)→ 해당 브랜치는 이 커밋을 가리킴
  2. $ cat .git/refs/heads/main e1a3e5fbe9c2d3ffcc29d24d0e826173ed8420c9
  3. 간접 참조 (Symbolic ref)→ HEAD는 실제 커밋을 직접 가리키지 않고, refs/heads/main이라는 ref를 가리킴
  4. $ cat .git/HEAD ref: refs/heads/main

이것이 Git이 HEAD → 브랜치 → 커밋 구조를 가지는 이유입니다.

 

🧠 3. HEAD는 특별한 Symbolic Ref

HEAD는 Git에서 작업 디렉토리의 기준점으로, 우리가 현재 어떤 브랜치 위에 있는지를 나타내며 symbolic ref입니다.

  • HEAD가 브랜치를 가리킬 때:
  • ref: refs/heads/main
  • HEAD가 detached 상태일 때 (직접 커밋을 가리킬 때):
  • e1a3e5fbe9c2d3ffcc29d24d0e826173ed8420c9

이 상태를 Detached HEAD 상태라고 하며, 브랜치가 아닌 특정 커밋을 체크아웃 했을 때 발생합니다.

 

🏷️ 4. 참조의 종류 정리

종류 경로 설명
로컬 브랜치 refs/heads/<name> 개발자가 만든 브랜치
원격 브랜치 refs/remotes/<remote>/<name> 원격 저장소의 브랜치 추적
태그 refs/tags/<tag> 특정 커밋을 식별하기 위한 불변 레퍼런스
HEAD HEAD 현재 체크아웃된 브랜치(또는 커밋)를 가리킴
FETCH_HEAD .git/FETCH_HEAD git fetch 결과를 임시로 저장
ORIG_HEAD .git/ORIG_HEAD 마지막 변경 전 HEAD 상태
MERGE_HEAD .git/MERGE_HEAD 병합 대상 커밋 저장
CHERRY_PICK_HEAD .git/CHERRY_PICK_HEAD 체리픽 중 대상 커밋 저장

 

📦 5. packed-refs: 많은 ref를 위한 최적화

Git은 많은 수의 태그나 브랜치를 효율적으로 관리하기 위해 .git/packed-refs라는 파일에 저장합니다. 이 파일은 ref를 개별 파일로 분산 저장하는 대신 하나로 압축합니다.

$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
e1a3e5fbe9c2d3ffcc29d24d0e826173ed8420c9 refs/tags/v1.0
  • git gc, git pack-refs 등을 실행하면 이 구조로 전환됨

 

🧾 6. Reflog: ref의 변경 이력 추적

Git은 각 ref의 변경 이력을 기록하기 위해 reflog를 사용합니다. 이는 .git/logs/ 경로에 저장되며, HEAD뿐 아니라 브랜치의 이력도 추적합니다.

.git/logs/
├── HEAD
└── refs/
    └── heads/
        └── main

 

예시

$ git reflog
e1a3e5f HEAD@{0}: commit: Add new login feature
d5c3a67 HEAD@{1}: checkout: moving from main to feature/login
  • HEAD@{1} → 이전 HEAD 위치
  • git reset --hard HEAD@{1} 으로 복원 가능

 

🧭 7. 브랜치 생성/이동 시 ref 동작

브랜치 생성

$ git branch dev
  • .git/refs/heads/dev 생성
  • 내용은 현재 HEAD가 가리키는 커밋의 SHA

브랜치 이동

$ git checkout dev
  • .git/HEADref: refs/heads/dev 로 갱신

 

🔂 8. Detached HEAD의 의미와 위험성

$ git checkout <commit-hash>
  • .git/HEAD에 커밋 해시가 직접 저장됨
  • 새 커밋 생성 시 브랜치에 연결되지 않음 → 부주의 시 고아 커밋 발생

 

🔬 9. Git 명령어와 refs의 관계

명령어 내부 동작
git commit 현재 HEAD가 가리키는 ref를 갱신
git branch refs/heads/<branch> 생성
git tag refs/tags/<tag> 생성
git push 로컬 refs/heads/* → 원격 refs/heads/* 전송
git fetch 원격의 refs/heads/*refs/remotes/<remote>/*에 복사
git reset HEAD나 브랜치 ref를 다른 커밋으로 이동

 

🧷 10. 심볼릭 ref와 직접 ref 비교

유형 예시 설명
심볼릭 ref ref: refs/heads/main 다른 ref를 가리키는 ref (간접 참조)
직접 ref e1a3e5fb... 실제 커밋 해시를 저장 (직접 참조)

 

🧩 11. Git 내부에서 ref는 어떻게 해석될까?

Git은 내부적으로 다음 순서로 ref를 탐색합니다:

  1. refs/heads/<name>
  2. refs/tags/<name>
  3. refs/remotes/<name>
  4. packed-refs에서 검색

예: git show mainrefs/heads/main → SHA → git cat-file -p SHA

 

🧰 12. refs 확인 명령 정리

git show-ref
# 모든 ref와 대응하는 커밋 해시 출력

git symbolic-ref HEAD
# HEAD가 가리키는 브랜치 ref 경로 확인

git rev-parse refs/heads/main
# 해당 ref가 가리키는 커밋 ID 출력

git update-ref refs/heads/main <new-commit-id>
# 수동으로 ref를 다른 커밋으로 갱신

 

🧾 결론: refs는 Git의 심장이다

  • refs는 Git에서 브랜치, 태그, 원격 브랜치 등 모든 명명된 참조의 핵심
  • HEAD, reflog, symbolic-ref, packed-refs, detached HEAD는 모두 refs와 연관
  • refs는 Git을 단순한 SHA-1 저장소가 아닌 사람이 관리 가능한 버전 관리 시스템으로 만들어주는 필수 시스템