· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
서브버전기초매뉴얼

※ 역자 주 : 이 글은 Subversion의 기초라고 생각되는 부분을 Version Control with Subversion For Subversion 1.2 - by Ben Collins-Sussman, Brian 책에서 발췌하여 번역하였습니다.

※ 위키로 옮긴 이 주 : 이 글은 [http]KLDP 포럼의 글을 옮긴 것입니다.


1. 소개


SubversionOpenSource 버전 관리 시스템입니다. 파일의 트리는 중앙 저장소에 위치하는데 이 저장소는 포함하고 있는 파일과 디렉토리들의 모든 변경사항을 기억하고 있다는 걸 제외하면 보통의 파일 서버와 동일합니다. 이는 데이터를 예전으로 돌릴수도 있고 어떻게 변화했는지 히스토리를 확인할수도 있게 합니다. 이것이 많은 이들이버전 관리 시스템을 타임머신이라고 부르는 이유입니다.

Subversion은 여러 컴퓨터에서 네트워크를 통해 접근할 수 있으며 다양한 사람들이 파일을 수정 가능하게 함으로써 협업이 가능케 해줍니다. 모든 작업에 버전이 매겨지기 때문에 만일 잘못된 수정을 가했더라도 쉽게 되돌리기만 하면 됩니다. Subversion은 소스 코드뿐 아니라 어떤 형식의 파일도 관리를 할수 있습니다.

2. Subversion의 역사


2000년 초 CollabNet,Inc 사는 CVS를 대체할만한 소프트웨어를 만들 개발자를 찾기 시작했습니다. CollabNet,Inc 사는 CEE라는 소프트웨어를 만들었는데 CVS는 그중 하나의 컴포넌트였습니다. 그들은 CVS의 한계를 처음부터 잘 알고 있었기에 더 나은 것을 찾으려 했으나 그런 것이 없던 차였습니다. 그래서 CollabNet,Inc 사는 CVS의 기본적인 원리만 유지하고 버그나 잘못된 설계등은 없앤 새로운 버전 관리 시스템을 만들기로 했습니다.

2000년 2월, 그들은 Open Source Development with CVS (Coriolis, 1999)의 저자인 Karl Fogel에게 새로운 프로젝트에 대해 물어보았고 마침 CVS로부터 고생하고 있던 그도 함께 참여하게 되었습니다. 그 후 몇 사람을 더 영입한 뒤 5월에 개발을 시작하였고 여러 사람의 도움과 함께 Subversion은 순식간에 많은 개발자들의 이목을 집중시켰습니다. 이는 많은 사람들이 CVS로부터 어려움을 겪고 있다는 사실이 입증된 셈이 되었고 14개월의 개발끝에 Subversion이 탄생하였습니다. Subversion은 무료로 다운로드가 가능하며, 수정, 재배포가 가능합니다.

3. Subversion의 기능


3.1. Directory Versioning


CVS는 단지 개별 파일의 히스토리만 기록하는 데 반해 Subversion가상 버전 파일 시스템을 구현하여, 디렉토리 트리 전반의 모든 변화를 기록합니다. 그리고 파일과 디렉토리에 함께 버전이 부여됩니다.

3.2. 실제적인 버전 히스토리


CVS는 파일 버전에 제한되어 있기 때문에 복사나 이름 변경과 같은 행위 - 파일이 변경된 것이지만 그것을 포함하는 디렉토리 또한 변경된 것임 - 는 지원하지 않습니다. 게다가 CVS에서는 같은 이름의 파일로 기존의 파일을 대체하지 못합니다. Subversion에서는 파일과 디렉토리 모두 추가,삭제,복사, 이름 변경이 자유롭습니다. 그리고 새로운 파일이 추가될 때마다 해당하는 새로운 히스토리가 시작됩니다.

3.3. 원자적 커밋


개별적인 수정 사항들이 모두 저장소에 적용이 되든지, 아니면 하나도 적용이 안 되는 것을 말합니다. 이것은 개발자들이 수정 사항들을 논리적 단위로 적용하여, 수정 사항의 일부만 적용되어 발생되는 문제를 예방해 줍니다.

3.4. 버전화 된 메타 데이터


각각의 파일과 디렉토리는 키와 값의 속성 집합을 가지고 있어서 원한다면 어떠한 임의의 키와 값도 저장할 수 있습니다. 이 속성 값도 파일과 같이 계속 버전화 됩니다.

3.5. 네트워크 레이어의 선택


Subversion은 새로운 네트워크 메커니즘을 구현하기 쉽도록 하는 추상적 저장소 접근법을 가지고 있습니다. SubversionApache HTTP서버에 확장 모듈로 플러그인 될 수 있기 때문에 견고하며 상호 동작이 매우 쉽습니다.

3.6. 일관된 데이터 핸들링


Subversion은 파일의 차이점을 바이너리 대조 알고리즘을 이용하기 때문에 text와 binary파일 모두에서 동일하게 동작합니다. 두 종류의 파일 모두 저장소에 압축되어 저장되며 차이점은 네트워크를 통해 양 방향으로 전송됩니다.

