A Guide to Branching in Mercurial

원문: http://stevelosh.com/blog/2009/08/a-guide-to-branching-in-mercurial/

난 최근 freenode[1]에 있는 #mercurial과 #bitbucket 채널에서 살고 있는데 “Mercurialgit에서 브랜치가 어떻게 다른가?”라는 질문을 꽤 많이 볼 수 있다.

좀 전에 닉 콰란토(Nick Quaranto)와 나는 Mercurial과 git에서 브랜치 모델에 대해 트워터로 얘기하며 주요한 차이를 재빨리 longreply로 써 보냈다. 일부 git 사용자에게 그 글을 알려줬는데 좋아하는 것 같으므로 좀 더 자세히 설명하기로 했다.

면책 조항: 이 글은 Mercurial로 작업할 때 사용하는 명령을 안내하기 위한 것이 아니라 브랜치 모델에 숨어 있는 개념을 알려주기 위한 것일 뿐이다. 일상적인 사용과 명령은 훌륭한 자료인 hg book을 참고한다(만약 그 내용이 유용하다면 종이 책을 사서 브라이언(Bryan)을 돕고 이제껏 한 편집 실수 중 가장 큰 실수를 한 출판물을 소장해 주길 바란다).

머리말

서로 다른 브랜치 모델을 설명하기 전에 다음은 예로 사용할 간단한 저장소이다.

Basic Repository

저장소는 ~/src/test-project 폴더에 있고 이 저장소에는 체인지셋이 0, 1 그리고 2까지 세 개이다.

git 사용자를 위해: Mercurial 저장소에서 각 체인지셋에는 git처럼 해시를 식별자로 사용한다. 하지만 Mercurial에서는 각 체인지셋에 번호도 할당하는데 이는 단지 지역 저장소만을 위한 것이다. 즉 복제한 저장소 사이에서는 pull, push 순서 등에 따라 체인지셋에 대한 번호가 다를 수 있으며 단지 한 저장소에서 작업할 때 편의성을 높이기 위한 것이다.

Mercurial에서 기본 브랜치 이름은 “default”이다. 그게 무슨 뜻인지 곧 알게 될 테니 지금은 걱정할 필요 없다. 그림에 default 표시를 해 뒀기에 언급했을 뿐이다.

모든 그림에서 점선 테두리로 표시한 것은 실제 객체로 존재하지 않는다. 해시나 번호 대신 체인지셋을 식별하는데 사용할 수 있는 특별한 이름이며 Mercurial에서는 즉시 리비전으로 계산해 낸다.

이제 default 표시는 무시하자. 중요하지 않을 때는 그림에서 회색으로 표시했다.

복제로 브랜치 만들기

Mercurial에서 가장 느리지만 가장 안전하게 브랜치를 만드는 방법은 저장소를 새로 복제하는 것이다.

$ cd ~/src
$ hg clone test-project test-project-feature-branch

이제 저장소 복사본이 두 개이며 각각에서 따로 커밋하고 원하는 대로 그 저장소 사이에서 체인지셋을 push/pull 할 수 있다. 일단 각 저장소에서 변경을 했다면 결과는 다음과 같을 것이다.

Branching with Clones

저장소 복사본이 두 개이고 브랜치를 만들거나 복제할 때 있던 체인지셋이 두 저장소 모두에 있다. test-project에서 test-project-feature-branch로 push하면 “Fix a critical bug” 체인지셋을 보내게 된다.

git 사용자를 위해: 체인지셋 번호는 저장소에 지역적이라고 얘기한 것을 기억하는가? 여기서 명확히 알 수 있다. 즉 서로 다른 두 체인지셋에서 번호가 3이다. 단지 이 번호는 단일 저장소 안에서 작업하는 동안에만 사용한다. push, pull을 하거나 다른 이와 얘기할 때는 해시를 사용해야 한다.

장점

복제는 매우 안전하게 브랜치를 만드는 방법이다. 두 저장소는 push나 pull을 할 때까지 완전히 독립적이므로 한 저장소에서 작업할 때 다른 저장소에 있는 어떤 내용도 망가뜨릴 위험이 없다.

브랜치가 더 이상 필요하지 않아 버리는 것은 브랜치를 복제하는 것만큼 매우 쉽다. 간단히 rm -rf test-project-feature-branch 로 지우면 된다. 저장소 이력을 편집할 필요도 없으며 그저 삭제하면 끝이다.

단점

OS에서 (대부분에서 지원하는) 하드 링크 기능을 제공하면 Mercurial에서는 복제할 때 그 기능을 사용하며 이는 그렇게 느리지 않다. 하지만 복제로 브랜치를 지역적으로 만드는 것은 다른 방법보다 느리다.

