프로그램을 개발하다보면 소스에 대한 버전관리는 필수 이다.  오래된 CVS 사용이후  SVN을 사용했었고, 최근에는 Git을 사용하고 있는데.  사실 Git에 대한 얘기를 들은지는 오래됐었지만 실제 사용까지는 시간이 많이 걸렸었데

 


 






 

git은 다른 버전 관리 소프트웨어들과 다른 개념을 갖고 있는게 있다보니 이를 이해하는것이 귀찮았었다.  하지만 Git 이 좋다니 몇가지 명령만으로 사용하고는 있었는데  최근에  Git 를  일종의 파일시스템으로 이용해봐야 겠다는 생각이 있어. 이렇게 하기위해서  Git에 대한 깊숙한 이해가 필요하기에

좀더 상세 하게 알아보게 되었다.

 

그러다 보니 그간 불분명했던 Git에 대한 것이 좀더 명확해졌는데. 이를 정리해 볼까 한다.   여기서는 Git 의 Repository 구조를 알기위해서는 Gi에서 Tree라는 object를 정확히 이해 하고 있어야하는데 이해대한 이해를 위해  git 의 Low Level 명령어를 이용해  Git repository에 파일과 디렉토리를 추가하는 것을 해볼것이다.

 

준비환경은 git 을 실행할 수 있는 환경이면 어디든 상관없는데. 참고로 여기서는 Windows 에 msysgit(http://code.google.com/p/msysgit/) 을 설치했을때 생겨나는 bash 쉘 에서 실행했다.

 

아래 내용은  git 의 문서인 (정확히는 Pro Git이라는 책의 온라인 링크) http://git-scm.com/book/en/Git-Internals 경로의 내용을 내 나름대로 좀더 명확하게 다뤄봤다고나 하는 정도이다.

 

 

먼저 git init 명령으로 빈  git 디렉토리 생성하고 진행한다.

 

 init 명령을 통해 git repository를 생성하고 나서 이하의 디렉토리를 살펴보면 다음과 같다.

 $ find .git -type f
.git/config
.git/description
.git/HEAD
.git/hooks/applypatch-msg.sample
.git/hooks/commit-msg.sample
.git/hooks/post-commit.sample
.git/hooks/post-receive.sample
.git/hooks/post-update.sample
.git/hooks/pre-applypatch.sample
.git/hooks/pre-commit.sample
.git/hooks/pre-push.sample
.git/hooks/pre-rebase.sample
.git/hooks/prepare-commit-msg.sample
.git/hooks/update.sample
.git/info/exclude



hash-object 명령을 이용해 object ID와  blob object를 생성한다.

 파일에서가 아닌 직접 입력으로 파일을 생성한다. 

$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4


위 명령을 하고 난뒤  git의 database 공간인  .git/objects  디렉토리 이하를 살펴보면   하나의 object가 새로 생성되었음을 알 수 있다.  

아래 명령 실행 참고
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4


위의 것은 파일로 부터 object를 만드는 것과 어떤 차이가 있을까?

이번에는 object를 파일로 부터 만들어 본다.

$ echo 'test content  2' > second.txt

 

$ git hash-object -w second.txt
warning: LF will be replaced by CRLF in second.txt.
The file will have its original line endings in your working directory.
031da66cfe6457d8fb541fa95e54e5b600576d41

 위 명령어는 파일로 부터 내용을 읽어 blob object와  object ID를 생성해서  git database에 생성한다.

단지 파일에서 내용을 읽을 뿐  앞서  echo 명령을 이용해 데이터를 바로 전달한 것과 차이가 없다

다음 명령실행결과를 보면  둘간에 차이가 없음을 알 수 있다.

$ find .git -type f
.git/config
.git/description
.git/HEAD
.git/hooks/applypatch-msg.sample
.git/hooks/commit-msg.sample
.git/hooks/post-commit.sample
.git/hooks/post-receive.sample
.git/hooks/post-update.sample
.git/hooks/pre-applypatch.sample
.git/hooks/pre-commit.sample
.git/hooks/pre-push.sample
.git/hooks/pre-rebase.sample
.git/hooks/prepare-commit-msg.sample
.git/hooks/update.sample
.git/info/exclude
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

단지 git/objects/ 디렉토리에 object파일(031da6)이 하나 만들어 졌을 뿐이다.  이 말인즉,  파일명 정보는  아직 저장 되지 않았다는 의미 이다. 단지 내용만 object로 만들어 졌다. 

실제로 object에 파일명을 붙여주는(매핑해주는) 작업은 추가로 해주어야 한다.

git 에서 object와  해당 object의 이름(파일명 또는 또 다른 tree 명)을 매핑해주는 정보는  tree 라는 object에 저장하게 된다. 

즉 파일명을 붙여주려면 tree object를 만들어 주어야 한다. tree object를 만들기 위해서는 먼저 index 정보가 필요하다. index 내용을 이용해서 tree object를 만들기 때문이다.



update-index 명령으로 이를 해줄 수 있으며  앞서  echo명령을 이용해 내용을 전달해서 만든 object 에 first.txt 라는 파일명을 부여해줘보겠다.

$ git update-index --add --cacheinfo 10644 d670460b4b4aece5915caf5c68d12f560a9fe3e4 first.txt

위 명령을 하고 나서 .git 디렉토리를 살펴보면 
 
$ find .git -type f
.git/config
.git/description
.git/HEAD
.git/hooks/applypatch-msg.sample
.git/hooks/commit-msg.sample
.git/hooks/post-commit.sample
.git/hooks/post-receive.sample
.git/hooks/post-update.sample
.git/hooks/pre-applypatch.sample
.git/hooks/pre-commit.sample
.git/hooks/pre-push.sample
.git/hooks/pre-rebase.sample
.git/hooks/prepare-commit-msg.sample
.git/hooks/update.sample
.git/index
.git/info/exclude
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

.git/index 파일이 생겨났음을 알 수 있다. 

이 index 정보는 곧  git 에서 말하는 stage 상태의  (cache)정보가 된다.  

그리고 아직 object들간의 관계정보는 없는데 관계정보라 함은,  object의 이름. object의 위치(파일시스템에서 보면 어떤 디렉토리에 존재하는 지 정보 등) 이들 관계정보를 갖는 것이 결국 tree 이다. 

git 에서 tree 에 대한 개념만 정확히 이해 하면 사실 90% 이상은 이해하는 것 같다.

git에서  commit 이라는 것이 특정 시점의 특정한 object들의 관계정보를 저장하면서 commit 이라는 object를 만드는 것인데 결국 이는 tree 정보를 말한다.   다만 commit object와  tree object 차이점은  commit object는 특정한 tree object를 가리키고 여기에 comment와 누가 만들고 commit했는지 정보를 추가하는 것 뿐이다.

여기서 오해가 없어야 할것이 대상 tree object는 계속 바뀐다는 것이다.  파일을 추가한다는 것은 기존 tree 정보를 활용해 object가 추가된다는 것이고, 파일을 변경한다는 것은 기존 object를 둔채 새로운 object가 역시 추가된 다는 것이고 

(좀더 상세히 말하면 중간에 index 에 추가되거나 변경되는 object들에 대한 매핑정보를 가지고 있다가 write-tree 에서 실제 새로운 tree 를 만들면서 저장이 된다.)   그리고 추가되고나 바뀐 내용을 저장하기 위해서는 새로운 tree object 가 생겨난다는 것이다.  따라서 해당 tree object를 가리키는 oid는 당연히 바뀌고 이때 commit을 하기되면 commit object가 가리켜야할 tree object역시 바뀌어야 하기 때문이다.



  index =   object's name + object type + oid 

  tree =  writed index 

  commit =  tree  +  comment + author info    라고 보면 되겠다.


그리고 branch 와 같은 reference 도  모두  특정한 commit을 가리키게되는 데 결국 특정시점에 만들어진 tree object를 가리키게 되는 셈이다.

이제 앞서 만든 index 내용을 tree object로 만들어 보자 

$ git write-tree
802e62ded81357dc7b0f74ed37f72bf245f88a4b

 tree 역시 object 로 만들어지기 때문에 object ID를 뱉어 냈다.

$ find .git/objects -type f
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/80/2e62ded81357dc7b0f74ed37f72bf245f88a4b
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

그리고 해당 tree object의 내용을 살펴보면  

$ git cat-file -p 802e62ded81357dc7b0f74ed37f72bf245f88a4b
100644 blob d670460b4b4aece5915caf5c68d12f560a9fe3e4    first.txt

 이제 commit object를 만들어 볼 것이다.  commit을 만들었다는 것은 이를 이용해 branch 를 지정 해 줄 수 있다는 의미이다.

앞서 만든 tree object를 이용해  commit-tree 명령을 하면  commit object가  생겨난다. 

$ echo 'commit first' | git commit-tree 802e62
296f93c8cd0adbd687cbf73b0c8cef61e138e71e

그리고 다시 한번 objects 디렉토리를 살펴보면   object가 하나 추가 되어있다. 해당 object는 commit object이다. 

$ find .git/objects -type f
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/29/6f93c8cd0adbd687cbf73b0c8cef61e138e71e
.git/objects/80/2e62ded81357dc7b0f74ed37f72bf245f88a4b
.git/objects/99/12b44e53af4bce15827526c1709f75bc7bb88a
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 

$ git cat-file -p 296f93c8cd0adbd687cbf73b0c8cef61e138e71e
tree 802e62ded81357dc7b0f74ed37f72bf245f88a4b
author user@somecomp.co.kr <user@somecomp.co.kr> 1395645128 +0900
committer commiter@somecomp.co.kr <commiter@somecomp.co.kr> 1395645128 +0900

commit first


이렇게 만들어진 commit을 master(reference)로 지정해 보겠다. 방법은 간단하다.  
해당 commit object oid .git/refs/heads/master  에 넣어주면 된다.

먼저 해당 폴더를 살펴보면 아직 파일이 없다. 

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags

 그리고 앞서 만든 commit 의 oid를 이용해 master 파일을 만든다.

$ echo  "296f93c8cd0adbd687cbf73b0c8cef61e138e71e" > .git/refs/heads/master

 이제 만들어진 master를 checkout해볼텐데 앞서 현재 디렉토리 상황을 한번 보면 


$ ls
second.txt

앞서 테스트용으로 만들어봤던 second.txt 파일이 있다. 

이제 checkout을 할 텐데  , chechkout 하기에 앞서 최종적으로 commit을 만들때 사용한 tree 에는 first.txt라는 파일명을 붙여준 object하나만을 매핑 하고 있음을 기억하고 있자.

checkout 에 -f 옵션을 붙여 줄텐데  이는 현재 작업디렉토리 상황을 무시하고 master branch 내용대로 파일들을 chechkout하라는 의미이다. 안그러면 현재 작업디렉토리에 first.txt 가 없기 때문에 삭제된 것으로 인지 하 기 때문이다.

$ git checkout -f master
Already on 'master'
$ ls
first.txt  second.txt

 자 이렇게 해서  파일없이 git object추가 부터 commit 까지 해보았다.

그럼 디렉토리는 어떻게 추가할까? 

git 에서 디렉토리는 tree 가 그 역할을 하게 되는데  tree 는 기본적으로 다른 object에 대한 매핑정보없이는 만들어질 수 없다.  즉 한개의 object라도 매핑이 되어야 한다.  

예를 들어 mydir 이라는 디렉토리가 생성되려면,  mydir/ 밑에 특정 object를 있는 것으로 경로를 지정해주면서 index에 추가 해주면 된다.

추가하기전에 먼저 현재 object 목록을 살펴보자


$ find .git/objects -type f
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/29/6f93c8cd0adbd687cbf73b0c8cef61e138e71e
.git/objects/80/2e62ded81357dc7b0f74ed37f72bf245f88a4b
.git/objects/99/12b44e53af4bce15827526c1709f75bc7bb88a
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4


그리고 서브디렉토리생성및  object 추가 하기 

$ git update-index --add --cacheinfo 10644 031da66cfe6457d8fb541fa95e54e5b600576d41 mydir/echo_second.txt

이렇게 index에 mydir/echo_second.txt를 추가하더라도 object 상황은 바뀐게 없다. 

$ find .git/objects -type f
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/29/6f93c8cd0adbd687cbf73b0c8cef61e138e71e
.git/objects/80/2e62ded81357dc7b0f74ed37f72bf245f88a4b
.git/objects/99/12b44e53af4bce15827526c1709f75bc7bb88a
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

단지 index 파일에만 내용이 저장되기 때문이다. 

그런데 이렇게 하고 나서 write-tree를 하게 되면 어떨게 될까.

$ git write-tree
adf331e74367e0a991c41b7918e14be8faf3189b

우선 해당 tree를 살펴보면  mydir 이라는 tree object를 가리키고 있는 것을 볼 수 가있는데, 

 $ git cat-file -p adf331e74367e0a991c41b7918e14be8faf3189b
100644 blob d670460b4b4aece5915caf5c68d12f560a9fe3e4    first.txt
040000 tree 1f6e6885b83dd560a9d6ea062a3e3a997b035855    mydir

정작 echo_second.txt는 안보인다.  어디있을까 ?
이것은 다시 mydir 이라는 tree object를 살펴보면 된다.

$ git cat-file -p  1f6e6885b83dd560a9d6ea062a3e3a997b035855
100644 blob 031da66cfe6457d8fb541fa95e54e5b600576d41    echo_second.txt

이렇게 write-tree를 하게되면 해당 tree 에 대한 새 object(adf331e) 와  echo_second.txt를 포함해야하는 tree object(1f6e688) 이 생겨나게 된다.

$ find .git/objects -type f
.git/objects/03/1da66cfe6457d8fb541fa95e54e5b600576d41
.git/objects/1f/6e6885b83dd560a9d6ea062a3e3a997b035855
.git/objects/29/6f93c8cd0adbd687cbf73b0c8cef61e138e71e
.git/objects/80/2e62ded81357dc7b0f74ed37f72bf245f88a4b
.git/objects/99/12b44e53af4bce15827526c1709f75bc7bb88a
.git/objects/ad/f331e74367e0a991c41b7918e14be8faf3189b
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 


즉 git 에서는 directory라는 형태의 object가 없고  다른 tree를 포함하는것으로 서브 디렉토리역할을 하게 되는데. tree 는 기본적으로 object의 관계정보를 가지고 있어야하기때문에  결국 디렉토리를 만들기 위해서는 하나의 이상의 파일을 포함해야 하게 된다.

그러다 보니 git 에서 빈 디렉토리가 필요할 경우  git add 명령을 이용해 비어있는 서브디렉토리를 지정해줘 봤자 추가가 안된다.  그래서 보통은  .gitignore 와 같은 파일을 하나 만들어 두고 추가하는 방법을 쓰곤 한다.

 

끝으로 이글을 정리하기 전에 참고한 링크 이다.

http://git-scm.com/book/en/Git-Internals

 

 

 

+ Recent posts