3.7. 효율적인 브랜칭과 태깅


브랜칭과 태깅의 비용은 프로젝트의 크기에 비례할 필요는 없습니다. Subversion은 브랜치와 태그들을 하드 링크와 비슷한 메커니즘을 이용하여 단순히 프로젝트를 복사하는 방법으로 생성하므로 시간이 매우 절약됩니다.

4. 아키텍쳐


맨 아래쪽은 모든 버전별 데이터를 담고있는 Subversion의 저장소 입니다. 맨 위쪽은 Subversion 클라이언트 프로그램으로서, working copy라고 불리는 버전별 데이터의 일부분을 로컬에서 관리합니다. 이 두 영역 사이에는 다양한 저장소 접근층이 있는데, 어떤 경로는 네트워크를 지나 서버를 거쳐서 저장소에 도달하는 반면, 어떤 것은 네트워크를 지나 직접 저장소에 접근을 합니다.

5. Versioning Model


5.1. 파일 공유의 문제점


해리와 샐리가 저장소에서 문서를 동시에 읽은 후 해리가 먼저 자신의 수정 사항을 저장소에 기록했습니다. 그후 샐리가 실수로 자신의 새 버전 파일을 저장소에 덮어 쓸 수가 있습니다. 차후에 시스템이 파일을 복구하도록 할 수 있겠지만 해리의 변경사항은 샐리의 새 버전 파일에서 반영되어있지 않습니다.

5.2. Lock-Modify-Unlock 모델


많은 버전 관리 시스템이 자신의 파일의 보호를 위해 lock-modify-unlock 모델을 사용합니다. 이 모델에서는 한번에 오직 한사람만이 파일을 수정할 수 있습니다. 해리는 변경 사항을 적용하기 전 반드시 파일을 잠궈야 합니다. 그리고 이제 샐리는 그 파일을 잠글수 없으므로 또한 해당파일을 수정할수 없게 됩니다. 샐리는 오직 파일을 읽을 수만 있으며, 해리가 파일 변경을 끝내고 lock을 풀기만을 기다립니다.

이것은 여러 가지 문제점을 가지고 있는데 첫번째 locking은 관리적 문제를 가져올 수 있습니다. 가끔 해리가 파일을 잠근 후 푸는 것을 잊어버린다면 샐리는 작업을 할 수 없게됩니다. 둘째로, locking은 동시 작업을 불가능하게 할 수 있습니다. 만일 해리가 텍스트 파일의 앞 부분을 수정하고 샐리는 그 파일의 뒷 부분을 수정하고 싶을 경우 locking은 동시 작업이 불가능합니다. 하지만 만약 두 파일이 병합될 수 있다면 두 사람은 각각의 사본으로 작업을 하고 최종적으로 병합하면 될 것입니다. 셋째로 locking은 보안의 의미를 왜곡합니다. 해리가 A 파일을 잠근 후 수정하고 샐리가 B 파일을 잠근후 수정한다고 가정할 때 서로 파일을 수정하는 도중 두파일이 호환되지 않아 동작하지 않을 수도 있습니다.

5.3. Copy-Modify-Merge 모델


Subversion, CVS 그리고 다른 버전 관리 시스템은 locking 대신 copy-modify-merge 모델을 사용합니다. 이 모델에서는 각각의 사용자 클라이언트가 프로젝트 저장소에 연결하여 개인적인 작업 복사본 - 저장소의 파일과 디렉토리와 똑같은 - 을 만들어 작업합니다. 사용자는 병행적으로 자신의 파일을 수정하며 최종적으로 개인적 복사본들이 병합되어 새로운 최종 버전이 생성됩니다.

해리와 샐리가 같은 프로젝트의 저장소로부터 복사본을 만들어 병행적으로 작업을 합니다. 둘은 같은 파일을 수정하였는데 샐리가 먼저 저장소에 자신의 변경사항을 반영합니다. 후에 해리가 저장소에 파일을 쓰려하지만 저장소로부터 최신버전이 아니라는 경고를 받습니다. 다시말해 A 파일은 그가 복사해간 다음 변경이 되었다는 것입니다. 그래서 해리는 저장소로부터 변경 사항을 받아 자신의 파일에 병합을 합니다. 다행히 샐리의 변경 사항이 그의 변경 사항을 덮어 쓰지는 않았습니다. 병합 후 그는 새로은 작업 파일을 저장소에 반영합니다. 하지만 만약 샐리의 변경사항이 해리의 것을 덮어쓸 경우는 어떨까요? 그러한 상황은 충돌(conflict)이라고 하며, 대부분 별 문제없이 해결됩니다. 그럴 때에는 충돌이 발생한 두 파일의 영역을 보고 작업자가 인위적으로 둘중 하나를 선택을 해야합니다. 소프트웨어는 자동으로 충돌을 해결하지 못합니다. 아마도 해리는 샐리와의 얘기한 후에 수정 사항을 적절히 변경하여 덮어 쓸 것입니다.

