1Chapter. 브랜치(branch)와 merge
1.1.브랜치(branch), 태그(tag), 머지(merge)는 대부분의 버전 콘트롤 시스템에서 공통적으로 사용하는 개념입니다. 만약 이런 개념들에 친숙하지 않다면, 이 장은 좋은 소개글이 될것입니다. 만약 이미 익숙한 내용이라면, 이 장을 통해 Subversion이 이런 개념들을 구현하고 있는지 알려주는 흥미로운 장이 되길 바랍니다. 브랜치(branch)는 버전 관리의 가장 기본이 되는 동작입니다. 데이터를 Subversion으로 사용한다는 것은 곧 이 기능에 의존하게 된다는 것을 의미합니다. 이 장에서는 당신이 Subversion의 기본 개념을 벌써 이해하고 있음을 전제로 합니다(>). 1.1. 브랜치(branch)란?당신이 어떤 기업의 한 부서에서 문서(무언가에 대한 핸드북)의 관리를 맡고 있다고 생각해 봅시다. 어느 날 다른 부서로부터 당신이 관리하는 핸드북에서 어떤 부분이 조금만 바뀐 핸드북을 갖고 싶다는 요청을 했다고 합시다. 이때 당신은 어떻게 할까요? 대답은 뻔합니다. 문서의 복사본을 만들어 두 개의 복사본을 따로 관리할 것입니다. 각 부서에서 수정을 요구하면 각 부서에 해당하는 복사본을 수정하면 됩니다. 종종 양쪽 복사본 모두에서 똑같은 변경을 해야 할 경우도 있을 것입니다. 예를 들어 최초의 문서에 오타가 있을 경우 다른 복사본에도 분명 같은 실수가 있을 것입니다. 두 개의 문서는 거의 같으며 약간의 차이만 있을 뿐입니다. 이것이 브랜치(branch)의 기본 개념입니다. 즉 하나의 개발 흐름은 다른 흐름과 독립적으로 존재하지만 과거로 거슬러 올라가면 같은 조상을 공유하고 있는 것입니다. 브랜치(branch)는 항상 무엇인가로부터 복사되어 만들어진 뒤, 점점 원본과 멀어지면서 자신만의 역사를 만들어갑니다. Subversion은 파일이나 디렉토리의 평행한(??)Parallel한 브랜치(branch)를 관리할 수 있습니다. 데이터를 카피해 새 브랜치(branch)를 만들거나 두 개의 버전이 어떤 관계를 가지고 있는지 기억할 수 있습니다. 어느 브랜치에 대한 수정을 다른 브랜치에도 적용할 수 있습니다. 마지막으로, 당신이 작업하고 있는 복사본의 일부분에다가 다른 브랜치들의 내용을 반영시킬 수도 있습니다. 즉, 당신은 매일매일 여러 개의 브랜치들을 서로 "혼합하고 붙일(mix and match)" 수 있습니다. 1.2. 브랜치(branch)의 이용이 시점에서 당신은 각 커밋이 어떻게 저장소에서 완전히 새로운 파일시스템 트리("리비전(revision)"이라고 불리는)를 만드는지 알고 있어야 합니다. 혹시 모른다면, 뒤로 돌아가 리비전에 관한 >를 읽어 주세요. 이 장에서도 2장의 예를 사용합니다. 당신과 동료 Sally가 paint와 calc. 라는 두 개의 프로젝트가 있는 저장소(repository)를 공유하고 있었던 것을 생각해봅시다. 그런데 이번에 누군가가 trunk와 branches, 이 두 개의 새로운 상부 디렉토리를 저장소(repository)에 추가했습니다. 이제 프로젝트는 이후에 설명할 trunk의 하위 디렉토리가 됩니다. 이전과 같이 당신과 Sally 는 각각/trunk/calc 프로젝트 작업의 복사본을 가지고 있다고 합니다. 당신이 프로젝트의 재편성을 맡았다고 가정합시다. 그 작업에는 긴 시간이 필요하고 프로젝트의 모든파일에 영향을 줍니다. 문제는 당신이 Sally에 간섭하고 싶지 않다는 것입니다. 그녀는 아직 여기저기에 있는 작은 버그를 잡고 있는 중이고, 이 작업을 하기 위해서 샐리는 프로젝트의 최근 버전을 언제든지 사용할 수 있어야 합니다. 만약 당신이 자신의 변경을 조금씩 커밋하면 Sally의 작업은 확실히 중단되고 말 것입니다. 이런 문제들을 해결할 수 있는 방법이 있습니다. 당신과 Sally는 1, 2주간 정보를 공유하는 것을 그만둡니다. 즉, 자신의 작업 복사에서 모든파일에 대한 큰 작업을 시작하지만 그것이 완료할 때까지 커밋도 갱신도 하지 않는 방법입니다. 그러나 이것에는 여러가지 문제가 있습니다. 우선 안전하지 않습니다. 대부분의 사람은 작업 카피에 사고가 생길 것에 대비해서 틈틈이 저장소(repository)에 자신의 작업을 보존하는 것을 좋아합니다. 다음으로 이 전략은 유연성이 없습니다. 만약 여러분이 여러 대의 컴퓨터로 일을 진행하고 있다면 (아마 두 대 정도의 컴퓨터에 /calc/trunk 의 작업 복사가 있겠지요) 자신의 변경을 번거롭게 왔다갔다 하며 복사하면서 작업하거나 한 대의 컴퓨터에서만 작업을 해야만 합니다. 마지막으로 당신의 작업이 완료되었을 때 자신의 변경을 커밋하는것이 몹시 어려운 것이라는 것을 알 수 있을 겁니다. Sally (또는 다른 멤버) 는 저장소(repository)에 각각 많은 다른 변경을 하고 있어 그것을 당신의 작업 카피에 merge하는 것은 큰 일이겠지요 작업을 단숨에 끝내야 한다면 더욱 큰 문제가 됩니다. 좀 더 좋은 방식은 저장소(repository)에 자기만의 별도의 브랜치(branch), 즉 자기만의 작업라인을 만드는 것입니다. 이것은 다른 사람에게 간섭 받지 않고, 자신의 어중간한 작업을 가끔 보존할 수 있도록 합니다. 그러면서도 일부 정보에 대해서는 동료와 공유할 수 있습니다. 뒤에서 구체적으로 어떻게하는지를 설명 합니다. 1.2.1. 브랜치(branch)의 작성브랜치(branch)의 작성은 매우 간단합니다 svn copy 커멘드를 이용해서 저장소(repository)에 있는 프로젝트를 복사하기만 하면 됩니다. Subversion는 하나의 파일만 복사할 수 있는 것이 아니라, 디렉토리 전체를 카피할 수도 있습니다. 당신이 /calc/trunk 디렉토리의 카피를 갖고 싶다고 가정 합니다. 새로운 카피는 어디에 두면 좋을까요? 어디에 두어도 상관이 없습니다. 어디에 두는지는 전적으로 프로젝트 팀의 정책에 따릅니다. 우리팀의 정책은 저장소(repository)의/calc/branches 구역에 브랜치(branch)를 모아두는 것이고, 새로 만들 브랜치(branch) 이름은 "my-calc-branch" 로 합시다. /calc/trunk을 복사해서 /calc/branches/my-calc-branch라고 하는 새로운 디렉토리 를 만들 것입니다. 카피를 만드는 데에는 두 가지 방법이 있습니다. 개념을 확실히 하기 위해 복잡한 방법을 먼저 설명하겠습니다. 먼저 저장소(repository)의 루트 (/calc)를 작업 카피에 체크아웃 합니다:
그리고는, svn copy커멘드에 작업 카피 패스를 둘 건네주는 것만으로 카피를 만들 수 있습니다:
이 경우,svn copy 커멘드는 재귀적으로 trunk작업 디렉토리의 내용을 새로운 작업 디렉토리branches/my-calc-branch 에 카피합니다. svn status 커멘드로 확인할 수 있습니다만, 복사된 디렉토리는 저장소(repository)에 새로 더해질 것이라고 예약됩니다. 단, A의 뒤에, +싸인이 표시되는데 주의해주세요. 이것은, 추가 예약이 완전히 새로운 것이 아니라, 무엇인가의 카피 인 것을 나타내고 있습니다. 변경을 커밋하면 Subversion은 네트워크 넘어로 작업 카피 데이터의 전체를 보내는 것이 것이 아니라, /calc/trunk를 카피하는 것으로 저장소(repository)에 /branches/calc/my-calc-branch 를 만듭니다:
그런데, 브랜치(branch)를 만드는 좀 더 간단한 방법이 있습니다. 먼저 설명해야 마땅했습니다 하지만: svn copy 는 인수에 URL를 두 개 취하는 것이 할 수 있다고 하는 것입니다.
이 두 개의 방법에는 아무 차이도 없습니다. 양쪽 모두 새로운 리비전 341의 디렉토리를 만들어, 새롭다 디렉토리는/trunk/calc의 카피에 됩니다. 다만 두번째의 방법은동시에 커밋도 발행합니다. [1] 두번째 방법(URL간 카피)은 즉시 커밋이 일어납니다. 이 방법은 저장소에 있는 많은 파일들을 체크 아웃 해오지 않기 때문에 보다 편리한 방법입니다. 실제로, 이 방법은 실제로는 카피가 전혀 일어나지 않습니다. 1.2.2. 자신용의 브랜치(branch)에서의 작업이것으로 프로젝트에 새로운 브랜치(branch)를 만들 수가 있었으므로, 새로운 작업 카피를 체크아웃 할 수 있습니다:
There's nothing special about this working copy; it simply mirrors a different directory in the repository. When you commit changes, however, Sally won't ever see them when she updates. Her working copy is of /calc/trunk. (Be sure to read the section called “Switching a Working Copy” later in this chapter: the svn switch command is an alternate way of creating a working copy of a branch.) 이 작업 카피(working copy)에는 굳이 특별한 것은 없습니다. 단지 해당 저장소(repository)의 다른 디렉토리로의 미러링일뿐입니다. 당신이 변경을 커밋할때에도 Sally가 update과는 상관없습니다. (When you commit, She won't ever see them when she updates.) 그녀의 작업 카피는 /calc/trunk 입니다. (이번 chapter이후에 [작업 카피의 변환] 섹션을 꼭 읽으세요 : svn switch 명령어가 브랜치의 작업카피를 새로 만드는 것에 대한 것입니다. 일주일간이 경과하는 동안에, 이하의 커밋이 일어났다고 합시다:
이것으로,integer.c에 두 개의 독립했다 개발 라인이 생겼습니다: integer.c의 카피에 일어난 변경 히스토리를 보면(자) 재미있는 것을 압니다:
Subversion은 카피된 시점까지의integer.c 의 히스토리를 더듬고 있는 것에 주의해 주세요. (브랜치(branch)는 리비전 341 그리고 만들어진 것을 생각해 내 주세요). 이번은 Sally가 자신의 파일 의 카피에 대해서 같은 커멘드를 실행했을 때의 모습을 보겠습니다:
Sally는 자신의 리비전 344의 변경을 볼 수가 있습니다만, 당신이 리비전 343에 한 변경은 볼 수가 없습니다. Subversion에서는, 이 두 커밋은 다른 저장소(repository)의 장소에 있는 다른 파일에 대해 일어납니다. 그러나, Subversion은, 두 개의 파일이 공통의 히스토리를 가지고 있는 것을나타내도 있습니다. 리비전 341 그리고 일어난 브랜치(branch) 카피 전은 양자는 같은 파일을 사용하고 있었습니다. Sally와 당신이 어느쪽이나 리비전 303으로 98을 볼 수가 있는 것은 기타째입니다. 1.2.3. 이 이야기의 교훈이 마디에서의 중요 사항은 둘입니다.
1.3. 브랜치(Branch)간 변경의 복사현재 당신과 Sally는 프로젝트에서 뻗어 나온 두 브랜치에서 각각 작업을 진행하고 있습니다. 당신은 자신만의(private) 브랜치에서, Sally는 메인 작업 공간인 trunk에서 작업중입니다. 여러명의 작업자가 있는 프로젝트에서는 대부분의 작업자가 trunk의 작업용 카피를 가지고 있는것이 보통입니다. 누군가가 trunk에 큰 변화를 가져올 만한 작업을 오랫동안 해야 한다면, 보통은 자신만의 브랜치를 만들고 작업이 끝나면 수정사항을 commit합니다. 그래서, Sally가 다른 사람과 필요없는 간섭을 하지 않을 수 있다는 장점이 있는 반면 서로 너무 동떨어지게 작업이 이루어 질 수도 있는 단점도 있습니다. 혼자 작업장에 틀여 박혀 진행하는 방법론의 문제는 당신이 당신 자신만의 브랜치에서 작업을 끝마치고, 주 작업 공간인 trunk에 병합(merge)하려고 할때 대규모의 충돌(conflicts)이 불가피 하다는 것입니다. 대신에 작업을 진행하면서 당신과 Sally가 변경을 계속 공유하는 방법을 사용 할 수도 있습니다. 어떤 변경을 공유할지는 당신이 결정할 문제입니다. 서브버전(Subversion)은 브랜치 간의 차이점 중 일부분만 카피(selectively copy)하는 기능이 있습니다. 그리고, 당신이 당신의 브랜치에서 모든 작업을 끝마쳤을땐, 당신의 브랜치에서 변경된 모든 것을 trunk에 다시 카피하면 됩니다. 1.3.1. 특정 변경만 카피하기이전 섹션에서, 당신과 Sally는 서로 다른 브랜치의 interger.c에 변경을 가했습니다. 만약 당신이 Sally가 수정한 리비전 334의 메세지를 통해 Sally가 몇몇 철자 오류를 수정한 것을 알았다고 합시다. 그럼 의심할 여지도 없이 당신의 카피에도 같은 철자 오류가 있을 겁니다. 당신은 곧 같은 철자 오류를 찾아 내어 고칠 것이고, 그 수정은 언젠가 당신의 브랜치를 머지할 때 충돌(conflict)을 일으킬 수 있습니다. 차라리 당신이 같은 곳에 많은 작업을 하기 전에 Sally가 수정한 것을 당신의 브랜치에 적용시키는 것이 더 좋을 것입니다. 이제 svn merge 명령을 사용할 때입니다. 이 명령은 svn diff와 사촌 지간이라고 할 수도 있습니다. (diff 명령은 3장에 설명되어 있습니다) 두 명령어 모두 저장소(repository)에 있는 어떤 두 오브젝트를 비교하여 그 차이점을 알려줍니다. 예로, 당신은 svn diff 명령을 써서 샐리가 리비전 334에서 무엇을 변화 시켰는지 정확히 알아낼 수 있습니다.
svn merge도 거의 같은 역활을 합니다. 당신의 터미널에 차이점을 뿌려주는 대신에, merge는 차이점을 당신의 작업 카피에 로컬 변경으로 직접 적용 시킵니다.
위의 svn merge의 출력 결과가 의미하는 것은 당신의 카피본인 interger.c가 패치 되었다는 것입니다. 이제 이 파일은 Sally가 수정한 것을 포함합니다. 다르게 얘기하면 수정 사항이 trunk에서 당신의 작업 카피인 개인용 브랜치로 카피 되었다는 것입니다. 이 시점에서 당신의 로컬 변경을 체크해서 제대로 돌아가는지 확인하는 작업이 더 필요합니다. 좀 일이 안풀리는 시나리오를 가정해 봅시다. interger.c를 merge시켰을대 충돌이 일어났습니다. 정식대로 이 충돌을 해결할 수도 있고 (3장을 참고하세요), 당신이 방금한 병합(merge)이 별로 좋지 않았다고 생각한다면 svn revert명령을 사용하여 로컬 변경을 되돌릴 수도 있습니다. 하지만, 당신이 병합된 파일을 확인하고, svn commit으로 변경을 적용시키는 일반적인 경우를 봅시다. 이 때는 변경이 당신의 저장소에 있는 브랜치에 병합됩니다. 버젼 컨트롤에서 쓰는 용어로, 이러한 브랜치 간의 카피를 하는 경우를 변경의 이식(porting changes)라고 합니다. 당신이 로컬 변경을 commit하였을때 로그 메세지로 어떤 브랜치의 리버젼으로 부터 어떤 변경을 이식하였는지 명시해야만 합니다. 예로
이것은 다음 장에서 곧 배울 중요한 "최고의 습관"입니다.
주의: svn diff와 svn merge는 개념적으로 매우 비슷하지만, 구문적으로는 많은 틀린 점을 가지고 있습니다. 자세한 내용은 9장이나 svn help를 참조하시기 바랍니다. 예를 들어svn merge는 타깃 인자로 작업본의 경로를 필요로 합니다. 예로 트리의 변경을 적용하는 장소 같은 곳 입니다. 타깃을 지정해 주지 않으면, 당신이 다음과 같은 일반적인 작업을 실행하려고 한다고 생각합니다.
타깃 경로를 지정하지 않고 디렉터리를 병합(merge)하려 하는 경우, svn merge는 현재 상황을 첫 번째 경우라 생각하고 당신의 현재 작업 디렉터리에 변경을 적용시키려고 합니다. 만약 당신이 특정 파일을 병합하려 하고, 그 파일과 같은 이름의 파일이 당신의 작업 디렉터리에 존재한다면, svn merge는 두 번째 경우라 생각하고 같은 이름의 로컬 파일에 변경을 적용시키려 합니다 만약 당신이 다른 곳에 변경을 적용시키려 할 경우에는, 그것을 명시해야 합니다. 예로, 당신의 작업공간 상위 디렉터리에 있을 경우, 변경을 적용할 타깃 디렉터리를 설정해 줘야 합니다.
1.3.2. 병합할 때 좋은 습관들1.3.2.1. 병합한 내용을 수동으로 기록하라차이점을 병합하는 것은 단순하게 들리지만, 실제로 해보면 굉장히 머리 아픈 작업입니다. 한 브랜치(branch)에서 다른 브랜치로 계속해서 병합을 하다 보면 실수로 같은 변경을 두 번 적용할 수도 있습니다. 어떤 경우에는 이런 일이 생겨도 별 문제가 없을 수도 있습니다. 파일을 패치 할 때, 서브버전(Subversion)은 파일이 이미 변경되었다는 것을 대체로 감지하고 아무 일도 하지 않습니다. 하지만 어떤 식으로든 이미 변경된 곳에 또다시 변경이 가해졌다면 충돌(conflict)이 생길 것입니다. 이상적으로는, 버전 컨트롤 시스템이 한 브랜치에 대한 수정사항의 중복 적용을 막아 주어야 합니다. 시스템이 한 브랜치에 대해 이미 수정한 사항들을 자동적으로 기억했다가, 그것을 당신에게 제공할 수 있어야 합니다. 이 정보를 통해 자동적인 병합을 최대화 할 수 있습니다. 아쉽게도, 서브버전(Subversion)은 이러한 시스템이 아닙니다. CVS와 마찬가지로 서브버전 1.0은 병합에 관한 어떠한 정보도 기록하지 않고 있습니다. 당신이 로컬의 변경을 커밋(commit)할 때, 저장소(repository)는 이러한 변경이 svn merge의 결과로 나온 것인지, 아니면 직접 그 파일을 수정한 것인지 알지 못합니다. 이게 사용자에겐 어떤 의미일까요? 서브버전이 이러한 기능 추가를 할 때 까지는 병합되는 정보를 직접 수동으로 관리해야 한다는 것입니다. 기록하기 제일 좋은 곳은 커밋 로그 메시지 입니다. 앞의 예제에서 보았듯이, 어떤 리비젼으로부터 (혹은 리비젼 범위로부터) 병합을 했는지 로그 메시지에 기록하는 것을 추천합니다. 추후에 svn log명령을 통해 당신의 브랜치가 어떤 변경을 적용했는지 알 수 있습니다. 이것을 통해서 이미 변경된 이식을 중복해서 적용하지 않게 주의해서 svn merge를 사용할 수 있습니다. 다음 장에서, 이러한 테크닉을 작동하는 예제로 보여드리겠습니다. 1.3.2.2. 병합을 사전에 검토하자병합의 결과가 로컬에만 적용되기 때문에, 그리 위험성이 높은 작업은 아닙니다. 병합의 잘못되었을 경우에는 단순히 svn revert 명령을 이용해서 변경을 다시 되돌리고 재시도 할 수 있습니다. 하지만, 당신이 당신의 작업 본에만 무언가 수정했을 수도 있습니다. 이 경우 병합을 적용하면 당신이 이미 수정한 것과 섞이게 될 것이고, svn revert는 더 이상 사용할 수 없을 것입니다. 이 두 가지 변경은 이제 분리될 수 없기 때문입니다. 이런 경우, 사람들은 병합을 적용하기 전에 병합된 결과를 미리 예측하거나, 실험해 보길 원할 겁니다. 이렇게 해보는 쉬운 방법은 우리가 첫 번째 병합 예제에서 보여준 것처럼, svn diff를 당신이 실행하려고 하는svn merge와 같은 인자로 실행하는 것입니다. 사전에 검토해 볼 수 있는 다른 방법은 병합 명령에 --dry-run 옵션을 주는 것입니다.
--dry-run 옵션을 사용하면 당신의 작업 본에는 어떠한 변경도 가해지지 않습니다. 이것은 진짜 병합을 적용했을 때 뿌려질 상태 코드(status code)만을 보여줄 뿐입니다. svn diff명령이 너무 상세한 정보를 줄 때는 위의 옵션으로 병합할 때 나타날 수 있는 고차원적(high level) 사전 검토를 할 수 있어 유용합니다. 1.3.2.3. 계통(Ancestry)정보를 사용하거나 무시하기서브버전 개발자와 이야기를 나눈다면, 계통(Ancestry)라는 용어를 많이 들을 수 있을 것입니다. 이 단어는 저장소에 있는 두 오브젝트의 관계를 나타낼 때 사용됩니다. 만약에 둘이 서로 연관되어있다면, 한 오브젝트가 다른 오브젝트의 조상이라고 말할 수 있습니다. 예로, 당신이 foo.c파일에 수정을 해서 리비전100으로 커밋이 되었다고 합시다. 그러면 foo.c@99(리비전 99의 foo.c)는 foo.c@100의 조상입니다. 다시 당신이 foo.c를 삭제하고 리버전 101로 커밋 하고, 다시 같은 파일 이름을 가진 파일을 리버전 102로 추가하였다고 합시다. 이 경우 foo.c@99와 foo.c@102는 서로 같은 경로를 가져서 연관된 것처럼 보이지만, 사실 저장소에서 이 파일은 서로 연관이 전혀 없습니다. 이 파일들은 히스토리도 공유하지 않고, 서로 계통도 틀립니다. 이 이야기를 꺼낸 이유는 svn diff와 svn merge에서 중요한 차이를 보이기 때문입니다. 전자의 명령은 계통을 무시하지만, 후자의 명령은 계통 정보를 이용합니다. 예를 들어, svn diff로 foo.c의 리비전 99와 리비전 102를 비교한다고 했을 때, 줄 단위로 차이(diffs)를 처리한 결과를 볼 수 있습니다. diff명령은 그냥 두 경로를 단순히 비교합니다. 하지만, svn merge로 위의 두 오브젝트를 비교했을 경우에는 서브버전은 이 두 파일이 서로 연관되지 않았다는 것을 파악하고 이전 파일을 삭제한 후 새 파일을 추가할 것입니다. 즉 D foo.c후 A foo.c 하는 것을 볼 수 있습니다. 대부분의 병합은 서로 계통적으로 연관된 트리를 비교하는 작업을 필요로 하고, svn merge의 기본 행동도 그런 것 입니다. 하지만, 때때로 서로 연관되지 않은 트리를 병합하는 경우도 있을 것입니다. 예를 들어, 서로 다른 벤더에서 릴리즈한 두개의 소스 코드 트리를 받았다고 합시다(“벤더 브랜치들”장을 참고해 주십시오). 만약에 svn merge명령을 써서 두 트리를 병합하려고 한다면 첫 번째 트리가 모두 삭제되고 두 번째 트리가 모두 추가되는 것을 보게 될 것입니다! 이 경우에는 svn merge 명령이 경로만 사용하고, 파일들과 디렉터리들의 연관 관계는 무시하면서 비교할 수 있어야 합니다. 병합 시에 --ignore-ancestry 옵션을 추가하면 svn diff명령과 같은 형태로 동작합니다. (반대로, svn diff명령에 --notice-ancestry 옵션을 추가하면 diff가 merge처럼 행동합니다.) 1.3.3. 브랜치(branch) 전체의 merge지금, 생각해 온 예를 완결시키기 (위해)때문에, 조금 시간이 경과했다고 합니다. 며칠인가 경과해, 많은 변경이 trunk에도 선반의 사적인 브랜치(branch)에도 오코시. 그리고 당신은 사적인 브랜치(branch)상에서의 작업을 끝냈다고 합시다; 기능 추가, 또는 버그 픽스(bug-fix)가 완료해, 다른 사람이 그 부분을 사용할 수 있도록(듯이) 하기 위해서, 당신의 브랜치(branch)상의 변경점의 모든 것을 trunk 에 merge 하고 싶다고 합니다. 그런데, 이러한 상황에서는, 어떻게 해svn merge (을)를 사용하면 좋은 것일까요? 이 커멘드는 두 개의 트리를 비교해, 그 차분을 작업 카피에 적용하는 것인 것을 생각해 내 주세요. 변경점 (을)를 받기 위해서(때문에)는, 당신은 trunk의 작업 카피를 손에 넣을 필요가 있습니다. 여기에서는 당신은(완전하게 갱신된) 원래의 작업 카피를 아직 가지고 있는지, /trunk/calc의 새로운 작업 카피를 체크아웃 한 것 (와)과 가정합니다. 그러나, 어느 트리와 어느 트리를 비교하면 좋은 것일까요? 조금 생각하면(자), 그 대답은 분명하게 생각됩니다: 단지 trunk의 최신의 트리와 당신의 브랜치(branch)의 최신의 트리입니다. 그러나, 조심해 주세요 이 가정은잘못해입니다. 그리고 이전 차이에, 대부분의 초심자는 당해 버립니다! svn merge는svn diff와 같이 일하므로 마지막 트렁크와 브랜치(branch)의 트리의 비교는 단지 당신이 자신의 트리에 대해서 간 변경점만을 나타내는 것이아니다 의를 알 수 있습니다. 그러한 비교는, 매우 많은 변경을 표시하겠지요: 그것은, 당신의 브랜치(branch)에 대한 추가점만을 표시하는 것이 아니라, 당신의 브랜치(branch)에서는 결코 일어나지 않았다, trunk상의 변경점의취소도 표시해 버리겠지요. 당신의 브랜치(branch)상에 일어난 변경만을 나타내려면 , 당신의 브랜치(branch)의 초기 상태와 최종적인 상태를 비교할 필요가 있습니다. svn log커멘드를 당신의 브랜치(branch)상에서 사용하면, 그 브랜치(branch)는 리비전 341으로 만들어진 것을 알 수 있습니다. 그리고, 브랜치(branch) 의 최종적인 상태는, 단지, HEAD 리비전을 지정하면 압니다. 결국, 최종적인 merge 처리는 이하와 같이 됩니다:
1.4. 저장소(repository)로부터의 변경을 삭제한다svn merge 가 좋게 있는 사용법은 벌써 커밋했다 변경을 롤백(rollback) 하는 것입니다. integer.c (을)를 변경한 리비전 303의 변경은 완전하게 실수였다고 합니다. 그것은 커밋해야 하지는 않았습니다. 작업 카피의 변경을 취소하는데 (은)는svn merge 를 사용할 수가 있습니다. 그 후로 로컬 의 변경을 저장소(repository)에 커밋합니다. 하지 않으면 안 되는 것은, 반대 방향의차분을 지정하는 것 뿐입니다:
저장소(repository) 리비전에 대한 하나 더의 생각은, 그것을
특정의 변경의 모임이라고 생각하는 것입니다(몇개의 버전 관리
시스템에서는, 이것을,changesets와
부르고 있습니다). 이러한 변경의 취소는, 보통svn merge 의 조작에 지나지 않기 때문에, 작업 카피가 바라는 상태가 되었는지 어떠했는지는 svn status 와svn diff 를 사용할 수가 있어 그 후svn commit 로 저장소(repository)에 최종적인 버전을 보낼 수가 있다, 라고 하는 것을 눌러 두어 주세요. 커밋 후는 이 특별한 체인지 세트는 이미 HEAD 리비전에는 반영되지 않습니다. 이렇게 생각할지도 모릅니다: 로 하면(자), 그것은 「취소」가 아니다 (이)가 아닌가. 변경은 아직 리비전 303에 존재하고 있는 것은, 이라고. 만약 누군가가calc 프로젝트의 리비전 303 (와)과 349의 사이의 버전을 체크아웃 했다고 하면(자), 잘못했다 변경을 받아들이는 것은 아닌지, 다른지, 라고. 말씀하시는 대로. 우리가, 변경의 것"취소"에 대해 말할 때, 사실은"HEAD로부터 없애는 것"을 말합니다. 원래의 변경은 저장소(repository)의 히스토리에 여전히 남아 있습니다. 대부분의 상황에서는 이것으로 충분합니다. 어쨌든 대부분의 사람들은 프로젝트의 HEAD를 뒤쫓는다 일에만 흥미가 있기 때문입니다. 그러나, 커밋에 관한 모든 정보를 삭제하고 싶다고 하는 예외적인 상황도 있겠지요. (아마, 누군가가 극비의 문서를 커밋해 버린, 등) 이것은 그렇게 쉬운 일이 아닙니다. Subversion은 의도적으로 결코 정보가 없어지지 않게 설계되고 있기 때문입니다. 히스토리로부터의 리비전의 삭제는, 연쇄적인 영향을 주어 모든 후속 리비전과 아마 모든 작업 카피에 혼란을 일으킵니다. [3] 1.5. 작업 카피의 변환svn switch 커멘드는 존재하고 있는 작업 카피를 다른 브랜치(branch)로 변환합니다. 이 커멘드는 브랜치(branch)로 작업하는데 항상 필요라고 하는 것은 아닙니다만, 유저에 대해서 편리한 쇼트 컷 (을)를 준비합니다. 전의 예로, 사적인 브랜치(branch)를 만든 뒤, 그 새롭다 저장소(repository) 디렉토리의 작업 카피를 체크아웃 했습니다. 그렇게 한다 대신에, 단지/trunk/calc 의 작업 카피를 새롭다 브랜치(branch)의 장소의 카피로 변경할 수가 있습니다:
브랜치(branch)에"스윗치"한 후에는, 작업 카피의 내용은 그 디렉토리 (을)를 새롭게 체크아웃 했을 경우와 완전히 같은 것이 됩니다. 그리고 보통 이 커멘드를 사용하는 편이 보다 효율적입니다. 그렇다고 하는 것은, 대부분 브랜치(branch)는 아주 조금 내용이 다를 뿐입니다. 서버는 그 브랜치(branch) 디렉토리 (을)를 반영시키기 위해서(때문에) 작업 카피로 하지 않으면 안 되는 최소한의 변경만을 송신하면 삽니다. svn switch 는 물론, 대부분의 프로젝트는calc보다는 좀 더 복잡해, 복수의 서브 디렉토리를 포함하고 있습니다. Subversion 유저는 브랜치(branch)를 이용할 때에는 자주(잘), 특정의 방식을 합니다. :
바꾸어 말하면(자), 유저가 특정의 서브 디렉토리상에서만 브랜치(branch)의 작업이 일어난다 일을 알고 있는 경우에는svn switch를 사용해 브랜치(branch)에 그 서브 디렉토리만을 이동합니다. (혹은, 끊은 하나의 작업 파일 만을 브랜치(branch)에 switch 하는 것 조차 있습니다! ) 그방법에서는, 작업 카피 의 거의 모든 갱신을 보통'간(trunk)'로부터 종래 대로 받는 것이 할 수 있습니다만, 바꾼 부분만큼은 변경되는 일 없이 남습니다(만약 브랜치(branch) 에 대해서 누군가가 변경점을 커밋하기만 하지 않으면). 이 기능은"혼합 작업 카피" 그렇다고 하는 개념에 완전히 새로운 차원을 덧붙이게 됩니다 작업 카피는 작업 리비전의 혼합을 포함하는 것이 가능한 한에서는 없고, 저장소(repository) 위치의 혼합도 포함할 수가 있습니다. 만약 작업 카피가 다른 저장소(repository) 위치로부터의 스윗치 된 서브 트리 (을)를 몇개인가 포함한다면, 그것은 계속 보통으로 기능합니다. 갱신하면(자), 각각의 서브 트리의 패치를 적절히 받겠지요. 커밋하면(자) 로컬 수정은 하나의 불가분의 변경을 저장소(repository)에 적용하겠지요. 저장소(repository) 위치의 혼합을 작업 카피에 반영시킬 수 있습니다만, 이러한 저장소(repository) 위치는 모두같은 저장소(repository) 의 안에 없으면 안됩니다. Subversion의 저장소(repository)는 아직 서로 통신 할 수 없습니다. 이것은 Subversion1. 0이후에 계획되고 있는 기능입니다. [4] svn switch 는 본질적으로는svn update 의 변종이므로, 같은 동작을 공유합니다. 작업 카피중의 어떠한 로컬의 변경도 저장소(repository)로부터 새로운 데이터가 닿을 때 보존됩니다. 이것으로 모든 영리한 잔기술이 (듣)묻게 됩니다. 예를 들어/trunk 의 작업 카피가 있어 거기에 몇개인가 변경을 더했다고 합니다. 그리고 돌연, 사실은 브랜치(branch)에 하는 변경이었던 일로 눈치챕니다. 문제 없습니다. 작업 카피를svn switch 그리고 브랜치(branch)에 스윗치 해도, 로컬의 변경은 그대로 남습니다. 그리고, 그것을 브랜치(branch)에 대해서 테스트해, 커밋할 수가 있습니다. 1.6. 태그다른 버전 관리의 개념에,태그가 있습니다. 태그는 어떤 시점에서의 프로젝트의"snapshot"입니다. Subversion 그럼 이 아이디어는 벌써 다양한 장소에 있는 것처럼 보입니다. 각각 의 저장소(repository) 리비전은 확실히 그것입니다즉, 그것은 커밋 직후의 파일 시스템의 snapshot입니다. 그러나, 사람은 자주 태그에 대해서 인간에게 친숙함이 있는 이름을 붙이고 싶으면 생각하는 것입니다. 예를 들어,"release-1. 0"과 같은. 또, 파일 시스템 의 좀 더 작은 서브 디렉토리의 snapshot를 갖고 싶은 일도 있습니다. 결국, 어느 소프트의 일부의 release-1. 0 이 리비전 4822의 특정의 서브 디렉토리 (이)라고 있는 것을 생각해 내는 것은 간단하지는 않습니다. 1.6.1. 간단한 태그의 작성한번 더,svn copy 의 도움을 받습니다. 만약 HEAD 리비전의/trunk/calc 의 스냅 쇼트를 만들고 싶을 때에는, 그 카피를 취하면 좋기 때문에 했다:
이 예에서는/tags/calc 디렉토리가 벌써
존재하고 있는 것으로 하고 있습니다. 카피 완료 후, 새롭다
release-1. 0 디렉토리는, 당신이 카피했다
시점의 HEAD 리비전에 대해 프로젝트가 어떻게 보이고 있었는지를 스냅
쇼트로서 영원히 남기는 것입니다.
물론, 어느 리비전을 카피할까에 임해서 좀 더 정확하고 싶으면
생각할지도 모릅니다. 다른 사람이 당신이 보지 않을 때에 프로젝트에
대해 변경점을 커밋하고 있었을지도 모르지 않기 때문에. 만약 당신이
/trunk/calc 의 리비전 350이 자신의 가지고 싶은 스냅
쇼트라고 알고 있으면svn copy 커멘드에
그렇지만 조금 기다려 주세요: 이 태그 작성의 수속은 브랜치(branch)를 만든다 위해(때문에) 사용해 온 수속과 같지 않아? 실은 그렇습니다. Subversion 에서는 태그와 브랜치(branch)에는 차이는 없습니다. 양쪽 모두 카피로 만들어진 보통 디렉토리입니다. 정확히 브랜치(branch)와 같이 카피된 디렉토리가"태그"이다고 말해지는 것은, 단지 인간이그렇게 취급하기로 결정했기 때문에, 다만 그것 뿐입니다. 그 디렉토리에 아무도 커밋하지 않는 한, 그것은 영원히 snapshot로서 남습니다. 만약 누군가가 거기에 커밋 하기 시작하면(자), 그것은 브랜치(branch)가 됩니다. 만약 저장소(repository)를 관리하고 있다면, 태그를 관리하려면 2통의 방법이 있습니다. 최초의 어프로치는,"유저 맡김"입니다. 프로젝트 폴리시로서 당신의 태그를 두는 장소를 결정해 모든 유저에게 그 디렉토리를 카피할 때에는 어떻게 취급할까 (을)를 알립니다. (즉, 모두가 거기에 커밋하지 않게 약속합니다) 두번째의 방식은 좀 더 가치가치입니다. Subversion가 제공하는 액세스 제어 스크립트의 어떤 것인지를 사용해, 태그 area에는 새로운 카피를 만드는 것만이 할 수 있어, 그 이외의 조작을 금지합니다. (###cross-ref a section that demonstrates how to use these scripts? ). 가치가치 방식은, 보통은 불필요합니다. 만약 유저가 잘못해 태그 디렉토리 (으)로 자신의 변경을 커밋해 버리면(자), 전의 장으로 설명한 방법으로 그 변경을 취소하면 좋기 때문에. 결국, Subversion은 버전 관리 시스템인 것입니다. 1.6.2. 복잡한 태그의 작성가끔, 하나의 리비전의 하나의 디렉토리보다 좀 더 복잡한 snapshot를 갖고 싶은 일이 있습니다. 예를 들어, 프로젝트가 우리의calc 보다 좀 더 크다고 합니다. 많은 서브 디렉토리와 좀 더 많이 의 파일이 있다고 합니다. 일의 과정에서, 특정의 기능과 버그 수정을 포함했다 작업 카피가 필요하게 되었다고 판단합니다. 특정의 리비전의 파일과 디렉토리를 선택해(svn update -r liberally 를 사용해), 이것을 만들 수도 있고, 특정의 브랜치(branch)에 파일과 디렉토리를 스윗치 하는 것에 의해도 할 수 있습니다. (svn switch를 사용한다) 이것을 하면(자), 당신과 작업 카피는 다른 리비전으로부터 되는 다른 저장소(repository) 위치의 다음 벗기가 됩니다. 그러나 테스트 후, 자신이 확실히 필요와 하고 있는 편성인 것을 알 수 있었습니다. 자 snapshot를 취합니다. 하나의 URL를 작업하고 있지 않는 다른 장소에 카피합니다. 이 경우, 하고 싶은 것은 특정의 작업 카피 상태 그리고, 그것을 저장소(repository)에 격납하고 싶습니다. 행운의 일로 svn copy 는 실제로는 4 종류가 다른 사용법이 있습니다. (Chapter 8을 읽어 주세요) 그 중에는 작업 카피 트리 (을)를 저장소(repository)에 카피한다, 라고 하는 것도 있습니다:
이것으로 저장소(repository)에 새로운 디렉토리가 생겼습니다. /tags/calc/mytag입니다. 이것은 당신의 작업 카피 의 정확한 snapshot입니다 혼합 리비전, URL 그리고 방법이라고입니다. 다른 유저는 이 기능의 재미있는 사용법을 찾아냈습니다. 가끔, 자신의 작업 카피에 로컬인 수정을 했다 브랜치(branch)가 있지만, 그것을 다른 멤버에게 보여 주고 싶다고 하는 것 같은 상황입니다. svn diff 를 사용해 패치 파일을 보내는(그것은 트리의 변경을 취득할 수 없습니다) 대신에, svn copy를 사용해, 작업 카피를 저장소(repository)의 사적인 area에"업 로드"합니다. 다른 멤버는 작업 카피를 새롭게 체크아웃 하는지,cvs merge (을)를 사용해 변경점만을 받을 수가 있습니다. 1.7. 브랜치(branch)의 관리여기까지로, Subversion은 매우 유연한 시스템인 것이 알아 받을 수 있었는지라고 생각합니다. 디렉토리의 카피라고 하는 같은 기본적인 구조 위에 브랜치(branch)도 태그도 실장하고 있어, 브랜치(branch)도 태그도 보통 파일 시스템의 공간안에 있으므로, 많은 사람들은 Subversion의 구조에 놀랍니다. 그것은너무 유연한정도 입니다. 이 마디에서는 시간 경과와 함께 어떻게 데이터를 배치해 관리하는 것이 좋은 것처럼 붙어, 조금 설명합니다. 1.7.1. 저장소(repository)의 레이아웃저장소(repository)의 편성에는 어느 정도, 표준화 된, 추천하는 방법이 있습니다. 대부분의 사람들은trunk디렉토리 에 개발의"주계", 브랜치(branch)의 카피가 있는branches 디렉토리, 그리고 태그의 카피가 있는tags디렉토리 (을)를 넣습니다. 저장소(repository)가 단 하나의 프로젝트를 포함한 경우에는 자주, 이 세 개의 디렉토리를 저장소(repository) 최상정도에 만듭니다:
저장소(repository)가 복수 프로젝트를 포함한 경우는, 브랜치(branch)에 의해 배치를 인덱스화합니다. :
또 프로젝트에 따라서는 :
물론, 이 표준적인 레이아웃을 무시해도 괜찮습니다. 당신과 팀이 가장 작업하기 쉽게, 이 레이아웃은 어떻게 변화시켜도 괜찮습니다. 어떤 것을 선택해도 그것은 영구히 고정된 것이 아닙니다. 언제라도 저장소(repository)를 재편성 할 수가 있습니다. 브랜치(branch)와 태그는 보통 디렉토리 에 지나지 않기 때문에svn move커멘드를 사용하면 좋아하는 대로 이동, 명칭 변경을 할 수 있습니다. 어느 레이아웃으로부터 다른 레이아웃에의 변환은 단지 서버측에서의 몇회인가의 이동의 이야기가 됩니다. 만약 저장소(repository)중의 편성에 무엇인가 마음에 들지 않는 곳이 있다면, 디렉토리 에 관련한 잔기술을 사용해 주세요. 1.7.2. 데이터의 수명Subversion 모델의 다른 좋은 기능으로서는 다른 버전 관리된 아이템과 같이, 브랜치(branch)와 태그는 유한의 수명 밖에 가지지 않는다 같게도 할 수 있는 것입니다. 예를 들어calc프로젝트 의 개인적인 브랜치(branch)상에서 모든 작업이 완료했다고 합니다. 모든 변경을,/trunc/calc에 merge 한 후에는 그 개인적인 브랜치(branch)의 디렉토리 완전히 소용 없게 됩니다:
이것으로 브랜치(branch)는 없어져 버렸습니다. 물론 정말로 삭제되었다 (뜻)이유가 아닙니다: 디렉토리는 단지 HEAD 리비전으로부터 없어졌다 만으로, 아무도 번거롭게 할 수 있는 것은 없어졌습니다. 만약 이전의 리비전 (을)를 보고 싶은 경우는, (svn checkout -r, svn switch -r, 혹은 svn list -r를 사용해) 낡은 브랜치(branch)를 언제라도 볼 수가 있습니다. 삭제한 디렉토리를 열람하는 것 만으로는 불충분한 경우는, 언제라도 되돌릴 수도 있습니다. 데이터의 restore는 Subversion에서는 문제없습니다. HEAD에 되돌리고 싶은 삭제 디렉토리(또는 파일)가 있는 경우는, 단지 svn copy -r 를 사용해 낡은 리비전으로부터 카피해 주세요:
우리의 예에서는 개인적인 브랜치(branch)는 비교적 짧은 생존 시간을 가집니다. 버그를 고치거나 새로운 기능을 추가하는데 이용했기 때문에입니다. 작업이 끝나면, 브랜치(branch)의 수명도 거기서 마지막입니다. 그러나, 개발 내용에 따라서는 매우 긴 시간에 걸쳐서 두 개의 브랜치(branch)가 병행해 계속 사는 일도 있습니다. 예를 들어, 안정판의calc프로젝트를 공에 릴리스 할 때가 와, 그 소프트의 버그를 취하는데는 몇개월걸린다고 합니다. 릴리스 버전에 새로운 기능을 추가시키고 싶지는 없습니다만, 모든 멤버에게 개발을 중지하도록(듯이) 말해 구도 있어 선. 거기서 대신에, 당신은 그만큼 큰 수정은 발생하지 않는다 "안정판"의 브랜치(branch)를 만듭니다:
이것으로 개발자는 최첨단의 기능(혹은 실험적인 기능)을 /trunk/calc에 계속 추가하는 것이 할 수 있어 버그 수정만을/branches/calc/stable-1. 0 에 커밋하는 것 같은 폴리시로 진행할 수가 있습니다. 즉, 멤버가 trunk상에서 계속 작업할 때 , 버그 수정에 붙어 안정판 브랜치(branch)상에 가지고 갈 수가 있습니다. 안정판 브랜치(branch) 하지만 출시된 후에도, 그 브랜치(branch)를 긴 시간 들여 계속 메인트넌스한다 그렇지즉, 고객에 대해서 그 릴리스를 계속 서포트한다 한. 1.8. 통계이 장에서는, 여러가지 기본에 접했습니다. 태그와 브랜치(branch)의 개념을 논의해, Subversion가svn copy로 디렉토리를 카피한다 것에 의해 이러한 개념을 실장하고 있는 것을 설명했습니다. svn merge인 브랜치(branch)로부터 다른 브랜치(branch)로 변경점 (을)를 카피하거나 잘못한 변경을 되돌리거나 하는 방법을 보여드렸습니다. svn switch를 사용해, 혼합 상태의 작업 카피를 만들어 보였습니다. 그리고, 어떻게 해 저장소(repository)내의 브랜치(branch)의 편성과 수명을 관리할까에 임해서 이야기했습니다. Subversion에 대해, 이것만은 기억해 둬 주세요: 브랜치(branch) 처리는 매우 가볍습니다. 좋아할 뿐(만큼) 사용해 주세요! 1.9. 중간에 약간 수정한 사람의 이야기:이 글은 약간 실망스럽다 말한다 정도로 몹시 읽기 힘든 번역이다. 나는 갑자기 고치고 싶다는 생각이 갑작스레 들기 시작해 단지 약간 고쳐보았을 뿐이다. "가지 와 병합" 에 심각한 부분만 골라내어 수정을 조금 가했다. 시간이 난다면 마저 고치고 싶다는 생각을(를) 하고 있다. 이 우스꽝스러운 section 은 낭중에 지우겠다. Notes
|
People who take cat naps don't usually sleep in a cat's cradle. |