[220112] Git이 커밋데이터를 저장하는 방식
Table of Contents
1 .git
git init
이후 만들어지는 .git
폴더의 디렉토리 구조
config description HEAD hooks/ info/ objects/ refs/
hooks/
는 클라이언트|서버 훅을 관리한다.
config
파일에는 해당 프로젝트에만 적용되는 설정 옵션이 있다.
objects/
모든 컨텐츠(object, 개체)를 저장하는 데이터베이스
refs/
커밋 개체(object)의 포인터(브랜, 태그, 리모트)를 저장한다.
HEAD
현재 checkout한 브랜치를 가리킴
index
staging area 정보를 저장한다.
2 Git 개체(object) 모델 개요
Git은 단순 Key-Value(파일이름-파일데이터) 구조이다.
Git은 두 개의 주요 자료구조가 있다.
object store
: DVCS(Distributed Version Control System)을 지원하는 방식의 한 부분으로써 효율적으로 복제를 수행할 때, copy가 효율적으로 되도록 하기 위함.index
: 인덱스는 임시적인 리포지토리 전용(private) 정보이다. 필요시 임의로 저장|수정할 수 있다.
2.1 The SHA (Secure Hash Algorithm)
40-digit의 "object name"을 가진다. 아래처럼 생김
8hf67f4664981e4397625791c8eabbb5f2279a31
이 SHA1
해시함수를 개체의 내용(content of object)을 인자로 받아서 수행한다. 즉, 값이 다르면 다른 hash값이 나온다. 즉, 같은 내용물이면 같은 hash가 나온다.
여기서 장점은
- Git은 아주 빠르게 두 개체가 동일한지 아닌지 확인할 수 있다. (전체 content가 아니라 hash만 비교하면 되니까)
2.다른 리포지토리에서 저장된 동일한 컨텐츠는 항상 동일한 이름으로 저장된다.
2.2 Git Object Store (Git 개체가 저장하는 것)
원본 파일, 로그메시지, 저자 정보, 날짜 그리고 프로젝트 branch나 revision을 재구축하기 위해 필요한 정보들.
또한 모든 object는 아래 3개를 가진다.
size
: 내용의 사이즈 (size of content)
content
: type
따라 그에 맞는 content를 갖는다.
type
: .git/objects
한 곳에 여러 타입의 object를 모두 저장한다.
blob
: 파일데이터를 저장하는데 사용. (일반적으로 파일)tree
: 트리는 기본적으로 디렉토리와 같다. 이 안에는 다른 트리와 blob이 있다.(서브디렉토리 와 파일)commit
: 리포지토리 내 소개되는 각 변경의 메타데이터를 저장한다. (작성자, 커미터, 커밋데이터, 로그메시지)tag
: 커밋된 특정 개체에 사람이 읽을 수 있도록 임의의 이름을 할당하는 것이다.
2.3 Subversion 과의 다른점
Serversion은 하나의 커밋과 그 다음 커밋과의 변경점만 저장한다. Git은 프로젝트 내 모든 파일의 snapshot를 커밋할 때마다 찍는다.
2.4 실습으로 보기 (Blob, Tree, Commit 타입은 어떻게 생기는가!)
$ git init test $ cd test $ touch index.txt $ echo "yhnam" > index.txt $ touch README.md $ echo "yhnam README" > README.md $ git add . $ git commit -m "initial commit"
이제 만들어진 구조를 보자.
$ tree .git/objects .git/objects ├── 69 │ └── e38ac9e9e6078d04c83e826d12c4cf41750d5f ├── 8b │ └── 489c1af624eb25297c88176af71f7175015f0b ├── ca │ └── 857a429caa2ddf9d46ed9a44267f6fcc87f785 ├── d7 │ └── 6e599d899107369b5f877ade67c35776954194 ├── info └── pack 6 directories, 4 files
여기 보면 폴더마다 2개의 글자가 있다. Git은 40자의 체크섬(checksum, SHA-1) 해시를 각 개체마다 만들고, 앞의 두글자는 폴더에 놓는다. 각 object에 대해서 알아보자.
$ git cat-file -p 69e38ac9e9e6078d04c83e826d12c4cf41750d5f 100644 blob 8b489c1af624eb25297c88176af71f7175015f0b README.md 100644 blob ca857a429caa2ddf9d46ed9a44267f6fcc87f785 index.txt $ git cat-file -p 8b489c1af624eb25297c88176af71f7175015f0b yhnam README $ git cat-file -p ca857a429caa2ddf9d46ed9a44267f6fcc87f785 yhnam $ git cat-file -p d76e599d899107369b5f877ade67c35776954194 tree 69e38ac9e9e6078d04c83e826d12c4cf41750d5f author ssisksl77 <ssiskl77@gmail.com> 1641982046 +0900 committer ssisksl77 <ssiskl77@gmail.com> 1641982046 +0900 initial commit
우리는 두 파일을 커밋했다. 그결과로
blob
타입 object 2개(8b489c1af624eb25297c88176af71f7175015f0b
,ca857a429caa2ddf9d46ed9a44267f6fcc87f785
)tree
타입 object 1개(69e38ac9e9e6078d04c83e826d12c4cf41750d5f
)commit
타입 object 1개(d76e599d899107369b5f877ade67c35776954194
)
그림으로 알아보자. (hash이름은 위 출력값과 다를 수 있다)
Figure 1: Git Blob Type Object
Figure 2: Git Tree Type Object
기본적으로 Tree 형식은 Root가 있기 마련이다! 즉, 모든 파일(모든 blob
개체)을 연결하고 있는 하나의 tree
개체가 있다.
다시 tree
개체를 보자.
git cat-file -p 69e38ac9e9e6078d04c83e826d12c4cf41750d5f 100644 blob 8b489c1af624eb25297c88176af71f7175015f0b README.md 100644 blob ca857a429caa2ddf9d46ed9a44267f6fcc87f785 index.txt
- 첫번째컬럼 : 100644은 file's permission을 말함.(100은 무시) 600 - 오너는 읽기/쓰기 가능 644 - 오너는 읽기/쓰기 가능. 그룹멤버/다른유저는 읽기 가능.
- 두번째컬럼 : 타입을 말하는 듯. 위에서는
blob
을 말하고 있다. - 세번째컬럼 : 이
blob
의 해시 - 네번째컬럼 : 파일명
아까 봤던 커밋메시지를 다시 보자. 출력한 정보 맨 위에는 커밋메시지가 연결되어 있는 트리를 보여주고 있다.
$ git cat-file -p d76e599d899107369b5f877ade67c35776954194 tree 69e38ac9e9e6078d04c83e826d12c4cf41750d5f author ssisksl77 <ssiskl77@gmail.com> 1641982046 +0900 committer ssisksl77 <ssiskl77@gmail.com> 1641982046 +0900 initial commit
Figure 3: Git Commit Type Object
3 git hash-object <data>
git hash-object <data>
: .git/objects/
디렉토리에 <data>
저장 및 접근할 수 있는 Key를 리턴.
$ git hash-object -h usage: git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>... or: git hash-object --stdin-paths -t <type> object type -w write the object into the object database --stdin read the object from stdin --stdin-paths read file names from stdin --no-filters store file as is without filters --literally just hash any random garbage to create corrupt objects for debugging Git --path <file> process file as it were from this path
3.1 실습하기
$ git init test Initialized empty Git repository in /tmp/test/.git/ $ cd test $ find .git/objects .git/objects .git/objects/info .git/objects/pack $ find .git/objects -type f
아직 아무것도 없다. 추가해보자.
$ echo 'test content' | git hash-object -w --stdin $ find .git/objects -type f .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
(여기서 -w
를 안하면 key는 리턴하지만 object는 수행하지 않는다. 즉, 변경사항이 없기 때문에 동일한 Key가 리턴된다.)
objects에 파일이 생겼다. Git은 데이터를 저장할 때 데이터와 헤더로 생성한 SHA-1 체크섬
으로 파일 이름을 짓는다. 해시의 처음 두 글자를 따서 디렉토리 이름에 사용하고 나머지 38글자를 파일 이름에 사용한다.
이제 각 변경사항마다 object를 만들어 보자.
$ 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/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects/83/baae61804e65cc73a7201a7252750c76066a30
각 변경이 있을 때마다 hash가 생긴다.
이제 text.txt
를 지워도 살릴 수 있다.
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt $ cat test.txt version 1 $ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt $ cat test.txt version 2
4 Tree 개체를 깊게 들여다보자
4.1 cat-file 로 읽어보기
$ git cat-file -p master^{tree}
master^{tree}
구문은 master 브랜치가 갖는 Tree 개체를 말한다.
폴더를 추가 하나 더 해보고 위 구문을 수행해보자.
4.2 하나 만들어보기
Git은 Staging Area(Index)의 상태대로 Tree개체를 만들고 기록한다.
4.2.1 Staging Area에 파일 추가 후 index 만들기
update-index
로 test.txt
만 들어있는 index를 만든다.
(Staging Area에 없는 파일이기 때문에 --add
추가 디렉토리파일이 아니라 데이터베이스에 있는 파일을 추가하기에 --cacheinfo
옵션 필요.)
$ git update-index --add --cacheinfo 100644 \ 83baae61804e65cc73a7201a7252750c76066a30 test.txt
4.2.2 Tree 개체로 저장하기
git write-tree
를 사용한다.
git write-tree error: invalid object 100644 83baae61804e65cc73a7201a7252750c76066a30 for 'test.txt' fatal: git-write-tree: error building trees
허허 test.txt
를 만들지 않고 추가했더니 안된다. 다시 해보자.
새 파일을 만들고 Git에 저장한다.
$ echo 'version 1' > test.txt $ git hash-object -w test.txt 83baae61804e65cc73a7201a7252750c76066a30 $ echo 'version 2' > test.txt $ git hash-object -w test.txt 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a ## 시작 $ git update-index --add --cacheinfo 100644 \ 83baae61804e65cc73a7201a7252750c76066a30 test.txt $ git write-tree ab24b11a30d2405206bd59165287845efbbf6554 $ git cat-file -t ab24b11a30d2405206bd59165287845efbbf6554 tree $ git cat-file -p ab24b11a30d2405206bd59165287845efbbf6554 100644 blob 8b489c1af624eb25297c88176af71f7175015f0b README.md 100644 blob c7b8df2aa621085bb6223f225ccdd19c4199c083 index.txt 040000 tree 7cf039735dff8e8575d086902da4efbccf2b7f48 lib 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt $ echo 'new file' > new.txt $ git update-index --add --cacheinfo 100644 \ 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt $ git update-index --add new.txt $ git write-tree 4b6a39b87cf5d65ca4511c68a8c27f5090b506df $ git cat-file -p 4b6a39b87cf5d65ca4511c68a8c27f5090b506df 100644 blob 8b489c1af624eb25297c88176af71f7175015f0b README.md 100644 blob c7b8df2aa621085bb6223f225ccdd19c4199c083 index.txt 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
우리는 여기서 test.txt
만 보면 된다.
test.txt
의 SHA값은 1f7a7a
이다. 우리는 이렇게 트리를 만들 수 있다.
더 나아가서 tree 개체를 하위 디렉토리로 만들어보자. 즉, 폴더 안으로 이동시킨다. (tree depth 증가시키기)
$ git read-tree --prefix=bak 7cf039735dff8e8575d086902da4efbccf2b7f48 $ git write-tree 0a8d1f6ba884f50667ccaa4bdc2715b363adf8ca
이렇게 하면 bak
디렉토리 안에 lib
트리가 있게 된다.
$ git cat-file -p 0a8d1f6ba884f50667ccaa4bdc2715b363adf8ca 100644 blob 8b489c1af624eb25297c88176af71f7175015f0b README.md 040000 tree 7cf039735dff8e8575d086902da4efbccf2b7f48 bak 100644 blob c7b8df2aa621085bb6223f225ccdd19c4199c083 index.txt 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
4.3 커밋개체
Tree 개체로 프로젝트 스냅샷은 만들었지만 아직 SHA-1를 외워야 한다는 점. 대체 누가 언제 왜 저장했는지에 대한 정보가 필요하다는 점. (그리고 이전 커밋이 뭔지도!)
커밋개체가 해결한다.
$ echo 'first commit' | git commit-tree 0a8d1f6ba884f50667ccaa4bdc2715b363adf8ca 80fe4576ec01ca81ac1954bcb5b028f367e12bab $ git cat-file -p 80fe4576ec01ca81ac1954bcb5b028f367e12bab git cat-file -p 80fe4576ec01ca81ac1954bcb5b028f367e12bab tree 0a8d1f6ba884f50667ccaa4bdc2715b363adf8ca author ssisksl77 <ssiskl77@gmail.com> 1641990390 +0900 committer ssisksl77 <ssiskl77@gmail.com> 1641990390 +0900 first commit
커밋 개체 형식을 보자.
- 맨 위는 최상단 Tree(루트 디렉토리)를 가리킨다.
user.name
,user.email
: 설정에서 가져온 author, commiter- 시간정보
echo
로 찍은 커밋메시지
4.3.1 commit-tree
이 함수로 만들 수 있다. Tree 개체와 SHA-1 값을 넘긴다.
$ mkdir lib $ echo "HI" > lib/a.txt $ git add . $ git commit -m "add folder and file" $ git cat-file -p master^{tree} 100644 blob 8b489c1af624eb25297c88176af71f7175015f0b README.md 100644 blob c7b8df2aa621085bb6223f225ccdd19c4199c083 index.txt 040000 tree 7cf039735dff8e8575d086902da4efbccf2b7f48 lib
이전 커밋을 가리키도록 할 수 있다.
$ echo 'second commit' | git commit-tree <지금커밋> -p <예전커밋>
우리가 지금까지 만든 .git/objects
를 보고 끝내자.
$ find .git/objects -type f | awk -F'/' '{print($3$4)}' 69e38ac9e9e6078d04c83e826d12c4cf41750d5f a3417ed29ce52de00849edc6d732fb46476348ce d76e599d899107369b5f877ade67c35776954194 b35da2a112a058936be0d61dba8ebf32bac3aabd abc40794b12174b59df43d8cd47d871e9aaff78e ab24b11a30d2405206bd59165287845efbbf6554 c7b8df2aa621085bb6223f225ccdd19c4199c083 ca857a429caa2ddf9d46ed9a44267f6fcc87f785 edebb11f6207f100793fb16674b82364101fc71c c1e3b52e700b18a2b8e1d2616e9277ab447bddd0 4b6a39b87cf5d65ca4511c68a8c27f5090b506df 7cf039735dff8e8575d086902da4efbccf2b7f48 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a 80fe4576ec01ca81ac1954bcb5b028f367e12bab 103c423144db1ef971ef69dc02695fcf8957924b 0a8d1f6ba884f50667ccaa4bdc2715b363adf8ca e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 fa49b077972391ad58037050f2a75f74e3671e92 83baae61804e65cc73a7201a7252750c76066a30 8b489c1af624eb25297c88176af71f7175015f0b $ find .git/objects -type f | awk -F'/' '{print($3$4)}' | xargs -n1 git cat-file -t tree commit commit tree tree tree blob blob tree blob tree tree blob commit commit tree blob blob blob blob
5 Reference
- git object type : https://medium.com/mindorks/what-is-git-object-model-6009c271ca66
- pro git (git objects) : https://git-scm.com/book/en/v2/Git-Internals-Git-Objects