6. 리비전(Revisions)


모든 Subversion의 commit(적용)은 하나의 원자적 트랜잭션으로 간주되어 파일과 디렉토리의 버전 숫자를 변경합니다. 만일 파일의 내용을 변경하거나, 파일이나 디렉토리의 생성, 삭제, 이름 변경, 복사 등의 작업을 했다면 하나의 변경 셋으로 커밋하게 됩니다. 저장소의 입장에서는 각각의 커밋이 원자적인 트랜잭션으로 간주됩니다. 모든 변경사항이 적용되든지 아니면 아무 것도 적용이 안 되든지 입니다. Subversion은 이 원자성을 고수하는데 프로그램이나 시스템이 망가지는 것을 방지하기 위함입니다.

저장소가 커밋을 받아들일 때마다 새로운 파일 시스템 트리를 생성하는데 이를 리비전(revision)이라고 합니다. 각 리비전에는 유일한 자연수가 할당되며 더 큰 숫자를 가질수록 최근 것입니다. 최초로 할당되는 리비전은 숫자 0이며 저장소엔 빈 부모 디렉토리 외엔 아무 것도 없는 상태입니다.

위 그림은 저장소를 잘 묘사하고 있습니다. 각 리비전에는 파일 시스템트리가 매달려 있고 각 트리는 커밋 후 저장소의 스냅샷이 됩니다. 리비전 번호는 개별적인 파일이 아닌 전체 트리에 적용되는 것으로 리비전 번호가 N이라는 것은 N번째 커밋 후의 저장소의 상태를 나타낸다고 볼 수 있습니다. Subversion 사용자가 foo.c 리비전 5라고 하는 것은 5번째 커밋 후의 foo.c를 말하는 것입니다. 일반적으로 리비전 N과 리비전 M의 파일은 다르지 않을 수 있습니다.

예를 들어 다음 3개의 파일을 체크아웃 받았다고 할 때 이들의 리비전은 4입니다:

  • calc/Makefile: 4
  • integer.c: 4
  • button.c: 4


이때 button.c만을 수정하고 커밋하면 이것은 저장소에 리비전 5를 생성할 것입니다. 이제 나의 작업 복사본은 다음과 같습니다:

  • calc/Makefile: 4
  • integer.c: 4
  • button.c: 5

이제 샐리가 interger.c를 수정하여 커밋하고 리비전 6이 생성됩니다. 그러면 저장소로부터 update를 수행하면 나의 작업 복사본은 다음과 같이 보일 것입니다:
  • calc/Makefile: 6
  • integer.c: 6
  • button.c: 6

나는 button.c를 수정하고 샐리는 integer.c를 수정하였습니다. 반면 Makefile은 리비전 4,5,6에서 모두 동일합니다. 하지만 SubversionMakefile에도 똑같이 리비전 6을 부여합니다.

7. 서로 다른 리비전의 작업 파일


일반적으로 Subversion은 융통성을 많이 고려하였는데 그 중 한 가지는 작업 파일들이 서로 다른 리비전을 가질수 있는 것입니다. 이것은 새로운 사용자에게는 혼란스러울 수도 있는데, 예를 들어 foo.html을 수정하여 커밋하였다고 합시다. 그리고 저장소에 리비전 15를 생성하였을 때 작업 파일이 리비전 15로 바뀐다고 예상할수도 있겠지만 그렇지 않습니다. 클라이언트는 svn update명령없이는 저장소의 상태에 대해서 전혀 알수가 없습니다. 커밋과 업데이트는 서로 다른 성격의 것이므로 svn update를 통해서만 저장소의 최신 리비전을 가져올 수 있는 것입니다.

8. 브랜칭(Branching)


브랜치, 태깅 그리고 머징은 대부분의 버전 관리 시스템에서 개념이 같습니다. 브랜칭은 버전관리의 기초가 되는 파트이며 Subversion으로 데이터를 관리한다면 결국에는 이 기능에 의존하게 됩니다.

당신이 회사에서 어떤 매뉴얼 문서를 관리한다고 할 때 어느날 다른파트에서 그 매뉴얼의 일부분을 그들의 파트에 맞게 수정해달라고 할수 있습니다. 아마도 우리는 그 문서의 복사본을 만들어서 두개의 문서를 개별적으로 관리할 것입니다. 그리고 두 문서의 똑같은 부분을 변경할 일이 종종 있을 것입니다. 두 문서는 거의 똑같지만 특정부분만 조금 다를것입니다. 브랜치(branch)의 기본개념은 이렇습니다. 브랜치란 같은 줄기에서 시작하여 어느 시점부터 다른줄기로 독립되어 나온 것을 말합니다. 브랜치는 언제나 어떤데이터의 복사본으로부터 생성되어 그 자신만의 히스토리를 시작합니다.

