.git 내부 파일 - Blob, Tree, Commit, Refs

Jaehyeon Han·2025년 4월 18일

Pro Git의 Git Internals 챕터를 읽고 정리하였다. 코드는 로컬에서 직접 실행한 결과이다.

데이터의 저장

Blob 개체

파일 내용을 byte로 바꾸어 저장한 파일이다.

Blob 개체의 생성

hash-object -w은 Blob 개체를 생성한다.

  • -w 옵션을 주면 파일을 저장한다.
  • 헤더 정보와 데이터에 대한 SHA-1 해시를 반환한다.

디렉토리와 파일은 다음 규칙에 의해 생성된다.

  • obejcts 디렉토리에 해시의 앞 두 글자로 디렉토리 생성
  • 내부에는 나머지 해시값을 이름으로 가진 파일 생성
  • 파일을 수정하고 저장하면 별도의 파일 생성
# 기본 동작
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

# 파일을 수정한 경우
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

세부 생성 과정

헤더 + 내용의 구조를 만든다. 전체 구조는 <타입> <내용의 크기>\0<실제 파일 내용>이다.

→ 이 구조로 SHA-1을 계산한다. 이 값은 파일 저장 경로와 포인터로 사용한다.

→ 해당 구조를 zlib으로 압축하여 파일 내용으로 저장한다.

Blob 개체 읽기

저장한 데이터는 일반적인 인코딩이 아니다. 읽기 위해서는 cat-file 명령을 사용해야 한다.

  • -p: 내용을 터미널에 출력
  • -t: 해당 개체의 종류를 출력
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

Tree 개체

파일이름과 해시에 대한 매핑을 저장하고 있는 개체이다.

모드   종류 해시                                          파일명
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

Tree 개체의 생성

Git은 일반적으로 Staging Area(Index)의 상태대로 Tree 개체를 만들고 기록한다. 그래서 우선 Staging Area에 파일을 추가해서 인덱스를 만들어야 한다.

  1. update-index: 인덱스를 변경한다.
  2. write-tree: Staging Area의 내용을 Tree 개체로 저장한다.

Tree 구조

Tree 개체 내부에 다른 Tree 개체를 포함할 수 있다. 이는 별도의 디렉터리를 나타낸다.

040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

위와 같은 Tree 개체가 있다면, 이는 루트 디렉터리에 bak 디렉터리, new.txt, test.txt가 있는 형태를 나타낸다.

Commit 개체

스냅샷을 누가, 언제, 왜 저장했는지에 대한 정보는 Commit 개체에 저장된다.

Commit 개체의 생성

commit-tree 명령을 통해 Commit 개체를 생성한다. 커밋 개체에 대한 설명과 Tree 개체의 SHA-1 값 한 개를 넘겨야 한다.

$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
196d012d42706c7076bf4041ff4409e11672770d

$ git cat-file -p 196d012d42706c7076bf4041ff4409e11672770d
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 # 최상단 Tree
author Jaehyeon Han <jaehyeonhan99@gmail.com> 1744938717 +0900
committer Jaehyeon Han <jaehyeonhan99@gmail.com> 1744938717 +0900

first commit # 커밋 메시지

Commit 개체의 연결

commit-tree 명령의 -p 인자로 부모 커밋을 지정할 수 있다. 이렇게 연결된 Commit 개체들은 하나의 히스토리를 만든다.

요약

위 내용이 git add 와 git commit 명령을 실행했을 때 Git 내부에서 일어나는 일이다. Git은 변경된 파일을 Blob 개체로 저장하고 현 Index에 따라서 Tree 개체를 만든다. 그리고 이전 커밋 개체와 최상위 Tree 개체를 참고해서 커밋 개체를 만든다. 즉 Blob, Tree, Commit 개체가 Git의 주요 개체이고 이 개체는 전부 .git/objects 디렉토리에 저장된다.

재밌는 점은 다른 Tree 개체가 같은 Blob 개체를 가리킬 수 있다는 점이다. 이 때문에 변경이 없는 파일은 별도의 Blob 개체가 생성되지 않아 저장공간을 아낄 수 있다.

Commit 개체, Tree 개체, Blob 개체 관계


Refs

각 개체의 SHA-1에 대한 별칭이라고 생각하면 된다. 브랜치의 역할이 정확히 이것이다.

Ref의 생성

update-ref <ref> <new_sha1> [<old_sha1>]를 통해 Ref를 만들거나 변경할 수 있다.

브랜치의 경우 refs/heads/브랜치 이름 경로에, 태그의 경우 refs/tag/태그 이름 경로에 저장한다.

HEAD 파일은 현 브랜치를 가리키는 간접(symbolic) Ref다. 간접 Refs라서 다른 Refs를 가리키고, SHA-1 값은 없다. 파일을 열어 보면 아래와 같이 생겼다.

cat .git/HEAD
ref: refs/heads/master

체크아웃이나 커밋을 하면 이 파일의 ref 값이 변경된다.

symbolic-ref <name> <ref>를 통해 name이 가리키는 ref를 변경할 수 있다.

태그

태그 개체는 Commit 개체와 유사하게 작성자, 작성 시점, 태그 메시지, 가리키는 커밋이 포함된다.

태그 개체는 Tree 개체가 아니라 Commit 개체를 가리킨다. 하지만 브랜치와 달리 가리키는 커밋 개체를 바꿀 수는 없다.

참고로 Commit 개체뿐만 아니라 모든 Git 개체에 태그를 달 수 있다.

Lightweight 태그

간단하게 커밋 개체의 SHA-1 값만 갖고 있다.

git update-ref refs/tags/1.0 196d012d42706c7076bf4041ff4409e11672770d
$ cat .git/refs/tags/1.0
196d012d42706c7076bf4041ff4409e11672770d

Annotated 태그

Annotated 태그는 내부에 부가 정보를 저장하기 때문에 update-ref만으로는 만들 수 없다. refs/tags/태그명 에 만들어진 파일에는 태그 개체의 SHA-1 값만 들어있고, 해당 개체에 태그 관련 정보가 담긴다.

$ git tag -a 1.1 fe76f45f5bc27d9f931cb7e20816a543d1b62374 -m 'test tag'

$ cat .git/refs/tags/1.1
0d428f64f35ae3ce7be711ddc282568a8dae4c50

$ git cat-file -p 0d428f64f35ae3ce7be711ddc282568a8dae4c50
object fe76f45f5bc27d9f931cb7e20816a543d1b62374
type commit
tag 1.1
tagger Jaehyeon Han <jaehyeonhan99@gmail.com> 1744944129 +0900

test tag

리모트 Refs

Git은 연결된 원격 브랜치의 정보를 refs/remotes/<리모트이름>/<브랜치이름> 경로에 저장한다. pushfetch 시에 변경된다.


참고자료

Git Internals 정리 :: Git은 어떻게 동작할까?

Pro Git Ch.10

What is in that .git directory?

0개의 댓글