그렇지만 브랜치를 복제하는 방법은 (가까이 있지 않은) 다른 개발자가 프로젝트에서 일하고 싶어할 때 정말로 일을 느리게 만들 수 있다. 두 브랜치를 (stable과 version-2 같은) 개별 저장소로 내 보내면 두 브랜치에서 일하려는 기여자는 인터넷을 통해 두 저장소 모두를 복제해 받아야 한다. 이는 저장소 크기와 대역폭에 따라 시간 소비가 많을 수 있다.

예를 들어 브랜치를 만든 시점 이전에 체인지셋이 10,000개 있고 그 이후에 브랜치마다 체인지셋이 100개라면 정말 시간 낭비가 엄청날 수 있다. 가져올 체인지셋이 10,200개가 아니라 20,200 개이다. 더구나 브랜치가 세 개라면 30,300개를 가져와야 한다.

이처럼 받아올 때 생기는 큰 부담을 피하는 방법이 있는데 #mercurial에서 귀도 오스트캠프(Guido Ostkamp)와 timeless_mbp가 알려줬다. 방법은 서버에서 브랜치를 하나 복제하고 여기에 나머지 브랜치를 pull로 가져온다. 그런 다음 이 저장소를 복제해 브랜치로 나눈다. 이렇게 해서 같은 체인지셋을 복제해 받아오는 부담을 피한다.

브랜치가 세 개인 저장소에 이 방법을 사용하는 예는 다음과 같을 것이다.