Subversion은 파일과 디렉토리의 브랜치들을 병행적으로 관리할수 있는 명령어를 가지고 있으므로 데이터를 복사해서 브랜치를 생성할 수 있으며 어떤 데이터와 관련이 있는지도 기억을 합니다. 또한 한쪽의 변경 사항을 다른쪽에 복사할 수도 있습니다. 결국 자신의 변경 사항 일부를 다른 브랜치에 적용할 수 있으므로 개발된 부분을 혼합하고 매치(mix and match)하는 것을 가능하게 해 줍니다.

9. 브랜치의 사용


매번 커밋을 할 때마다 Subversion은 완전히 새로운 파일 시스템 트리(리비전)를 생성합니다. 샐리가 paint와 calc라는 두 프로젝트를 커밋했다고 할 때, 저장소의 모습은 다음과 같습니다. 각 프로젝트는 trunk와 branches라는 서브 디렉토리를 가지고 있습니다.

해리와 샐리가 calc 프로젝트를 함께 진행한다고 할 때 /calc/trunk를 개발 소스의 기본 줄기라고 하면 둘은 똑같이 /calc/trunk의 작업본을 가지고 있을 것입니다.

이때 해리가 그 프로젝트의 근본적인 재수정을 요청받았다고 합시다. 그것은 수정하는데 매우 긴 시간이 걸릴 것이고 프로젝트의 모든 파일을 고쳐야 할 수도 있습니다. 그리고 해리는 이곳 저곳의 버그를 수정하고 있는 샐리의 작업에 방해하고 싶지도 않습니다. 샐리는 언제나 최신버전인 /calc/trunk의 프로젝트가 사용 가능하다라는 가정 하에 작업을 하고 있으므로 해리가 작업을 하면서 하나 하나에 대해 커밋을 한다면 샐리의 것을 망가뜨릴 것입니다.

한 가지 방법은 이렇습니다. 해리와 샐리가 한 주에서 두 주간 파일 공유를 멈추고 작업본으로 모든 수정을 끝내는 것입니다. 물론 중간에 커밋은 없습니다. 이것은 몇 가지 문제점을 가지고 있는데, 우선 이건 그다지 안전하지 않다는 것입니다. 저장소에 자신의 작업을 자주 반영하는 대부분의 사람들은 자신의 작업본에 실수를 할 우려가 있습니다. 둘째로는 이 방법이 유연하지 않다는 것입니다. 만약 해리가 다른 두대의 컴퓨터에서 작업을 한다면 매번 자리를 옮길 때마다 작업본의 복사본을 가지고 다녀야 할것입니다. 같은 맥락으로 해리의 작업중인 사항을 다른 사람과 공유하기가 어렵게 됩니다. 공통적인 소프트웨어 개발의 가장 좋은 방법은 다른 사람이 자신의 진행 사항을 보게 하는 것입니다. 만약 아무도 그의 수정 사항을 보지 못한다면, 귀중한 피드백을 받을 수 없게 됩니다. 결국 해리가 모든 수정을 마치고 저장소에 반영하려 할 때 기존의 샐리의 데이터와 통합하는 것이 매우 힘들게 됩니다.

가장 좋은 해결책은 저장소에 자신만의 브랜치를 생성하는 것입니다. 이로써 다른 사람의 작업을 방해하지 않고도 나의 불완전한 소스를 저장할 수 있으며 특정인과 정보를 공유할 수도 있습니다.

다음은 /calc/branches/my-calc-branch/calc/trunk의 브랜치를 생성한 예입니다:
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Creating a private branch of /calc/trunk."
Committed revision 341.

Cheap Copies : Subversion 저장소는 특별하게 설계가 되었습니다. 만일 디렉토리를 복사할 때 저장소의 공간이 늘어나는 것을 걱정할 필요가 없습니다. Subversion은 실제로 데이터를 복사하는 것이 아니라 새로운 디렉토리가 기존의 트리를 가리키도록 하는데 이것은 Unix의 하드 링크와 비슷한 개념입니다. Lazy 복사라고도 불리우는데 만약 디렉토리 내의 한 개의 파일만 수정되었다면 실제로는 한 개의 파일만 변경되고 나머지 파일들은 여전히 원본 파일의 링크를 가지고 있게 됩니다. 물론 이 메커니즘은 사용자 입장에서는 단순히 트리가 복사되는 것으로 보이므로 알 수 없습니다.

10. 머징(merging)


머징이란 단어가 혼란스럽게 들리겠지만, 사실은 매우 간단합니다. 더 적절한 이름은 diff-and-apply 이라고 할수 있는데 왜냐하면 그게 동작의 전부이기 때문입니다. 두 저장소 트리를 비교하고 그 차이점을 작업본에 적용하는데 명령어는 다음 세 개의 인자를 받습니다.

  1. 초기 저장소 트리 (주로 비교의 왼쪽편이라 합니다)
  2. 최종 저장소 트리 (주로 비교의 오른편이라 합니다)
  3. 로컬 변경으로서의 차이를 적용할 작업본(주로 머지의 대상이라 합니다.)

