본문 바로가기

IT

[git] 깃의 속사정, 4대 원소를 파헤치기

git에 대한 변변한 서적이 한 권 없던 시절,

한 선구자가 프로젝트에 전격적으로 git을 도입하였습니다.

이미 다른 형상관리툴에 익숙한 개발자들은 git이 결코 달갑지 않았습니다.

기존 툴을 장점을 수없이 열거하며 자신들에게 익숙한 과거로 회귀하자는 르네상스운동을 벌였습니다.

하지만 몇 년이 지난 지금...

이제는 git에 익숙해진 대다수 개발자들은,

'아직도 git을 사용하지 않는' 개발자들을 나무랄 정도로 git 신봉자가 되어버렸습니다.

이는 비단 우리 집단에서만 벌어진 일은 아닐 것입니다.

 

 

git은 가볍고 빠르죠.

'쉘환경이 윈도우보다 편한 개발자들'에게는 다른 툴보다 쉬울 지도 모릅니다-

 

다른 형상관리툴을 압도하기 위해,

git은 영악하게 설계되었습니다.

 

그리고 그 설계의 핵에는 4가지 원소가 있습니다.

commit, tree, blob, tag.

 

C언어에 char, int, long, float, double과 같은 데이터 타입이 있는 것처럼,

git은 내부적으로 commit, tree, blob, tag의 4가지 오브젝트 타입을 관리합니다.

 

이러한 오브젝트는 .git/objects에 개별적인 파일들로 존재합니다.

하나의 commit, 하나의 tree, 하나의 blob 그리고 하나의 tag는 각각 하나의 파일입니다.

두개의 commit은- 당연히 두개의 파일입니다.

 

오브젝트가 담긴 파일의 이름은 git이 오브젝트 컨텐츠의 내용을 참고하여 생성하는 40자리 문자열입니다.

git에 "hello.txt"라는 파일을 하나 추가하면,

"hello.txt"라는 이름의 오브젝트를 생성하는 것이 아닙니다.

"hello.txt"의 내용 전부를 해시테이블에 넣어,

40자리의 해시값을 뽑아내어 오브젝트 파일 이름으로 사용합니다.

 

그렇다면, "hello.txt"라는 이름은 어디에 저장되는 것일까요?

"hello.txt"를 위한 오브젝트인 blob에는 파일이름인 "hello.txt"라는 문자열이 저장되지 않습니다.

대신 디렉토리 구조를 나타내는 tree 오브젝트에서 "hello.txt"라는 문자열을 찾을 수 있습니다.

이는 리눅스 파일시스템에서 흔히 사용하는 inode - dentry의 관계와 동일합니다.

(이에 대한 설명은 본 주제와 무관하므로 생략합니다.)

리눅스를 개발한 리누즈가 git에도 동일한 개념을 차용했다는 점은 쉽게 유추할 수 있습니다.

 

이러한 오브젝트들은 하나의 파일로 .git/objects에 차곡차곡 쌓이게 되는데,

한 디렉토리에 너무 많은 파일이 있으면 파일 시스템의 성능이 저하될 수 있기 때문에,

오브젝트의 파일이름 중 앞 2글자는 디렉토리 이름으로 사용하고,

나머지 38글자를 파일이름으로 사용하게 됩니다.

 

각각의 오브젝트 타입마다 담고 있는 내용은 아래와 같습니다.

 

 blob(binary large object)

- 타입 : "blob" 타입
- 사이즈 : 컨텐츠의 용량을 bytes로 표시
- 컨텐츠 : blob의 컨텐츠에는 텍스트, 이미지, 음악 혹은 단순 이진 파일처럼 다양한 형식의 파일이 저장될 수 있다.
  파일이름이나 파일형식은 blob에 저장되지 않는다.
  파일의 메타정보를 제외한 파일의 내용 전체를 품는다.

 

 tree

- 타입 : "tree" 타입
- 사이즈 : 트리 오브젝트의 용량을 bytes로 표시
- tree 객체 : 하위 디렉토리의 트리 객체를 재귀적으로 참조할 수 있다.
- blob 객체 : 한 디렉토리에 있는 모든 blob을 담고 있다.
   객체에 대한 접근권한, 파일이름은 여기서 관리한다.

 

 commit

- 작성자
- 커밋 실행자
- 커밋 날짜
- 로그 메시지
- tree 객체 : 해당 커밋에서의 dir/file의 상태를 알 수 있다.

 

 tag