$ hg clone http://server/project-main project
$ cd project
$ hg pull http://server/project-branch1
$ hg pull http://server/project-branch2
$ cd ..
$ hg clone project project-main --rev [head of mainline branch]
$ hg clone project project-branch1 --rev [head of branch1]
$ hg clone project project-branch2 --rev [head of branch2]
$ rm -rf project
$ cd project-main
$ [edit .hg/hgrc file to make the default path http://server/project-main]
$ cd ../project-branch1
$ [edit .hg/hgrc to make the default path http://server/project-branch1]
$ cd ../project-branch2
$ [edit .hg/hgrc to make the default path http://server/project-branch2]

이렇게 하려면 브랜치 헤드의 ID를 알아야 하는데 아마 모를 것이므로 찾아내야 한다.

게다가 브랜치마다 헤드가 여럿 있더라도 새로운 헤드는 하나뿐이라고 가정한다. 만약 mainline에 없는 헤드 두 개가 branch1에 있다면 둘 모두에 대해 ID를 찾고 두 ID 모두를 clone 명령에 사용해야 한다.

또 다른 귀찮은 일은 작업하는 프로젝트가 특정 파일 경로에 있는 코드에 의존할 때 생긴다. 복제로 브랜치를 만들면 브랜치 사이를 오갈 때마다 디렉터리 이름을 바꿔야 한다(또는 파일 경로 설정을 바꿔야 한다). 일반적으로는 이런 일이 거의 없을 테지만 (빌드 툴 대부분은 해당 코드에 대한 절대 경로에 관여하지 않는다) 가끔 생긴다.

개인적으로는 이 방법을 좋아하지 않으며 사용하지 않는다. 하지만 다른 이는 사용하므로 이해해 두면 좋다(Mercurial에서는 이 방법을 사용한다).

git과 비교

git에서도 이 방법으로 브랜치를 만들 수 있지만 그리 자주 보진 않았다. 기술적으로는 GitHub에서 포크하는 것과 정확히 같지만 대부분의  사람은 “포크”[2]와 “브랜치”를 다른 개념으로 생각한다.

북마크로 브랜치 만들기

다음 방법은 북마크로 브랜치를 만드는 것이다. 예를 보자.

$ cd ~/src/test-project
$ hg bookmark main
$ hg bookmark feature

두 브랜치에서 현재 체인지셋에 (본질적으로는 태그인) 북마크 두 개가 생겼다.

이 브랜치 사이를 오갈 때는 hg update feature 명령을 사용해 해당 브랜치의 최신 체인지셋으로 갱신하고 그 브랜치에서 작업하고 있는 것을 표시해 둘 수 있다. 커밋하면 북마크는 새로 생성한 체인지셋으로 옮겨 간다.

참고: 실제 일상적으로 북마크를 사용하는데 있어 더 자세한 정보는 북마크 페이지를 읽어 보길 바란다. 여기서는 서로 다른 브랜치 모델을 보여주려는 정도이며, 북마크를 사용하려면 알아 둬야 할 것이 있다.

이 방법을 사용한 저장소는 다음과 같을 것이다.

Branching with Bookmarks

위 체인지셋 그림은 꽤 단순하다. 브랜치 지점은 체인지셋 2이고 각 브랜치에는 새로운 체인지셋이 하나있다.

이제 표시해 둔 것을 살펴보자. default 표시는 그대로 있고 계속해서 무시한다.

이 그림에 있는 새로운 레이블이 두 개가 북마크이다. 테두리가 점선이 아니라는 걸 눈치챘나? 북마크는 디스크에 저장하는 실제 객체이며 여러분이 사용하도록 Mercurial에서 만들어 둔 편리한 바로가기 정도가 아니기 때문이다.

북마크 이름을 리비전 대신 사용하면 Mercurial에서는 해당 리비전을 찾아 사용한다.

장점

북마크는 브랜치에 의미 있는 레이블을 붙일 수 있는 빠르고 손쉬운 방법이다.

더 이상 필요하지 않을 때 삭제할 수 있다. 예를 들어 새 기능 개발을 마치고 변경 내용을 주 브랜치에 병합한 후에는 feature 북마크를 유지할 필요가 더 이상 없을 것이다.

단점

손쉽다는 점이 단점도 될 수 있다. 북마크를 삭제하고 일 년 후 코드를 살펴보면 북마크 이름이 더 이상 없으므로 병합한 전체 체인지셋은 무엇 때문에 한 것인지 잘 모르게 된다. 이는 체인지셋 로그를 잘 적어 둔다면 그리 큰 문제는 아닐 것이다.

북마크는 지역적이며 push나 pull을 사용해 전달할 수 없다! Mercurial 1.4에 추가한다는 소문이 있지만 현재는 북마크를 전달하려면 그 정보를 담고 있는 파일을 직접 줘야 한다.

갱신: Mercurial 1.6부터 저장소 사이에 push와 pull로 북마크를 전달할 수 있다.

git과 비교

브랜치를 북마크로 만드는 것은 git에서 브랜치를 다루는 일반적인 방법과 매우 비슷한다. Mercurial 북마크는 커밋하는 체인지셋을 가리키는 이름인 git 참조와 비슷하다.

가장 큰 차이는 git refs는 push와 pull로 전달할 수 있으나 Mercurial 북마크는 그렇지 않다는 것이다.

명명한 브랜치로 브랜치 만들기

세 번째 방법은 Mercurial의 명명한 브랜치를 사용해 브랜치를 만드는 것이다. (나를 포함해) 일부는 이 방법을 좋아하지만 다른 많은 이는 그렇지 않다.

명명한 브랜치를 새로 만들려면 다음처럼 한다.

$ cd ~/src/test-project
$ hg branch feature

커밋할 때 hg branch를 사용해 다른 브랜치에 있다는 것을 표시하지 않으면 새로 만든 체인지셋은 부모와 같은 브랜치에 존재한다.

명명한 브랜치를 사용하면 저장소는 다음과 같다.

Branching with Named Branches

이 방법에서 중요한 차이는 브랜치 이름이 체인지셋 메타 정보의  일부분으로 영원히 남는다는 것이다(위 그림을 보면 체인지셋 4에서 볼 수 있다).

참고: 기본 브랜치는 default이며 상세 출력을 하지 않으면 일반적으로 나타나지 않는다.

이제 지금까지 무시했던, 테두리가 점선인 마법의 레이블을 설정할 때가 됐다. 리비전 대신 사용하는 브랜치 이름은 “해당 브랜치의 최신 체인지셋”을 가리키는 단축 표기이다. 이 예제 저장소에서

  • hg update default를 실행하면 체인지셋 3으로 갱신하며 이는 default 브랜치의 최신이다.
  • hg update feature를 실행하면 체인지셋 4로 갱신하며 이는 feature 브랜치의  최신이다.

이 레이블 중 어느 것도 (북마크와 달리) 실제 디스크에 존재하는 객체가 아니다. 이 레이블을 사용하면 Mercuriall에서 즉시 적절한 리비전으로 계산한다.

장점

명명한 브랜치를 사용하는 가장 큰 장점은 브랜치 이름이 해당 브랜치에 있는 모든 체인지셋의 메타 정보에 있으며 이는 상당히 도움이 된다는 점이다(특히 graphlog를 사용할 때).

단점

많은 이가 브랜치 이름으로 체인지셋 메타 정보를 혼란스럽게 하는 것을 좋아하지 않는다. 특히 바로 병합할 작은 브랜치에서 작업 중이라면 더욱 그렇다.

과거에는 브랜치를 “닫을” 방법이 없어 시간이 흐를수록 브랜치 목록이 커지는 문제도 있었다. 이는 Mercurial 1.2에서 고쳤으며 hg commit을 사용할 때 –close-branch 선택지를 사용한다.

git과 비교

내가 아는 한 Mercurial에 있는 명명한 브랜치와 같은 것은 git에 없다. 브랜치 정보는 git 체인지셋 메타 정보에 절대 저장하지 않는다.

익명으로 브랜치 만들기

마지막 방법은 가장 빠르고 쉬운 것으로 원하는 리비전으로 갱신하고 커밋하는 것이다. 이에 대해 이름을 생각하거나 어떤 다른 일도 할 필요 없이 그저 갱신하고 커밋하면 된다.

특정 리비전으로 갱신하면 Mercurial에서는 작업 디렉터리의 부모를 해당 체인지셋으로 표시해 둔다. 커밋하면 새로 만든 체인지셋의 부모는 작업 디렉터리의 부모가 된다.

갱신하고 커밋만 한 결과는 다음과 같다.

Branching Anonymously이렇게 하면 브랜치 사이는 어떻게 오가야 할까? 그저 리비전 번호 (또는 해시)를 hg update –check REV 명령과 사용(–check는 -c로 줄일 수 있다)하면 된다.

참고: –check 선택지는 Mercurial 1.3에서 추가했으나 문제가 있고 1.3.1에서 고쳤다. 1.3.1 이전 버전을 사용한다면 갱신해야 한다.

hg log와 hg graphlog처럼 로그를 출력하는 명령은 저장소에 있는 모든 체인지셋을 보여주므로 체인지셋을 잃을 문제도 없다.

장점

이는 브랜치를 만드는 가장 빠르고 쉬운 방법이다. 브랜치 이름을 생각하거나 작업을 마쳤을 때 뭔가 닫거나 삭제할 필요도 없다. 그저 갱신하고 커밋하면 된다.

이 방법은 체인지셋 둘 또는 세 개 정도로 재빨리 수정할 때 훌륭하다.

단점

익명 브랜치는 분명히 브랜치에 대해 설명하는 이름이 없다는 것을 뜻하므로 몇 달 후에도 해당 브랜치에서 무엇을 한 건지 기억하려면 커밋 메시지를 잘 적어야 한다.

브랜치 이름을 나타내는 이름이 없다는 것은 브랜치를 오갈 때마다 hg log나 hg graphlog를 사용해 리비전 번호를 찾아야 할 거라는 것을 뜻한다. 브랜치를 바꾸는 일이 많으면 그 가치에 비해 문제가 더 클 수도 있다.

git과 비교

git에서는 이런 식으로 다루는 방법이 실제 없다. 갱신하고 커밋할 수는 있지만 새로 커밋한 내용에 대해 참조를 만들지 않으면 다른 브랜치로 바꾼 후에는 절대 다시 찾을 수 없을 것이다. 한 무더기의 로그 내용에서 찾는 것을 좋아하지 않는다면 말이다.

아, 그리고 희망적인 건 버려지진 않는다는 점이다.

때때로 재빨리 수정해야 하는 브랜치에 대해 이름을 생각하고 싶지 않을 수 있다. git에서는 브랜치로 정말 뭔가 하려면 Mercurial과 달리 반드시 이름을 붙여야 한다.

Mercurial과 git 차이점 한 가지 더

Mercurial과 git 브랜치에서 차이가 한 가지 더 있다.

Mercurial에서는 기본적으로 모든 브랜치를 push 또는 pull 하지만 git에서는 현재 브랜치만 push 또는 pull 한다.

git 사용자이면서 Mercurial로 작업한다면 이는 중요하다. Mercurial에서 단일 브랜치만 push 또는 pull 하려면 –rev 선택지(단축 표기는 -r)를 사용해 해당 브랜치의 최신 리비전을 지정하면 된다.

$ hg push --rev branchname
$ hg push --rev bookmarkname
$ hg push --rev 4

리비전을 지정하면 Mercurial에서는 해당 체인지셋과 그 체인지셋의 조상 중 대상 저장소에 없는 것만을 보낸다.

이는 “복제로 브랜치를 만드는” 방법에는 해당하지 않는다. 해당 브랜치가 별도 저장소이기 때문이다.

결론

이 안내가 도움이 되길 바란다. 내가 놓친 부분이 있으면, 특히 git에 대한 것(써야 할 때 외에는 더이상 git을 사용하지 않는다)이나 질문이 있으면 알려주길 바란다!


[1] IRC 네트워크
[2] 현재 진행 중인 프로젝트에서 벗어나 자신만의 프로젝트를 진행하기 위해 저장소를 복제해 분기하는 것으로 기술적인 개념이 아니라 사회적인 개념이다. 완전히 독립적인 프로젝트를 진행하기도 하고 패치를 만들어 원래 프로젝트에 전달하기도 한다.

You may also like...