일단 이 세 개의 파라미터가 주어지면 두 개의 트리가 비교된 결과가 작업본에 적용됩니다. 명령이 끝난뒤의 모습은 수작업으로 파일을 svn add 또는 svn delete한 것과 똑같습니다. 그 결과가 맘에 들면 svn commit을 하면되고 아니라면 svn revert로 되돌리면 됩니다. 다음은 머지 사용의 예입니다:
$ svn merge http://svn.example.com/repos/branch1@150 \
http://svn.example.com/repos/branch2@212 \
my-working-copy

$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy

$ svn merge -r 100:200 http://svn.example.com/repos/trunk

첫번째 예는 3개의 인자를 모두 명시했는데 각 트리는 URL@REV의 형태입니다. 두번째 예는 동일한 URL에서 서로 다른 리비전을 비교할 때 간략히 쓰는 방법입니다. 마지막 예에서는 마지막 파라미터를 생략하여 디폴트로 현재 디렉토리를 사용하고 있습니다.

시간이 지나면서 트렁크(trunk)와 개인적인 브랜치(branch)에 많은 변경사항이 일어날 것입니다. 이제 해리는 개인 브랜치의 수정을 마치고 메인 트렁크에 변경 사항을 적용하려고 합니다. 이때 svn merge가 어떻게 사용될수 있을까요. svn merge는 두개의 트리를 비교하여 그 차이점을 작업본에 적용합니다. 그러므로 변경사항을 받기위해 일단은 작업본을 파일을 최신으로 유지합니다. 그 다음은 비교할 두 트리를 선택해야 합니다. 많은 이들이 트렁크의 최신 트리를 개인 브랜치의 최신 트리와 비교하면 된다고 생각하는데 이것은 잘못된 생각입니다. 최신의 트렁크와 브랜치의 트리를 비교하는 것은 브랜치의 변경사항을 완전히 설명해주지 못합니다. 이는 너무 많은 변경사항을 포함하고 있는데, 브랜치의 변경사항뿐만이 아니라 트렁크의 삭제된 사항까지도 보여주기 때문입니다. 브랜치의 변경사항만을 표현하려면 브랜치의 초기상태와 최종상태를 비교해야 합니다. 초기리비전은 svn log를 이용하면 알 수 있고 최종 리비전은 HEAD라는 리비전을 사용하면 됩니다. 다음은 머징의 예입니다:
$ cd calc/trunk
$ svn update
At revision 405.
현재 최신 리비전은 405임을 보여줍니다.

$ svn merge -r 341:405 http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
브랜치의 r341과 r405의 차이를 작업본에 적용합니다. 세 개의 파일이 업데이트 되었습니다.

$ svn status
M integer.c
M button.c
M Makefile
상태를 확인해보니 세개의 파일이 수정된 상태입니다.

$ svn commit -m "Merged my-calc-branch changes r341:405 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 406.
커밋을 하니 r406로 저장되었습니다.

머징을 한뒤 버그픽스와 기능 개선을 위해서 한주간 더 브랜치에서 작업을 하게 되었다고 가정을 하겠습니다. 저장소의 헤드는 리비전480일 때 예전의 수정사항이 중복되지 않고 오직 새로운 변경사항만 머징하도록 하고 싶다면 다음과 같이 작업해야 합니다. svn log로 마지막 머징했던 때의 리비전을 확인해봅니다.
$ cd calc/trunk
$ svn log
…
------------------------------------------------------------------------
r406 | user | 2004-02-08 11:17:26 -0600 (Sun, 08 Feb 2004) | 1 line
Merged my-calc-branch changes r341:405 into the trunk.
------------------------------------------------------------------------
…
마지막 머징 시점의 리비전이 406이므로 처음부터 405까지는 머징이 된것입니다. 그러므로 리비전406부터 HEAD까지만 머징하면 됩니다.

$ svn update
At revision 480.

$ svn merge -r 406:480 http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
r406부터 r408까지 머징합니다.

$ svn commit -m "Merged my-calc-branch changes r406:480 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 481.
커밋을 하니 저장소의 리비전이 481로 변경되었습니다.

머징은 되돌리기 기능으로도 사용할수 있는데, 다음은 r303을 r302버전으로 되돌리기 하는 것을 보여주고 있습니다:
$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk
U integer.c
$ svn status
M integer.c

$ svn commit -m "Undoing change committed in r303."
Sending integer.c
Transmitting file data .
Committed revision 350.

11. 개발자의 브랜치 사용패턴