- 객체종류
- 태그이름
- tagger
- 태그메시지
- PGP 서명정보


git에는 고맙게도 오브젝트의 상태정보를 얻을 수 있는 명령어가 노출되어 있습니다.

git cat-file을 오브젝트에 사용하여 오브젝트가 품고 있는 정보를 자세히 알아보려 합니다.

usage: git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>
   or: git cat-file (--batch|--batch-check) < <list_of_objects>

<type> can be one of: blob, tree, commit, tag
    -t                    show object type
    -s                    show object size
    -e                    exit with zero when there's no error
    -p                    pretty-print object's content
    --textconv            for blob objects, run textconv on object's content
    --batch[=<format>]    show info and content of objects fed from the standard input
    --batch-check[=<format>]
                          show info about objects fed from the standard input


- 우선, 새로운 git repository를 하나 만듭니다.

$ git init
$ ls -la .git/objects/
.git/objects/:
합계 16
drwxrwxr-x 4 jinux jinux 4096 4월 6 00:57 .
drwxrwxr-x 7 jinux jinux 4096 4월 6 00:57 ..
drwxrwxr-x 2 jinux jinux 4096 4월 6 00:57 info
drwxrwxr-x 2 jinux jinux 4096 4월 6 00:57 pack

위의 .git/object 디렉토리에는 info와 pack 디렉토리가 있습니다.

이 두 디렉토리가 오브젝트는 아닙니다.

 

- "hello.txt" 를 추가해보자.

$ echo "Hello" > Hello.txt
$ git add Hello.txt
$ git commit -m "Add Hello.txt"
[master (root-commit) 44cac0a] Add Hello.txt
1 file changed, 1 insertion(+)
create mode 100644 Hello.txt
$ ls -la .git/objects/
합계 28
drwxrwxr-x 7 jinux jinux 4096 4월 6 02:03 .
drwxrwxr-x 8 jinux jinux 4096 4월 6 02:03 ..
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:03 2e
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:03 44
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:03 e9
drwxrwxr-x 2 jinux jinux 4096 4월 6 00:57 info
drwxrwxr-x 2 jinux jinux 4096 4월 6 00:57 pack

위에 총 3개의 오브젝트(2e, 44, e9)가 생겼습니다.

파일 하나(e9)를 추가했고,

파일이 담긴 tree(2e)가 추가되었고,

커밋(44)이 하나 추가되었습니다.

우선, 커밋의 내용을 살펴보죠.

 

$ git cat-file -p 44cac0afe16f0819bba6cc2332b9418ba3c1ce8b
tree 2ea873e13e84497d7459150a0b2b662403e3bc2b
author Storycompiler <storycompiler@hanmail.net> 1428253401 +0900
committer Storycompiler <storycompiler@hanmail.net> 1428253401 +0900

Add Hello.txt

커밋이 가리키는 tree ID가 명시되어 있습니다.

author와 committer가 시간과 함께 기록되어 있습니다.

그리고 마지막 줄은 메시지가 있습니다.

위의 커밋이 가리키는 트리도 살펴보죠.

 

$ git cat-file -p 2ea873e13e84497d7459150a0b2b662403e3bc2b
100644 blob e965047ad7c57865823c7d992b1d046ea66edf78 Hello.txt

트리는 저장된 오브젝트가 blob인지 tree이지 가리는 인자가 권한정보를 뒤이어 나옵니다.

그리고 오브젝트의 ID와 파일 혹은 디렉토리이름이 나옵니다.

파일명 혹은 디렉토리명을 트리에서 관리한다는 것을 다시 확인할 수 있습니다.

 

 

blob가 가리키는 파일을 아래와 같습니다.

 

$ git cat-file -p e965047ad7c57865823c7d992b1d046ea66edf78
Hello

blob에는 파일의 내용만 저장되어 있습니다.

그 외의 메타정보는 찾을 수가 없습니다.

 

- "world.txt"를 추가해보죠.

 

$ echo "World" > "world.txt"
$ git add world.txt
$ git commit -m "Add world.txt"
[master 839b975] Add world.txt
1 file changed, 1 insertion(+)
create mode 100644 world.txt
$ ls -la .git/objects/
합계 40
drwxrwxr-x 10 jinux jinux 4096 4월 6 02:34 .
drwxrwxr-x 8 jinux jinux 4096 4월 6 02:34 ..
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:34 08
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:33 21
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:03 2e
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:03 44
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:34 83
drwxrwxr-x 2 jinux jinux 4096 4월 6 02:03 e9
drwxrwxr-x 2 jinux jinux 4096 4월 6 00:57 info
drwxrwxr-x 2 jinux jinux 4096 4월 6 00:57 pack

 

