DVCS 세상에 Git만 존재하는 것은 아니다. 사실 Git 이외에도 다양한 시스템이 존재하는데 각자가 나름의 철학 대로 분산 버전 관리 시스템을 구현했다. Git 이외에는 Mercurial이 가장 많이 사용되는 분산 버전 관리 시스템이며 Git과 닮은 점도 많다.
Mercurial로 코드를 관리하는 프로젝트에서 클라이언트로 Git을 쓰고자 하는 사람에게도 좋은 소식이 있다. Git은 Mercurial 클라이언트로 동작할 수 있다. Mercurial을 위한 Bridge는 리모트 Helper로 구현돼 있는데 Git은 리모트를 통해서 서버 저장소의 코드를 가져와서 그렇다. 이 프로젝트의 이름은 git-remote-hg이라고 하며 https://github.com/felipec/git-remote-hg에 있다.
우선 git-remote-hg을 설치한다. 아래처럼 PATH 실행경로에 포함된 경로중 아무데나 git-remote-hg 파일을 저장하고 실행 권한을 준다.
$ curl -o ~/bin/git-remote-hg \
https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg예제에서는 ~/bin 디렉토리가 $PATH 실행경로에 포함되어 있다고 가정한다.
git-remote-hg를 실행하려면 Python 라이브러리 mercurial 이 필요하다.
Python이 설치되어있다면 아래처럼 Mercurial 라이브러리를 설치한다.
$ pip install mercurial(Python 설치가 안돼 있다면 https://www.python.org/ 사이트에서 다운로드 받아 설치한다.)
마지막으로 Mercurial 클라이언트도 설치해야 한다. https://www.mercurial-scm.org/ 사이트에서 다운로드 받아 설치할 수 있다.
이렇게 필요한 라이브러리와 프로그램을 설치하고 나면 준비가 끝난다. 이제 필요한 것은 소스코드를 Push 할 Mercurial 저장소다. 여기 예제에서는 Mercurial을 익힐 때 많이 쓰는 "hello world" 저장소를 로컬에 복제하고 마치 리모트 저장소인 것 처럼 사용한다.
$ hg clone http://selenic.com/repo/hello /tmp/hello이제 Push 할 수 있는 “서버”(?) 저장소가 준비됐고 여러가지 작업을 해 볼 수 있다. 잘 알려진 대로 Git과 Mercurial의 철학이나 사용방법은 크게 다르지 않다.
Git에서 늘 하던 것처럼 처음에는 Clone을 먼저 한다.
$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program리모트 저장소가 hg로 시작하는 Mercurial 저장소지만 git clone 명령으로 쉽게 Clone 할 수 있다.
사실 내부에서는 git-remote-hg Bridge가 Git에 포함된 HTTP/S 프로토콜(리모트 Helper)과 비슷하게 동작한다.
Git과 마찬가지로 Mercurial또한 모든 클라이언트가 전체 저장소 히스토리를 복제(Clone)해서 사용하도록 만들어졌기 때문에 Clone 명령으로 히스토리를 포함한 저장소 전체를 가져온다. 예제 프로젝트는 크기가 작아서 저장소를 금방 clone 한다.
log 명령으로 커밋 두 개를 볼 수 있으며 가장 최근 커밋으로는 여러 Ref 포인터로 가리키고 있다.
Ref중 일부는 실제 존재하지 않을 수도 있다.
.git 디렉토리가 실제로 어떻게 구성돼 있는지 보자.
$ tree .git/refs
.git/refs
├── heads
│ └── master
├── hg
│ └── origin
│ ├── bookmarks
│ │ └── master
│ └── branches
│ └── default
├── notes
│ └── hg
├── remotes
│ └── origin
│ └── HEAD
└── tags
9 directories, 5 filesgit-remote-hg는 Git 스타일로 동작하도록 만들어 주는데 속으로 하는 일은 Git과 Mercurial을 매핑해 준다.
리모트 Ref를 refs/hg 디렉토리에 저장한다.
예를 들어 refs/hg/origin/branches/default 는 Git Ref 파일로 내용은 master 브랜치가 가리키는 커밋인 “ac7955c” 로 시작하는 SHA 해시값이다.
refs/hg 디렉토리는 일종의 refs/remotes/origin 같은 것이지만 북마크와 브랜치가 구분된다는 점이 다르다.
notes/hg 파일은 git-remote-hg가 Git 커밋을 Mercurial Changeset ID와 매핑을 하기 위한 시작지점이다.
살짝 더 안을 들여다보면.
$ cat notes/hg
d4c10386...
$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800
Notes for master
$ git ls-tree 1781c96...
100644 blob ac9117f... 65bb417...
100644 blob 485e178... ac7955c...
$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9refs/notes/hg 파일은 트리 하나를 가리킨다. 이 트리는 다른 객체와 그 이름의 목록인 Git 객체 데이터베이스다.
git ls-tree 명령은 이 트리 객체 안에 포함된 모드, 타입, 객체 해시, 파일 이름으로 된 여러 항목을 보여준다.
트리 객체에 포함된 한 항목을 더 자세히 살펴보면 “ac9117f” 으로 시작하는 이름(master 가 가리키는 커밋의 SHA-1 해시)의 Blob 객체를 확인할 수 있다. “ac9117f” 이 가리키는 내용은 “0a04b98” 로 시작하는 해시로 default 브랜치가 가리키는 Mercurial Changeset ID이다.
이런 내용은 몰라도 되고 모른다고 걱정할 필요 없다. 일반적인 워크플로에서 Git 리모트를 사용하는 것과 크게 다르지 않다.
다만 한가지, 다음 내용으로 넘어가기 전에 Ignore 파일을 살펴보자.
Mercurial과 Git의 Ignore 파일은 방식이 거의 비슷하지만 아무래도 .gitignore 파일을 Mercurial 저장소에 넣기는 좀 껄끄럽다.
다행히도 Mercurial의 Ignore 파일 패턴의 형식은 Git과 동일해서 아래와 같이 복사하기만 하면 된다.
$ cp .hgignore .git/info/exclude.git/info/exclude 파일은 .gitignore 파일처럼 동작하지만 커밋할 수 없다.
이런저런 작업을하고 master 브랜치에 커밋하면 원격 저장소에 Push 할 준비가 된다.
현재 저장소 히스토리를 살펴보면 아래와 같다.
$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" programmaster 브랜치는 origin/master 브랜치보다 커밋이 두 개를 많으며 이 두 커밋은 로컬에만 존재한다.
그와 동시에 누군가가 커밋해서 리모트 저장소에 Push 했다고 가정해보자.
$ git fetch
From hg::/tmp/hello
ac7955c..df85e87 master -> origin/master
ac7955c..df85e87 branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program--all 옵션으로 히스토리를 보면 “notes” Ref도 볼 수 있는데 git-remote-hg에서 내부적으로 사용하는 것이므로 유저는 신경쓰지 않아도 된다.
나머지 내용은 예상한 대로다. origin/master 브랜치에 커밋 하나가 추가되어 있어 히스토리가 갈라졌다.
이 장에서 살펴보는 다른 버전관리 시스템과는 달리 Mercurial은 Merge를 충분히 잘 다루기 때문에 특별히 더 할 일이 없다.
$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
hello.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
* 0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program완벽하고 멋져. 이렇게 Merge 하고 나서 테스트가 통과한다면 정말로 Push 하고 공유할 준비가 끝난 것이다.
$ git push
To hg::/tmp/hello
df85e87..0c64627 master -> master정말 완벽하게 멋져! Mercurial 저장소 히스토리를 살펴보면 기대한대로 모든 것이 멋지게 끝난 것을 확인할 수 있다.
$ hg log -G --style compact
o 5[tip]:4,2 dc8fa4f932b8 2014-08-14 19:33 -0700 ben
|\ Merge remote-tracking branch 'origin/master'
| |
| o 4 64f27bcefc35 2014-08-14 19:27 -0700 ben
| | Update makefile
| |
| o 3:1 4256fc29598f 2014-08-14 19:27 -0700 ben
| | Goodbye
| |
@ | 2 7db0b4848b3c 2014-08-14 19:30 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" programChangeset 2 번은 Mercurial로 만든 Changeset이다. 3 번과 4 번 Changeset은 git-remote-hg로 만든 Changeset이고 Git으로 Push 한 커밋이다.
Git 브랜치는 한 종류 뿐이다. Git 브랜치는 새 커밋이 추가되면 자동으로 마지맛 커밋으로 이동하는 포인터다. Mercurial에서는 이런 Refs를 “북마크” 라고 부르는데 하는 행동은 Git의 브랜치와 같다.
Mercurial에서 사용하는 “브랜치” 의 개념은 Git보다 좀 더 무겁다.
Mercurial은 Changeset에 브랜치도 함께 저장한다. 즉 브랜치는 히스토리에 영원히 기록된다.
develop 브랜치에 커밋을 하나 만드는 예제를 살펴보자.
$ hg log -l 1
changeset: 6:8f65e5e02793
branch: develop
tag: tip
user: Ben Straub <ben@straub.cc>
date: Thu Aug 14 20:06:38 2014 -0700
summary: More documentation“branch” 로 시작하는 라인이 있는 것을 볼 수 있다. Git은 이런 방식을 흉내낼 수(흉내낼 필요도) 없다(Git의 ref로 표현할 수는 있겠다). 하지만 Mercurial이 필요로 하는 정보이기에 git-remote-hg는 이런 비슷한 정보가 필요하다.
Mercurial 북마크를 만드는 것은 Git의 브랜치를 만드는 것과 같이 쉽다. Git으로 Clone 한 Mercurial 저장소에 아래와 같이 브랜치를 Push 한다.
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
* [new branch] featureA -> featureA이렇게만 해도 북마크가 생성된다. Mercurial로 저장소 내용을 확인하면 아래와 같다.
$ hg bookmarks
featureA 5:bd5ac26f11f9
$ hg log --style compact -G
@ 6[tip] 8f65e5e02793 2014-08-14 20:06 -0700 ben
| More documentation
|
o 5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
|\ Merge remote-tracking branch 'origin/master'
| |
| o 4 0434aaa6b91f 2014-08-14 20:01 -0700 ben
| | update makefile
| |
| o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| | goodbye
| |
o | 2 f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" program[featureA] 태그가 리비전 5에 생긴 것을 볼 수 있다.
Git을 클라이언트로 사용하는 저장소에서는 Git 브랜치처럼 사용한다. Git 클라이언트 저장소에서 한 가지 할 수 없는 것은 서버의 북마크를 삭제하지 못 한다(이는 리모트 Helper의 제약사항이다).
Git보다 무거운 Mercurial 브랜치도 물론 사용 가능하다. 브랜치 이름에 branches 네임스페이스를 사용하면 된다.
$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
* [new branch] branches/permanent -> branches/permanent위의 내용을 Mercurial에서 확인하면 아래와 같다.
$ hg branches
permanent 7:a4529d07aad4
develop 6:8f65e5e02793
default 5:bd5ac26f11f9 (inactive)
$ hg log -G
o changeset: 7:a4529d07aad4
| branch: permanent
| tag: tip
| parent: 5:bd5ac26f11f9
| user: Ben Straub <ben@straub.cc>
| date: Thu Aug 14 20:21:09 2014 -0700
| summary: A permanent change
|
| @ changeset: 6:8f65e5e02793
|/ branch: develop
| user: Ben Straub <ben@straub.cc>
| date: Thu Aug 14 20:06:38 2014 -0700
| summary: More documentation
|
o changeset: 5:bd5ac26f11f9
|\ bookmark: featureA
| | parent: 4:0434aaa6b91f
| | parent: 2:f098c7f45c4f
| | user: Ben Straub <ben@straub.cc>
| | date: Thu Aug 14 20:02:21 2014 -0700
| | summary: Merge remote-tracking branch 'origin/master'
[...]“permanent” 라는 브랜치가 Changeset 7 번에 기록됐다.
Mercurial 저장소를 Clone 한 Git 저장소에서는 Git 브랜치를 쓰듯 Checkout, Checkout, Fetch, Merge, Pull 명령을 그대로 쓰면 된다. 반드시 기억해야 할 게 하나 있는데 Mercurial은 히스토리를 재작성을 지원하지 않고 단순히 추가된다. Git으로 Rebase를 하고 강제로 Push 하면 Mercurial 저장소의 모습은 아래와 같아진다.
$ hg log --style compact -G
o 10[tip] 99611176cbc9 2014-08-14 20:21 -0700 ben
| A permanent change
|
o 9 f23e12f939c3 2014-08-14 20:01 -0700 ben
| Add some documentation
|
o 8:1 c16971d33922 2014-08-14 20:00 -0700 ben
| goodbye
|
| o 7:5 a4529d07aad4 2014-08-14 20:21 -0700 ben
| | A permanent change
| |
| | @ 6 8f65e5e02793 2014-08-14 20:06 -0700 ben
| |/ More documentation
| |
| o 5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
| |\ Merge remote-tracking branch 'origin/master'
| | |
| | o 4 0434aaa6b91f 2014-08-14 20:01 -0700 ben
| | | update makefile
| | |
+---o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| | goodbye
| |
| o 2 f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" programChangeset 8, 9, 10 이 생성됐고 permanent 브랜치에 속한다. 하지만 예전에 Push 했던 Changeset들이 그대로 남아있다.
그러면 Mercurial 저장소를 공유하는 동료들은 혼란스럽다. 이딴식으로 커밋을 재작성 하고 강제로 Push 하지 말지어다.