대부분의 소프트웨어는 전형적인 라이프사이클 - 코딩, 테스트, 릴리즈, 반복 - 이 있는데 여기에는 두 가지 문제점이 있습니다. 우선, 개발자는 품질 인증팀이 릴리즈된 제품을 검사할 동안 기다려야만 합니다. 둘째 대부분의 팀은 구 버전과 릴리즈된 버전을 함께 서포트해야 합니다. 만일 최근에 버그가 발견되었다면 릴리즈된 제품에도 버그가 존재할것인데 사용자는 새로운 릴리즈를 기다리지 않고 즉시 버그가 수정되기를 바랄것입니다. 이때 버전관리가 필요한데 전형적인 프로세스는 다음과 같습니다:

  1. 개발자는 트렁크에 모든 새 작업을 올려놓는다. 매일 매일 수정사항이 트렁크에 커밋된다. 새 기능, 버그픽스 등등······.
  2. 트렁크를 릴리즈 브랜치로 카피한다. 개발팀이 생각하기에 제품을 릴리즈할 준비가 되었다고 생각할 때 트렁크를 브랜치로 카피한다. 예를 들어 /branches/1.0가 될것이다.
  3. 팀은 이와 병행해서 작업을 계속한다. 한 팀이 릴리즈 브랜치의 테스트를 할 때 , 다른 팀은 트렁크에서 계속 작업을 할 수 있다. 어느 쪽에서든 버그가 발견되면 버그픽스가 왔다갔다 할것이다. 하지만 어느 시점이 되면, 그런 프로세스는 멈추고 브랜치는 릴리즈 바로 직전의 최후 테스트 버전으로 고정될 것이다.
  4. 브랜치는 태그되고(tagged) 릴리즈된다. 테스트가 끝나면 /branches/1.0은 레퍼런스 스냅샷인 /tags/1.0.0으로 카피된다. 이 태그는 패키지화 되어 고객에게 릴리즈된다.
  5. 브랜치는 항상 유지 보수된다. 트렁크에서는 2.0버전의 작업이 계속된다해도, 버그픽스는 트렁크에서 /branches/1.0으로 계속 포팅된다. 일정량의 버그픽스가 쌓이면, 버전 1.0.1을 릴리즈하기로 결정한다. /branches/1.0/tags/1.0.1로 복사되고 태그는 또다시 패키지화 되어 고객에게 릴리즈된다.

이 모든 과정이 소프트웨어 개발에서 반복되는데, 2.0버전의 개발이 완료되면, 새로운 2.0버전 릴리즈 브랜치가 만들어지고, 테스트되고, 태그된 후 릴리즈 됩니다. 몇 년 후 저장소는 유지 보수되는 릴리즈 브랜치 여러 개와 최종 버전인 태그 여러 개가 남아있을 것입니다.

12. 피쳐 브랜치 (feature branch)


피쳐 브랜치는 트렁크의 작업을 방해하지 않으면서 복잡한 수정 작업을 하도록 만든 임시 브랜치를 말합니다. 영구적으로 유지 보수되는 릴리즈 브랜치와는 달리 피쳐 브랜치는 생성후 잠시 사용되다가 트렁크로 머지되고 영구적으로 삭제됩니다. 어떤 프로젝트는 피쳐브랜치를 전혀 사용하지 않을 수도 있으며 어떤 프로젝트는 브랜치를 극단적으로 많이 사용합니다. 그래서 어떤 조그마한 변경 사항도 트렁크로 직접 커밋되는 경우가 없고 피쳐브랜티에 적용된후 리뷰를 거쳐 트렁크로 머지됩니다. 그리고 피쳐브랜치는 삭제되는데 이 경우 트렁크의 데이터가 언제나 안정적이며 사용가능하다는 장점이 있습니다. 피쳐 브랜치에서 작업을 계속하다 몇 주 후 트렁크와 싱크를 맞추려면 부담이 매우 크므로 정책을 정해서 일주일에 한번 지난 주 트렁크의 변경사항을 브랜치로 정기적으로 업데이트 하는 것이 필요합니다. 이때 로그 메시지에 마지막 머징한 기록을 잘 유지하여 중복 머징하지 않도록 합니다. 중복 머징은 피해를 주는 것은 아니지만 머징 효율을 떨어뜨리기 때문입니다. 어느 시점이 되면 싱크가 맞추어진 피쳐 브랜치를 트렁크에 머지해야할 상황이 올 겁니다. 마지막으로 최신 트렁크의 데이터로 브랜치를 업데이트하면 이제 브랜치와 트렁크는 내가 브랜치에 가한 변경 사항을 제외하고는 모두 동일하게 맞추어질 것입니다. 이렇게 한 후 브랜치와 트렁크를 비교하여 머지를 수행합니다. 다음은 예입니다:
$ cd trunk-working-copy
$ svn update
At revision 1910.
$ svn merge http://svn.example.com/repos/calc/trunk@1910 \
http://svn.example.com/repos/calc/branches/mybranch@1910
U real.c
U integer.c
A newdirectory
A newdirectory/newfile
…
HEAD 리비전을 비교한 뒤 오직 나의 변경 사항만이 커밋됩니다. 이 때는 업데이트한 뒤이므로 둘다 트렁크의 변경 사항을 가지고 있습니다.

13. 작업본 스위칭(switching a working copy)


