Git 레퍼런스

git log 1a410e 라고 실행하면 전체 히스토리를 볼 수 있지만, 여전히 1a410e를 기억해야 한다. 이 커밋은 마지막 커밋이기 때문에 히스토리를 따라 모든 개체를 조회할 수 있다. SHA-1 값을 날로 사용하기보다 쉬운 이름으로 된 포인터를 사용하는 것이 더 좋다. 즉 SHA-1 값을 쉬운 이름으로 저장한 파일이 필요하다.

Git에서는 이런 것을 "레퍼런스" 또는 "refs"라고 부른다. .git/refs 디렉토리에 SHA-1 값이 들어 있는 파일이 있다. 현 프로젝트에 아직 레퍼런스는 하나도 없지만, 그 구조는 매우 단순하다:

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f
$

레퍼런스가 있으면 마지막 커밋이 무엇인지 기억하기 쉽다. 사실 내부적으로는 다음과 같이 단순하다:

$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master

SHA-1 값 대신에 지금 만든 레퍼런스를 사용할 수 있다:

$ git log --pretty=oneline  master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

레퍼런스 파일을 직접 고치는 것은 좀 못마땅하다. Git은 좀 더 안전하게 바꿀 수 있는 update-ref 명령을 하고 있다:

$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9

Git의 브랜치의 역할이 바로 이것이다: 브랜치는 일련의 작업들의 헤드를 참조하는 포인터 또는 레퍼런스이다. 간단히 두 번째 커밋을 가리키는 브랜치를 만들어 보자:

$ git update-ref refs/heads/test cac0ca

브랜치는 직접 가리키는 커밋과 그 커밋으로 따라갈 수 있는 모든 커밋을 포함한다:

$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

이제 Git 데이터베이스는 그림 9-4처럼 보인다.

9-4. 브랜치 레퍼런스가 추가된 Git 데이터베이스
9-4. 브랜치 레퍼런스가 추가된 Git 데이터베이스

git branch (branchname) 명령을 실행하면 Git은 내부적으로 update-ref 명령을 실행한다. 입력받은 브랜치 이름과 현 브랜치의 마지막 커밋에서 SHA-1 값을 가져다 update-ref 명령을 실행하는 것이다.

HEAD

git branch (branchname) 명령을 실행할 때 Git은 어떻게 마지막 커밋의 SHA-1 값을 아는 걸까? HEAD 파일은 현 브랜치를 가리키는 간접(symbolic) 레퍼런스다. 간접 레퍼런스이기 때문에 다른 레퍼런스와 다르게 생겼다. 이 레퍼런스은 다른 레퍼런스를 가리키는 것이라서 SHA-1 값이 없다. 파일을 열어 보면 다음과 같이 생겼다:

$ cat .git/HEAD 
ref: refs/heads/master

git checkout test를 실행하면 Git은 HEAD 파일을 다음과 같이 바꾼다:

$ cat .git/HEAD 
ref: refs/heads/test

git commit을 실행하면 Commit 개체가 만들어지는데, 지금 HEAD가 가리키고 있던 커밋의 SHA-1 값이 그 Commit 개체의 부모로 사용된다.

이 파일도 손으로 직접 편집할 수 있지만 symbolic-ref라는 명령어가 있어서 좀 더 안전하게 사용할 수 있다. 이 명령으로 HEAD의 값을 읽을 수 있다:

$ git symbolic-ref HEAD
refs/heads/master

HEAD의 값을 변경할 수도 있다:

$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD 
ref: refs/heads/test

refs 형식에 맞지 않으면 수정할 수 없다:

$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/

Tag

중요한 개체 타입을 모두 살펴봤지만 남은 개체가 하나 더 있다. Tag 개체는 Commit 개체랑 매우 비슷하다. Commit 개체처럼 누가, 언제 Tag를 달았는지 Tag 메시지는 무엇이고 어떤 커밋을 가리키는지에 대한 정보가 포함된다. Tag 개체는 Tree 개체가 아니라 Commit 개체를 가리킨다는 것이 그 둘의 차이다. 브랜치처럼 Commit 개체를 가리키지만 옮길 수는 없다. Tag 개체는 늘 그 이름이 뜻하는 커밋만 가리킨다.

2장에서 살펴봤듯이 Tag는 Annotated Tag와 Lightweight Tag 두 종류로 나뉜다. 먼저 다음과 같이 Lightweight Tag를 만들어 보자:

$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d

Lightwieght Tag는 만들기 쉽다. 브랜치랑 비슷하지만 브랜치처럼 옮길 수는 없다. 이에 비해 Annotated Tag는 좀 더 복잡하다. Annotated Tag를 만들면 Git은 Tag 개체를 만들고 거기에 커밋을 가리키는 레퍼런스를 저장한다. Annotated Tag는 커밋을 직접 가리키지 않고 Tag 개체를 가리킨다. -a 옵션을 주고 Annotated Tag를 만들어 확인할 수 있다:

$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'

Tag 개체의 SHA-1 값을 확인한다:

$ cat .git/refs/tags/v1.1 
9585191f37f7b0fb9444f35a9bf50de191beadc2

cat-file 명령으로 해당 SHA-1 값의 내용을 조회한다:

$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700

test tag

object 부분에 있는 SHA-1 값이 실제로 Tag를 단 커밋이다. 그리고 Commit 개체에 Tag를 다는 것이 아니라 Git 개체에 Tag를 다는 것이다. 그래서 모든 개체에 Tag를 달 수 있다. Git 프로젝트에서는 관리자가 자신의 GPG 공개키를 Blob 개체로 추가하고 그 파일에 Tag를 달아 둔다. 다음 명령으로 그 공개키를 확인할 수 있다:

$ git cat-file blob junio-gpg-pub

Linux Kernel 저장소에도 커밋이 아닌 다른 개체를 가리키는 Tag 개체가 있다. 그 Tag는 저장소에 처음으로 소스 코드를 임포트했을 때 그 첫 Tree 개체를 가리킨다.

Remote 레퍼런스

그리고 Remote 레퍼런스라는 것도 있다. Remote를 추가하고 Push하면 Git은 각 브랜치마다 Push한 마지막 커밋이 무엇인지 refs/remotes 디렉토리에 저장한다. 예를 들어, origin이라는 Remote를 추가하고 master 브랜치를 Push 한다:

$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
   a11bef0..ca82a6d  master -> master

originmaster 브랜치에서 서버와 마지막으로 교환한 커밋이 어떤 것인지 확인하려면 refs/remotes/origin/master 파일을 확인한다:

$ cat .git/refs/remotes/origin/master 
ca82a6dff817ec66f44342007202690a93763949

Remote 레퍼런스와 refs/heads에 있는 레퍼런스인 브랜치와 차이점은 Checkout할 수 없다는 것이다. 이 Remote 레퍼런스는 서버의 브랜치가 가리키는 커밋이 무엇인지 적어둔 일종의 북마크이다.