프로젝트 생산성

[Git] Git 내부 구조 알아보기, objects - commit, blob, tree

:) :) 2024. 9. 12. 00:17

[ 이전 포스트 ]

 

 

[Git] 개발자 필수 도구, Git이 뭔데?

Git이란?Git은 소스코드나 파일의 변화를 추적하는 버전 관리 시스템(VCS, Version Control System)입니다.Git의 설계 목표는 다음과 같습니다.빠른 속도데이터 통합분산 구조비 선형적 개발 ( 여러 컴퓨터

300-29-1.tistory.com

 


 

 

 

Git - What is Git?

Most operations in Git need only local files and resources to operate — generally no information is needed from another computer on your network. If you’re used to a CVCS where most operations have that network latency overhead, this aspect of Git

git-scm.com

'깃북 1.3 시작하기 - Git 기초'에 대한 발췌 및 정리입니다.

 

 


 

 

Git은 Unix File System과 비슷하다

Git의 파일시스템은 UNIX 파일시스템과 비슷합니다.

Git은 각 파일 내용을 blob으로 표현합니다.

 

UNIX FS가 파일을 디렉토리로 묶어 관리하는 것 처럼

Git은 blob을 tree로 묶어 tree의 leaf 노드로 관리합니다.

 

blob이란

blob은 Git에서 파일의 내용을 저장하는 기본 객체 유형입니다.

Binary Large Object의 약자입니다.

 

파일의 실제 내용만을 저장하며, 파일명이나 권한 등의 메타데이터는 포함하지 않습니다.

.git/objects 디렉토리에 저장됩니다.

 

git add 명령으로 파일을 스테이징 영역에 추가할 때 blob이 생성됩니다.

blob은 파일 내용만 포함하므로, 파일 시스템의 구조 정보는 tree 객체에 저장됩니다.

 

blob의 ID

OS에서 부여하는 i-node가 파일을 고유하게 식별하는 것처럼,

blob은 SHA1 hash 알고리즘으로 생성한 ID로 고유하게 식별합니다.

 

SHA1 hash 알고리즘을 통해 파일의 크기와 내용을 토대로 ID를 생성합니다.

(SHA, Secure Hash Algorithm, 안전한 해시 알고리즘, SHA-1은 임의 길이 입력데이터(최대 64비트)를 160비트의 출력 데이터(해시값)으로 바꾼다)

 

blob의 특성

blob은 두 가지 특성을 추가로 가지고 있습니다.

  • blob의 내용은 생성 이후 절대로 변하지 않습니다.
    • 원본 파일이 수정되는 경우, 새로운 blob이 생성되기 때문입니다.
    • git은 객체 불변성 구조인 것을 떠올리면 이해하기 쉽습니다.
  • 동일한 파일의 동일한 내용은, 다른 커밋이어도, 다른 레포지토리여도, 심지어 다른 인터넷에 나타나더라도 항상 동일한 blob id로 표현됩니다.
    • 확인해볼까요?
    • 진짜 이름과 내용과 크기가 동일한 파일에 대한 커밋은 이후 생성되는 blob hash id가 똑같습니다!
      • 이 말은 곧 하나의 git 프로젝트에서, 특정 시점의 파일 상태가 이전 시점과 동일하다면, 하나의 blob을 공유해 표현할 수 있어 훨씬 더 컴팩트하게 공간을 사용합니다!!
  • 여러 트리가 동일한 blob을 참조하는 경우 이는 파일 하드 링크와 같습니다.
  • 최소한 하나의 링크가 남아있는 한 blob은 레포지토리에서 사라지지 않습니다!

 

blob과 파일의 차이점

git blob은 내용에 대한 메타데이터를 가지지 않음으로써 파일과 차이점을 가집니다.

파일 시스템은 자주 수정되는 과정을 지원하도록 설계되었지만,

Git은 그렇지 않기 때문입니다.

 

Git은 객체 불변성 구조입니다.

 

한 번 생성한 blob은 절대 수정하지 않는 구조입니다(물론 삭제는 가능합니다).

blob이 가리키는 파일이 수정되면, 해당 변화된 파일 내용에 따라

새로운 id를 가지는 blob이 생성되기 때문입니다.

 

따라서 Git은 프로젝트의 특정 시점에서,

모든 파일 내용을 스냅샷 형태로 저장해

해당 스냅샷은 절대 변하지 않도록 관리할 수 있습니다.

(blob 형태로 저장되는 구조를 의미합니다.)

 

 

commit 이란

https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F

전체 파일의 스냅샷을 찍는 과정을 커밋이라고 합니다.

이렇게 커밋을 통해 스냅샷을 지속적으로 저장하면,

커밋을 기준으로 전체 프로젝트의 시간 흐름이 생기게 됩니다.

 

이 시간 흐름에 대한 제어,

즉 프로젝트의 버전 관리(이전 상태로 rollback)는

특정 시점의 스냅샷을 불러오는 것을 통해 진행할 수 있습니다.

 

버전은 커밋으로 인해 생긴 해당 시점의 프로젝트의 상태를 의미합니다.

각 버전은 파일(소스코드) 수정으로 인해 서로 다른 파일 내용을 가지고 있습니다.

 

Git은 단일 파일에 대해 존재하는 모든 버전의 내용을 blob으로 가지고 있습니다.

따라서 당시 다른 VCS 도구에 비해 사용하는 용량이 크다고 하는 것입니다.

그러나 버전관리를 위해 예전 commit으로 돌아갈 때 속도가 빠르고 사용하기 좋다는 장점이 있습니다.

또한 많은 용량을 차지하게 될 때 직접 최적화를 하기 때문에, Git이 최고입니다!

 

 

간단한 실습

git init 후, aaaaa 라는 내용을 담은 a.txt 파일을 생성합니다.

$ git status 결과

 

git add 명령어를 통해 staging area에 올리고, 커밋합니다.

 

$ git add ./a.txt로 staging area에 올림



커밋하면, .git/objects 폴더 내부에 폴더가 세 개 생깁니다.

47, 63, e4 디렉토리가 생겼다

 

 

Git - git-cat-file Documentation

If --batch or --batch-check is given, cat-file will read objects from stdin, one per line, and print information about them. By default, the whole line is considered as an object, as if it were fed to git-rev-parse[1]. When --batch-command is given, cat-fi

git-scm.com

 

git cat-file -t <object> 명령어를 통해 해당 object의 type을 확인할 수 있습니다.

 

이때 object 칸에 입력할 내용은

object의 디렉토리 명 + 내부 파일 명이 됩니다.

 

tree, commit, blob 오브젝트가 생성되었다.

 

git cat-file -p <object> 명령어를 통해 해당 object가 무슨 내용을 담고 있는지 확인해봅시다.

 

tree object는 blob의 권한과 hash-id 가 적혀져 있고,

어떤 파일에 대한 blob인지도 알 수 있습니다.

 

commit object는 tree object의 id와

작성자 및 committer를 알 수 있습니다.

 

blob object에는 커밋한 시점의 실제 파일의 내용이 적혀져 있습니다.

 

 

중간 정리

커밋 단위로 tree, commit, blob 오브젝트를 묶어 관리할 수 있습니다.

그리고 blob 오브젝트는, 커밋 당시의 파일 내역을 통째로 저장하고 있음도 알 수 있습니다.

 

commit을 할 때마다 그때의 스냅샷을 디스크에 저장합니다.

변경 사항이 한 파일의 한 글자이더라도, 커밋하면 git은 전체 파일을 스냅샷으로 저장합니다.

이를 느슨한 객체 형식, loose object format 이라 합니다.

 

다른 VCS는 git은 같은 파일의 새 버전과 이전 버전 간의 변경 내역만을 저장하는 구조입니다.

Git은 특유의 구조로 인해 다른 VCS (예: subversion) 보다 디스크 공간을 더 많이 소모하지만,

snapshot을 통해 commit 단계의 시간을 단축하는 이점이 있습니다.

(변경 내역을 델타(변화량) ‘연산’하면서 저장하는게 아니라 그냥 통째로 저장)

 

그러나 Git은 git gc를 이용해 이러한 단점도 없앨 수 있습니다.

PACKFILES를 생성해 비슷한 내용을 가진 스냅샷을 제거해 크기를 줄입니다.

 

이러한 작업 후 git이 차지하는 디스크 용량은 델타 방식을 사용하는 다른 VCS와 비슷하다고 합니다.

Git은 델타 방식을 사용하는 다른 VCS보다 더 빠르고,

차지하는 디스크 크기는 델타 방식을 사용하는 다른 VCS와 비슷할 것입니다.

 

Git은 성능과 디스크 공간 비용 사이에서 균형 잡힌 방법을 찾습니다.

GIT이 최고입니다.

 

팩파일은 .git/objects/pack 아래에 있습니다.

"git gc" 명령을 직접 실행해보면 알 수 있습니다.

 

 

다시, commit 이란

Git에서 commit은 프로젝트의 특정 시점의 스냅샷을 저장하는 핵심 개념입니다.

저는 지금까지 “commit은 어떤 추상적인 작용, 개념이다”라는 느낌으로 알고 있었는데,

Git은 사실 commit도 객체, Object로 관리합니다.

 

각 commit도 SHA-1 해시로 된 고유한 ID를 가집니다.

commit은 부모 commit에 대한 ID를 가질 수 있습니다.

이렇게 여러 commit을 묶어 시간의 흐름, 즉 히스토리를 만들 수 있습니다.

 

 

commit, tree, blob

commit은 프로젝트의 특정 시점의 스냅샷을 저장한다고 했습니다.

이 말은 특정 시점의 모든 파일을 blob형태로 저장하는 것과 동일합니다.

따라서 commit은 커밋시점에 저장된 모든 blob을 관리하고 있어야 합니다.

 

이 때, 모든 blob을 관리하는 자료구조가 바로 tree이고,

따라서 commit은 tree id만 참조해도 되도록 구현되었습니다.

 

tree id 역시 SHA-1 해시로 된 고유한 ID를 가집니다.

짚고 넘어가야 할 점이 하나 있는데,

blob은 파일 이름과 내용에 따라 식별됩니다.

 

즉, 파일 이름과 내용이 같다면, 다른 시점에서 커밋을 해 blob을 생성하더라도,

동일한 blob이 나오기에 객체의 중복을 피해야 합니다.

 

따라서 동일한 blob을 가리키는 경우가 종종 존재합니다.

 

 

 

예시

a.txt 파일 생성

예시를 볼까요?

commit [1] 은

a.txt 파일에 aaaaa를 기입한 내용을 담고 있다고 가정합시다.

// a.txt 파일 생성

aaaaa
$ git add ./a.txt && git commit -m "[1]"

[master (root-commit) ab35e6f] [1]
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt

 

 

아래처럼, git cat-file -p <hash-id> 명령어를 통해 해당 객체가 가지고 있는 내용을 확인할 수 있습니다.

$ git cat-file -p ab35e6f

tree 47a49f4d903e382a053d1511a75d29c56ac8d0a5
author . <.@gmail.com> 1726285207 +0000
committer . <.@gmail.com> 1726285207 +0000

[1]

이 커밋은 47a49f 트리를 가지고 있네요.

 

 

이 트리를 보면 a.txt blob을 알 수 있습니다.

$ git cat-file -p 47a49f

100644 blob e4a7dd9d0225aed28294d66245dd544e5006867e    a.txt

좋습니다. a.txt의 blob을 의미하는 e4a7dd~ blob을 찾을 수 있습니다.

 

 

a.txt 파일 수정

commit [2] 는

a.txt 파일을 baaaa로 수정한 내용을 담고 있습니다.

// a.txt 파일 수정

baaaa
$ git add ./a.txt && git commit -m "[2]"

[master af7ca2f] [2]
 1 file changed, 1 insertion(+), 1 deletion(-)

 

 

commit 객체를 살펴보겠습니다.

$ git cat-file -p af7ca2f

tree 71563988e8a7fe1167872c0c62484815dd36cd08
parent ab35e6f3789785003e6b172138e3ad868a8a6221
author . <.@gmail.com> 1726285820 +0000
committer . <.@gmail.com> 1726285820 +0000

[2]

해당 커밋은 715639~ hash id로 시작하는 tree를 가지고 있습니다.

아까랑 다른 점은 parent가 생겼다는 건데,

이는 처음에 생성했던 루트 커밋을 의미합니다.

 

이 커밋은 af7ca2f 커밋 다음에, 동일한 브랜치에서 진행한 커밋이기 때문에

바로 연결될 수 있었습니다.

이렇게 커밋 히스토리를 쌓아나갈 수 있습니다.

 

 

tree 객체를 살펴보겠습니다.

$ git cat-file -p 715639

100644 blob c1130e8883c7098d4782d6a635757196b73170a7    a.txt

아까랑 똑같은 a.txt파일에 대한 blob인데,

blob id가 e4a7dd가 아니라 c1130e로 시작하는 것을 알 수 있습니다.

 

네, 파일 내용이 달라졌으니 전혀 다른 blob으로 표현된 것입니다.

 

 

이후 다시

commit [3] 에서

a.txt 파일을 aaaaa로 수정하고 커밋해 봅시다.

// a.txt 파일 수정

aaaaa
$ git add ./a.txt && git commit -m "[3]"

[master 87b2ac4] [3]
 1 file changed, 1 insertion(+), 1 deletion(-)

 

 

커밋을 살펴볼까요?

git cat-file -p 87b2ac4

tree 47a49f4d903e382a053d1511a75d29c56ac8d0a5
parent af7ca2f5d4ae31656c503c2d4778b66a3bb54af2
author . <.@gmail.com> 1726286155 +0000
committer . <.@gmail.com> 1726286155 +0000

[3]

어!

트리 id가 맨 처음 커밋했을 때 생성되었던 id와 똑같은 것을 보실 수 있습니다.

똑같은 트리를 가리키고 있는 걸 알 수 있습니다.

 

커밋이 다른 커밋과 모두 동일한 blob을 가지고 있다면

동일한 tree를 가리키는 것을 알 수 있었습니다! 신기하네요!

 

따라서 자연스럽게

commit [3] 이 a.txt에 대해 유지하고 있는 blob은

commit [1] 에서 a.txt에 대해 유지하고 있는 blob과 동일합니다.

 

 

b.txt 만들고 커밋하면, 이번 커밋은 다른 tree를 가질까?

a.txt에 대한 blob id는 이전 커밋과 동일하면서,

b.txt 라는 새로운 요소가 tree에 추가되니 이전과 달리 새로운 tree를 가질 것으로 기대됩니다.

// b.txt 파일 생성

bbbbb
$ git add ./b.txt && git commit -m "[4]"

[master a44bc10] [4]
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt

 

 

커밋을 살펴봅시다!

$ git cat-file -p a44bc10
tree 4669458ee96d6f02cd3e7060b56b6102b0166944
parent 87b2ac44e796627e1c97836787c0cdc63f0aac2b
author . <.@gmail.com> 1726286524 +0000
committer . <.@gmail.com> 1726286524 +0000

[4]

이전과는 다른 트리가 생성되었네요!

 

트리를 살펴볼까요?

$ git cat-file -p 466945

100644 blob e4a7dd9d0225aed28294d66245dd544e5006867e    a.txt
100644 blob 71165b066ad762f608901c937bca51e26da322b5    b.txt

a.txt에 대한 blob은 처음과 같은 e4a7dd~ 임을 알 수 있습니다!

b.txt에 대한 blob은 새로 생성되었네요!

 

 

 

결론

  1. Git의 파일 시스템은 UNIX 파일 시스템과 유사한 구조를 가집니다.
  2. Git은 파일 내용을 blob 객체로 저장하며, 이는 SHA-1 해시로 고유하게 식별됩니다.
  3. blob은 git add 명령어를 통해 변경된 파일을 staging area로 관리할 때 생성됩니다.
  4. blob은 불변성을 가지며, 동일한 내용의 파일은 항상 같은 blob ID를 가집니다.
  5. tree 객체는 디렉토리 구조를 나타내며, blob들을 리프노드로 표현합니다!
  6. commit 객체는 프로젝트의 특정 시점 스냅샷을 나타내며, tree 객체를 참조합니다.
  7. commit 객체마다 참조하는 tree가 존재합니다.
  8. 같은 내용의 파일은 다른 커밋에서도 동일한 blob을 공유하여 저장 공간을 절약합니다.
  9. 파일 내용이 변경되면 새로운 blob이 생성되고, 이에 따라 새로운 tree와 commit이 생성됩니다.
  10. Git의 이러한 구조는, 결국 효율적인 버전 관리와 프로젝트 히스토리 추적을 가능하게 합니다!

 

 

 

 

[ 다음 포스트 ]

 

 

[Git] 버전 관리의 꽃, 브랜치(branch)

Git - Branches in a Nutshell3.1 Git Branching - Branches in a Nutshell Nearly every VCS has some form of branching support. Branching means you diverge from the main line of development and continue to do work without messing with that main line. In many V

300-29-1.tistory.com

 

 

Ref.

 

Git - Book

 

git-scm.com

 

 

Git - Git 개체

여러분이 사용하는 쉘이 어떤 것인가에 따라 master^{tree} 표현식이 오류를 일으킬 수도 있다. Windows 에서 CMD는 ^ 문자는 이스케이프 기호로 사용한다. ^ 문자를 제대로 사용하려면 git cat-file -p master

git-scm.com

 

 

git blob tree — Key Puncher

Git 5: Blobs & Trees

www.keypuncher.net