svn switch 명령어는 현재의 작업본을 다른브랜치로 바꾸어 줍니다.

다음은 예입니다.

$ cd calc
작업디렉토리로 들어갑니다.

$ svn info | grep URL
URL: http://svn.example.com/repos/calc/trunk
저장소의 URL을 확인해봅니다.

$ svn switch http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
Updated to revision 341.
Switch를 이용해 저장소의 위치를 변경합니다.

$ svn info | grep URL
URL: http://svn.example.com/repos/calc/branches/my-calc-branch

저장소의 위치가 바뀐 것을 확인할수 있습니다.

브랜치로 스위칭한 것은 처음에 저장소로부터 체크아웃한것과 완전히 동일합니다. 스위칭도 –r을 이용해 특정 리비전으로 스위칭할수 있습니다.

대부분의 프로젝트는 이 예제보다 훨씬 복잡하기 때문에 서브 디렉토리가 많이 있기 때문에 서브버전을 사용하는 개발자는 다음과 같은 개발 알고리즘을 따르고 있습니다.

1. 트렁크를 통째로 브랜치로 복사한다.

2. 트렁크에서 내가 작업할 부분만 브랜치쪽으로 스위칭해 개발한다.

즉, 어떤 사용자가 브랜치작업시 특정 서브디렉토리만 사용할것이라면 svn switch 명령으로 특정 서브디렉토리만 스위칭할수 있음을 뜻합니다. 심지어는 오직 하나의 파일만 브랜치로 스위칭할수도 있습니다. 이와 같은 모습은 작업본이 혼재된 리비전을 가질 수 있을뿐 아니라 저장소의 위치또한 혼재하여 존재할수 있음을 뜻합니다.

만일 작업본에 각각 다른 저장소로 스위칭된 서브 디렉토리가 여럿 존재한다면, 업데이트할때 디렉토리별로 각각의 적합한 서브트리로부터 패치를 받을 것입니다. 뿐만 아니라 커밋을 할때 로컬파일은 저장소에 원자적인 하나의 리비전을 생성할 것입니다. 주의할점은 작업본이 각각 여러 저장소의 위치를 바라보고 있다고 해도 그 위치들은 모두 동일한 저장소 안에 있어야 한다는 것입니다. 서브버전은 아직 저장소간의 통신이 허용되지 않습니다.

14. 스위칭과 업데이트


svn switch와 svn update가 비슷해보인다는 생각을 해보았습니까? 사실 switch는 update 명령의 상위개념입니다. Svn update를 할 때, 트리를 비교하여 그 차이점을 클라이언트에게 보냅니다. Switch와 update의 한가지 차이점은 update는 언제나 동일한 경로의 것을 비교한다는 것입니다.

즉, /calc/trunk의 작업본은 svn update를 통해 자동적으로 /calc/trunk의 최신 리비전과 비교를 수행합니다. 하지만 switch를 사용한다면 /calc/trunk의 것을 어떤 브랜치의 경로의 최신리비전과 비교를 수행하게 됩니다.

다시말해 update는 작업본을 시간상으로 이동할수 있는데 반해 switch명령은 시공간상을 이동할수 있습니다.


Siwtch와 update명령어는 모두 저장소에서 새로운 데이터를 받은후에도 로컬파일의 수정사항은 보존된다는 특징이 있습니다. 이를 이용하면 작업을 상당히 편하게 할수 있는데 한 예로 /calc/trunk의 작업본으로 작업을 하다가 분득 브랜치에 변경사항을 적용하고 싶다면 해당 브랜치로 스위칭을 합니다. 로컬파일의 변경사항은 그대로 이기 때문에 테스트해보고 마음에 들면 브랜치로 커밋합니다.

15. 태그(Tags)


또다른 서브버전의 개념에는 태그(Tag)가 있습니다. 태그는 단지 어느시기의 프로젝트의 스냅샷을 의미합니다. 그리고 서브버전에서는 각 리비전이 바로 매번커밋후 파일시스템의 스냅샷입니다. 하지만 사람들은 태그에 release-1.0과 같은 좀더 직관적인 이름을 붙이고 싶어하며 더 작은 서브디렉토리의 스냅샷을 만들고 싶어합니다. 그리고 또한 릴리즈1.0이 리비전4822 이라는 것을 기억하기도 쉽지 않습니다.

16. 간단한 태그 만들기


다음은 /calc/trunk의 스냅샷을 만드는 예입니다.
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/tags/release-1.0 \
-m "Tagging the 1.0 release of the 'calc' project."

Committed revision 351.

/calc/trunk를 /calc/tags/release-1.0 으로 복사하였습니다. release-1.0디렉토리는 복사할 당시 트렁크의 HEAD리비전을 나타내는 스냅샷으로 계속 남아있을 것입니다. 복사시 더욱 정확히하려면 r 350 이라는 옵션을 줌으로써 리비전 350이 릴리즈1.0이라는 것을 명시할 수 있습니다.

