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 는 기본적으로 이런 모델로 동작한다.