GIT - Directory
git directory
1. directory
1.1 Object
object 는 git을 구성하는 데이터파일 같은 것이다. 즉, working directory 파일 정보를 object 형식으로 변환하여 object database ({working directory}/.git/object) 에 저장한다.
각 object 파일은 Zlib을 이용하여 압축며 파일의 내용과 헤더를 40자의 SHA-1 해시값으로 저장한다.
object는 blob, tree, commit, tag 로 세분화 된다.
1.1.1 blob
working directory 파일 즉, 내용이 저장되는 object 이다. blob 에는 파일의 이름이나 형식은 저장되지 않고 파일의 내용 만 저장된다. 쉽게 말하면 이름이 다른 2개의 파일이 프로젝트에 있어도 내용이 같으면 git 은 blob 을 하나만 저장한다는 것을 뜻한다. (물론, clone 이나 fetch 등을 할 경우에도 파일은 하나만 전송된다.)
$ cd .git/objects/
$ ls -al
81/
info/
pack/
$ cd 81/
$ ls -al
78c76d627cade75005b40711b92f4177bc6cfc
.git/objects/
디렉토리를 확인하면 하위 3개의 디렉토리를 확인할 수 있다. {info}, {pack} 은 git init
할 때 git 이 생성해주는 디렉토리이며 {81} 디렉토리 내부로 들어가 확인하면 파일_(38자리 파일명)_ 이 생성된 것을 확인할 수 있다. git은 40자리 해시를 생성할 때 앞의 2자리를 디렉토리명으로 만들어 파일을 생성한다.
즉, 실제로 생성된 blob의 이름은 8178c76d627cade75005b40711b92f4177bc6cfc 이다.
git 에서 제공하는 cat-file
명령으로 데이터를 확인할 수 있다.
$ git cat-file -p 8178c76d627cade75005b40711b92f4177bc6cfc
readme
$ git cat-file -t 8178c76d627cade75005b40711b92f4177bc6cfc
blob
-p 옵션은 파일의 내용을 확인할 수 있으며 -t 옵션은 타입을 확인할 수 있다. (위 예에서는 README file 의 내용인 ‘readme’ 문자열과 object 의 타입인 ‘blob’ 이 확인됨)
1.1.2 tree
working directory 의 디렉토리 즉, 디렉토리 구조가 저장되는 object 이다. tree 의 내용은 해당 디렉토리 내부의 파일과 디렉토리 정보 (파일명, 형식, SHA-1, …) 를 담은 blob 과 tree object 의 리스트이다.
$ git commit -m 'first commit'
# On branch master
#
# Initial commit
#
1 file changed, 1 insertions(+), 0 deletions(-)
create mode 100644 README
$ cd /home/test/git_example/.git/objects
$ ls -al
72/
81/
b7/
info/
pack/
tree object 를 확인하기 위해서 현재까지 staging area 를 commit 후 object 디렉토리에는 최초 ‘81/’ 디렉토리외에 2개의 디렉토리가 더 생성된 것을 확인할 수 있다.
$ cd b7/
$ ls
7313d7be366609dd2e77aa96d7fd73f4e27853
$ git cat-file -t b77313d7be366609dd2e77aa96d7fd73f4e27853
tree
$ git cat-file -p b77313d7be366609dd2e77aa96d7fd73f4e27853
100644 blob 8178c76d627cade75005b40711b92f4177bc6cfc README
tree object 를 살펴본 위의 예에서 type 은 tree 이며, 내용은 README 파일의 정보를 담고 있음을 확인할 수 있다. 이는 working directory 의 루트 디렉토리 정보를 가지고 있는 tree object 임을 의미한다.
1.1.3 commit
commit history를 저장하고 있는 object 이다.
$ git cat-file -t 721350e7569afabfee7c544c57daf6997f21efba
commit
$ git cat-file -p 721350e7569afabfee7c544c57daf6997f21efba
tree b77313d7be366609dd2e77aa96d7fd73f4e27853
author uni2u <uni2u@mail.address> 1463649969 +0900
committer uni2u <uni2u@mail.address> 1463649969 +0900
first commit
파일 내용을 위의 예와 같이 살펴볼 수 있다. author, committer, commit message 를 포함하고 있으며 내용의 가장 첫번째 줄에 tree object 에 대한 정보가 포함된다. 이 tree object 정보는 해당 commit 스냅샷의 최상단 tree 를 가리키는 포인터 이다. (tree object 즉, 루트의 SHA-1 값을 가리키고 있음을 확인함)
$ echo "test" > test.txt
$ git add *
$ git commit -m 'second'
[master de55b98] second
1 files changed, 1 insertions(+), 0 deletions (-)
create mode 100644 test.txt
$ git cat-file -p de55b98c4a4ce69f04faecd3ffd8bee4983ffb05
tree 8b7d9f48e5f99cce57911e37e88b8fe5b07310dc
parent 721350e7569afabfee7c544c57daf6997f21efba
author uni2u <uni2u@mail.address> 1463652360 +0900
committer uni2u <uni2u@mail.address> 1463652360 +0900
second
위 예와 같이 test.txt 파일을 생성 및 commit 후 해당 commit object 를 찾아서 내용을 보면 parent 가 추가된 것을 볼 수 있다. 이는 현재 commit 바로 직전 commit SHA-1 값을 가리키는 포인터이다. 이 포인터를 통해 git 은 commit 의 부모를 참조할 수 있다.
1.1.4 tag
git 의 특정 commit 에 tag 를 달면 tag object 가 생성된다. tag 는 Lightweight 와 Annotated 로 나뉘는데 Lightweight 는 단순히 특정 commit 에 대한 포인터로 동작하는 반면, Annotated 는 tag 의 작성자, 메일, 날짜, 메시지 등을 저장하고 있으며 보안을 위해 GPG 서명을 포함할 수 있다.
1.2 References
git object 는 한번 생성되면 그 값을 변화할 수 없다. 즉, 파일이 수정되면 새로운 object 가 생성되는 구조이다. 또한 40자리의 SHA-1 코드는 접근하기 어렵다. 이러한 이유로 reference 가 존재한다. 이는 특정 commit 을 가리키는 포인터라는 점에서 tag object 와 유사하지만 reference 는 그 값을 변화할 수 있다. (우리가 이미 git 에서 사용하고 있는 branch, remote, HEAD 같은 요소를 reference 라고 함)
1.3 Data Model
+------+
| HEAD |
+------+
|
V
+------+
|master|
+------+
|
V
+------+ +------+
|commit|<---------|commit|
|721350| |de55b9|
+------+ +------+
| |
V V
+----------+ +----------+
|tree(root)| |tree(root)|
| b77313 | | 8b7d9f |
+----------+ +----------+
| | |
V +---+ V
+------------+ | +--------------+
|blob(README)|<-+ |blob(test.txt)|
| 8178c7 | | 9daeaf |
+------------+ +--------------+
상기 모든 예를 도식화 하면 위의 그림과 같다. 최초 README 파일은 최초 commit 이후 변화가 없기 때문에 두번째 commit 최상의 tree 가 첫번째와 같은 blob 을 가리키고 있고 두번째 commit 에서 새로 생성된 test.txt 만 추가됨을 알 수 있다. 두번째 commit 을 master branch reference 가 가리키고 있고 그 reference 를 다시 HEAD reference 가 바라보고 있다.
이 상태에서 /dir/test.txt/ 가 추가되고, README 파일이 수정된다면 다음과 같다.
+--------+
| HEAD |
+--------+
|
V
+--------+
| master |
+--------+
|
V
+--------+ +--------+ +--------+
| commit |<----------| commit |<--------| commit |
| 721350 | | de55b9 | | bc8aaa |
+--------+ +--------+ +--------+
| | |
V V V
+----------+ +----------+ +----------+
|tree(root)| |tree(root)| |tree(root)|
| b77313 | | 8b7d9f | | d5e2fb |
+----------+ +----------+ +----------+
| | | | | |
V +------+ V +---+ V +------+
+------------+ | +--------------+ | +------------+ | +---------+
|blob(README)|<-+ |blob(test.txt)|<-+ |blob(README)| +->|tree(dir)|
| 8178c7 | | 9daeaf | | c1102f | | 2a6469 |
+------------+ +--------------+ +------------+ +---------+
|
V
+------------------+
|blob(dir/test.txt)|
| dec2cb |
+------------------+
commit object, root tree object 가 생성되어 신규 생성된 dir tree object, README, dir/text blob object 들을 바라보고 있다. git 은 이런 방식으로 commit 이 될 때 마다 비순환 그래프 (DAG: Directed Acyclic Graph) 를 만든다.
1.3.1 branch
위와 같은 저장소에서 branch 를 만들었을 때 git 모델을 보면 다음과 같다. (dev branch 를 git branch dev 명령을 통하여 만들고 checkout 후 README 파일을 수정, commit 했다고 가정)
+--------+
| master |
+--------+
|
V
+------+ +------+ +------+
|commit|<------------|commit|<----------|commit|<---------------------------+
|721350| |de55b9| |bc8aaa| |
+------+ +------+ +------+ |
| | | |
V V V |
+----------+ +----------+ +----------+ |
|tree(root)| |tree(root)| |tree(root)|<---------------------+ |
| b77313 | | 8b7d9f | | d5e2fb | | |
+----------+ +----------+ +----------+ | |
| | | | | | | |
V +------+ V +---+ V +------+ | |
+------------+ | +--------------+ | +------------+ | +---------+ | |
|blob(README)|<-+ |blob(test.txt)|<-+ |blob(README)| +->|tree(dir)| | |
| 8178c7 | | 9daeaf | | c1102f | | 2a6469 | | |
+------------+ +--------------+ +------------+ +---------+ | |
^ | | |
| V | |
| +------------------+ | |
| |blob(dir/test.txt)| | |
| | dec2cb | | |
| +------------------+ | |
| +--------+ |
+--------------------------------+ | |
| V V
+------------+ +----------+ +------+
|blob(README)|<-----|tree(root)|<-----|commit|
| f3aad0 | | 430b43 | |5f6682|
+------------+ +----------+ +------+
^
|
+---+ +----+
|dev|<---|HEAD|
+---+ +----+
commit 하나 추가로 인해 발생한 일련의 도식이다. 새로운 commit object 와 tree object 가 추가되었고, README 파일 내용이 수정되었기 때문에 README blob object 도 새로 생성되었다. 나머지는 변함이 없기 때문에 포인터들이 그대로 가리키고 있다.
1.3.2 merge
이 상태에서 master branch 로 돌아와 dev 를 merge 하면 다음과 같다.
+------+
| HEAD |
+------+
|
V
+------+ +---+
|master| |dev|
+------+ +---+
| |
+---+ +---+
V V
+------+ +------+ +------+ +------+
|commit|<---|commit|<---|commit|<---|commit|
|721350| |de55b9| |bc8aaa| |5f6682|
+------+ +------+ +------+ +------+
새로운 commit object 를 생성하지 않고 master branch 의 포인터만 원래 생성된 commit object 의 SHA-1 값으로 변화하였다. 이는 merge 하기 전의 dev branch 가 가리키는 commit object 가 master branch 가 가리키는 commit object 와 같기 때문이다. 즉, master branch 의 바로 다음 진행이 되어도 무방한 commit 이기 때문에 단순히 branch 의 포인터만 바꾼것으로 merge 가 완료된다. 이를 fast forward 라고 한다.
+------+ +------+ +------+ +------+ +----+
|commit|<---|commit|<---|commit|<---|master|<---|HEAD|
|bc8aaa| |5f6682|<-+ |6e65b4| +------+ +----+
+------+ +------+ | +------+
|
| +------+ +---+
+-|commit|<---|dev|
|946f20| +---+
+------+
위와 같이 dev branch 의 부모 commit 이 master branch 가 아니고 분리가 된 상황에서 다시 merge 를 진행하면 다음과 같다.
+------+ +------+ +------+ +------+ +------+ +----+
|commit|<----|commit|<----|commit|<----|commit|<----|master|<----|HEAD|
|bc8aaa| |5f6682|<-+ |6e65b4| +--|946f20| +------+ +----+
+------+ +------+ | +------+ | +------+
+---+ +---+
| |
V V
+------+ +---+
|commit|<----|dev|
|946f20| +---+
+------+
이 경우는 fast forward 가 되지 않고 새로운 commit 이 하나 생성된 후 master branch 가 해당 commit 으로 옮겨진다. 새로 생성된 commit 은 parent 를 2개 가지고 있는것을 확인할 수 있다. git 의 merge 는 기본적으로 이런 모델로 동작한다.