이 과정은 브랜치를 생성하던 과정과 동일한데 서브버전입장에서는 태그와 브랜치의 차이가 없으며 둘 다 그저 복사를 통해 생성된 평범한 디렉토리일 뿐입니다. 그리고 tags라는 디렉토리로 복사를 한 이유는 단지 사람이 그렇게 하기로 결정했기 때문입니다. 누군가 태그로 커밋을 하지 않는다면 스냅샷으로 남아있겠지만 누군가 커밋을 하는 순간 태그는 의미상 브랜치가 되버립니다.

17. 복잡한 태그 만들기


큰 프로젝트에는 많은 서브디렉토리와 더욱 많은 파일들이 있기 마련입니다. 만일 다양한 기능과 버그픽스된 작업본을 가지고 싶다면 특정 리비전으로 udpate하거나 파일이나 디렉토리를 특정 브랜치로 switch함으로써 혼재된 리비전의 로컬파일들을 구성할 수 있습니다. 이 작업본의 스냡샷을 만들고 싶다면 다음과 같이 합니다.

$ ls

my-working-copy/

$ svn copy my-working-copy http://svn.example.com/repos/calc/tags/mytag

Committed revision 352.

copy명령어는 다양한 기능을 가지고 있는데 여기서는 copy를 통하여 파일을 upload하고 있습니다. 다른사람은 이 파일을 체크아웃하거나 svn merge를 통하여 변경사항만 받아갈수도 있을 것입니다.

18. 저장소 레이아웃


저장소를 구성하는 표준적이고 추천할만한 방법이 몇 개 있습니다. 대부분의 사람들은 개발의 중심이 되는 줄기에 trunk를 사용하며, 브랜치 복사본에 branches를, 그리고 태그 복사본에 tags를 사용합니다. 저장소가 하나의 프로젝트만 관리한다면 저장소의 모습은 다음과 같습니다.

/trunk
/branches
/tags

하지만 저장소가 여러 개의 프로젝트를 관리한다면 레이아웃은 다음과 같을 것입니다.

/paint/trunk
/paint/branches
/paint/tags

/calc/trunk
/calc/branches
/calc/tags


물론 일반적인 레이아웃을 무시하고 상황에 맞는 다른 방법을 사용하는 것도 가능합니다. svn move명령을 통해 branches와 tags의 위치를 이동할수도 있습니다.

하지만 공유하고 있는 디렉토리가 이동하면 다른 사람이 update명령을 실행할 때 경로가 더 이상 존재하지 않는다는 에러가 발생하기 때문에 새로운 경로로 switch해야 합니다.

19. 데이터의 일생


예를들어 Calc 라는 프로젝트의 브랜치에서의 작업을 마쳤다면 트렁크로 커밋을 할것입니다. 그리고 브랜치의 개인적인 디렉토리는 더 이상 필요가 없으므로 삭제를 해도 됩니다.

$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Removing obsolete branch of calc project."

Committed revision 375.


이제 개인적인 브랜치는 사라졌지만 필요하다면 언제든지 다시 살려낼수가 있습니다. 서브버전에서 데이터를 다시 살려내는 것은 아주 간단합니다.
$ svn copy -r 374 http://svn.example.com/repos/calc/branches/my-calc-branch \
http://svn.example.com/repos/calc/branches/my-calc-branch

Committed revision 376.

삭제전의 HEAD리비전을 명시하고 copy를 하면 다시 살아납니다.

개인적인 브랜치는 수명이 매우 짧아서 나의 작업이 끝나면 나의 브랜치도 끝납니다. 하지만 소프트웨어 개발에서는 중심이 되는 브랜치를 동시에 2개를 운영하면서 꽤 긴 주기로 나란히 개발하는 것 또한 흔히 있는 일입니다. 예를 들어 calc프로젝트의 안정버전을 릴리즈할 시기가 되었다고 할 때 버그를 잡는데 몇 개월이 걸릴것이라는 것을 알고 있다고 합시다. 다른 사람들은 새로운 기능을 추가하겠지만 정작 개발을 그만두고 기다리라고는 할수 없는 상황일 것입니다. 이때 stable이라는 브랜치를 만들고 버그를 수정할수 있습니다.

$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/stable-1.0 \
-m "Creating stable branch of calc project."

Committed revision 377.


개발자들은 /calc/trunk에서 새로운 최신기술을 계속 적용할수 있으며, 동시에 버그픽스는 stable 브랜치에서 이루어지게 됩니다. 그리고 트렁크에서 수정된 버그픽스를 선택적으로 stable브랜치에 적용하기도 합니다. 이경우는 릴리즈전까지 꽤 긴 시간 stable 브랜치가 존재하게 됩니다.

마지막으로 서브버전의 브랜치와 태그는 매우 가벼운 작업이므로 자유롭게 사용해도 됩니다.

ID
Password
Join
Be careful how you get yourself involved with persons or situations that can't bear inspection.


sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2008-03-31 17:55:36
Processing time 0.0119 sec