오브젝트가 6개가 되었습니다.

기존의 오브젝트(2e, 44, e9)는 그대로 있고,

커밋(83), 트리(08), 새로운 파일(21) 3개의 오브젝트 파일이 추가되었습니다.

커밋이나 파일은 모두 새로운 내용이므로 새로운 파일이 당연합니다.

그렇다면 트리는 왜 추가되었을까요?

왜냐하면 트리는 blob에 대한 내용물을 담고 있는데,

파일이 하나 늘면서 해당 blob을 트리에 추가하였기 때문입니다.

$ git cat-file -p 08be0f512375756b66561c0dcfb8429cd2ab5193
100644 blob e965047ad7c57865823c7d992b1d046ea66edf78	Hello.txt
100644 blob 216e97ce08229b8776d3feb731c6d23a2f669ac8	world.txt

- "subdir" 디렉토리를 추가한 후, "hello.txt"와 "world.txt"를 복사해보죠.

$ mkdir subdir
$ ls
Hello.txt  subdir  world.txt
$ cp Hello.txt subdir/
$ cp world.txt subdir/
$ git add subdir
$ git commit -m "Add subdir"
[master 2f5bfb4] Add subdir
 2 files changed, 2 insertions(+)
 create mode 100644 subdir/Hello.txt
 create mode 100644 subdir/world.txt
$ ls -al .git/objects/
합계 48
drwxrwxr-x 12 jinux jinux 4096  4월  6 02:44 .
drwxrwxr-x  8 jinux jinux 4096  4월  6 02:44 ..
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:34 08
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:33 21
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:03 2e
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:44 2f
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:03 44
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:44 6f
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:34 83
drwxrwxr-x  2 jinux jinux 4096  4월  6 02:03 e9
drwxrwxr-x  2 jinux jinux 4096  4월  6 00:57 info
drwxrwxr-x  2 jinux jinux 4096  4월  6 00:57 pack


subdir을 생성하고,

파일 2개를 복사해서 넣었습니다.

하지만 현재 총 오브젝트 파일은 기존에서 2개만 추가된 8개입니다.

기존의 오브젝트 파일(2e, 44, e9, 83, 08, 21)에서 2f, 6f 오브젝트만 추가되었습니다.

이 파일 두개는 무엇일까요?


$ git cat-file -p 2f5bfb452a671dc72b9f1ac17865212c98311262 
tree 6f3f9a776e764914ce54a476e39155d1fe4ddf65
parent 839b97545a2ec5a34a8c6f0152be72b418a82df3
author Storycompiler  1428255851 +0900
committer Storycompiler  1428255851 +0900

우선 2f 파일은 신규로 생성한 커밋 오브젝트 파일입니다.


$ git cat-file -p 6f3f9a776e764914ce54a476e39155d1fe4ddf65
100644 blob e965047ad7c57865823c7d992b1d046ea66edf78	Hello.txt
040000 tree 08be0f512375756b66561c0dcfb8429cd2ab5193	subdir
100644 blob 216e97ce08229b8776d3feb731c6d23a2f669ac8	world.txt

다른 하나는 subdir이 생겨서 내용이 변경된 root tree의 오브젝트입니다.

그렇다면, subdir의 객체(08be0f512375756b66561c0dcfb8429cd2ab5193)는 어디에 있을까요?

subdir은 "Hello.txt"와 "world.txt"를 가지는 디렉토리로,

좀 전의 root 디렉토리를 지칭한 트리객체와 그 내용이 동일합니다.

따라서 08be로 시작하는 기존의 트리 오브젝트를 그대로 사용하게 됩니다.

 

git의 속사정을 몰라도 개발하는데 아무 지장이 없습니다.

사실 세상 대부분을 몰라도 개발하는데 아무 지장이 없습니다.

하지만, 그 많은 개발자 중에 어쩌면 한두 명이라도 궁금해할지도 모른다는 생각에 포스팅을 남깁니다.

혹시 당신이 바로 그 개발자라면...

당신의 존재가 이 포스팅을 월요일 오전 3시 20분에 작성하고 있는 날 위안시켜주네요.

출근 2시간 40분 전.

 

 

_끝