실용주의 프로그래머를 위한 버전관리 Using CVS DarkKaiser, 2007년 6월 30일2023년 9월 6일 1. 서론 소스 코드 관리라고 불리기도 하는 버전 관리는 프로젝트를 지원하는 기술의 세 축 가운데 첫째 축이다. 모든 프로젝트에서는 반드시 버전 관리를 사용해야 한다.소스 코드 관리라고 불리기도 하는 버전 관리는 프로젝트를 지원하는 기술의 세 축 가운데 첫째 축이다. 모든 프로젝트에서는 반드시 버전 관리를 사용해야 한다. 버전 관리를 하면 팀과 개인 모두 다음과 같은 이득을 누릴 수 있다. 전체 프로젝트의 되돌림(UNDO) 버튼이 팀에 생긴다. 돌이킬 수 없는 실수라는 것이 없어지고, 잘못을 원상 복구하기도 쉽다. 동일한 코드를 가지고 여러 개발자가 잘 정리된 방식으로 작업하게 된다. 누군가 다른 팀원이 만든 코드를 덮어 쓰더라도 그 전 내용을 잃어버릴 염려가 없다. 버전 관리 시스템은 여태까지 무엇이 변경되었는지 기록을 유지한다. 중심 개발 버전을 계속 진행하는 동시에 소프트웨어의 과거 릴리즈를 계속 지원할 수 있게 한다. 릴리즈 직전이라 코드 동결(code freeze)을 하더라도 팀이 일을 멈추지 않아도 된다. 버전 관리는 전체 프로젝트의 타임머신과 같아서, 시계 바늘을 돌려서 과거 어느 때 프로젝트가 어떤 모습이었는지 정확하게 보여줄 수 있다. 이 기능은 코드를 조사할 때도 유용하지만, 특히 고객이 문제가 생긴 이전 릴리즈를 들고 왔을 때 과거로 돌아가 그 릴리즈를 다시 생성할 때 꼭 필요하다. 2. 버전 관리란 무엇인가? 저장소 대부분의 버전 관리 시스템에서 저장소는 프로젝트 파일의 모든 버전들의 원본을 담아놓는 중심 장소다. 일부는 데이터베이스를 저장소로 쓰고, 몇몇은 일반 파일 시스템을 사용하고, 몇몇은 둘을 조합해서 쓴다. 무엇이 저장되는가? 저자소에는 프로젝트의 모든 것이 저장된다. 하지만 우리가 말하는 “모든 것”이란 정확히 무엇인가? 일단, 프로젝트를 빌드하려면 분명 프로그램 소스 파일이 필요하다. 버전 관리에서 소스 코드의 비중이 굉장히 크기 때문에 중요하기는 하지만, 많은 사람들이 버전 관리 아래 저장해야 할 다른 모든 것들을 잊는 실수를 저지른다. 예를 들어 자바 프로그래머는 소스를 컴파일하려고 빌드 툴인 Ant를 사용하기도 한다. Ant는 자신의 작업을 제어하기 위해 보통 build.xml이라고 불리는 스크립트 파일을 쓴다. 이 스크립트는 빌드 과정의 일부분이라서, 없으면 애플리케이션을 빌드하지 못한다. 따라서 이것도 반드시 버전 관리 시스템에 저장해야 한다.사실 무엇을 저장소에 넣고 빼야 하는지 결정할 때 간단히 시험해 볼만한 법이 하나 있다. 스스로에게 “x의 최신판이 없더라도 애플리케이션을 빌드하고 인계할 수 있나?”하고 물어보는 것이 그것이다. 답이 “안돼.”라면 x는 저장소에 들어가야 한다. 릴리즈할 소프트웨어를 생성하기 위해 필요한 파일 외에도, 코드가 아닌 프로젝트 산출물도(나중에 프로젝트를 이해하는 데 필요한 것이라면) 모두 버전 관리 아래 저장해야 하는데, 프로젝트 문서도 (외부 문서와 내부 문서 모두) 여기 포함된다. 중요한 전자우편, 회의 기록, 웹에서 찾은 정보 등 프로젝트에 기여하는 모든 것들이 여기 포함될 수 있다. 이와 비슷하게, 환경 설정을 제어하려고 메타데이터를 사용하는 프로젝트도 많다. 이 메타데이터도 저장소에 들어가야 한다. 릴리즈 CD를 만들기 위해 사용하는 스크립트나, QA팀이 사용하는 시험 데이터 등등도 마찬가지다. * 자동 생성된 파일은 어떻게 해야 하지? 프로젝트를 빌드할 때 필요한 모든 것을 저장해야 한다면, 자동 생성된 파일도 모두 저장해야 한다는 말인가? 예를들어 소스 트리 구조의 API 문서를 생성하려고 ?JavaDoc을 돌리는 경우도 있다. 이렇게 생성된 문서도 버전 관리 시스템의 저장소에 저장해야 하나? 간단한 답은 “아니.”다. 자동 생성된 파일을 저장소에 저장되는 다른 파일로부터 다시 만들어낼 수 있다면, 그런 파일을 저장하면 중복일 뿐이다. 왜 중복이 안 좋은가? 디스크 용량 걱정 때문이 아니다. 질서가 어지러워지는 것을 원하지 않기 때문이다. 소스와 문서를 함께 저장한 상태에서 소스를 변경하면 이제 문서는 시간에 뒤쳐진다. 만약 문서도 같이 갱신하는 것을 잊어버리고 체크인하면, 저장소에 잘못된 문서가 들어가 있게 된다. 따라서 이런일이 일어나지 않기위해 정보의 원본은 하나만 유지해야 한다. 대부분의 자동 생성 파일에 같은 규칙이 적용된다. 작업공간과 파일 다루기 작업 공간을 처음으로 만들려면, 저장소에서 파일을 가져와야 한다. 이 작업을 체크아웃(check out)이라 한다. 저장소에서 체크아웃을 하면, 저장소에 있는 파일을 작업 공간으로 복사해 와서 지역 복사본을 만든다.프로젝트를 하면서, 여러분은 지역 작업 공간의 코드를 변경하게 된다. 언제가 여러분이 변경한 내용을 저장소에 저장하고 싶은 때가 온다. 이 과정을 전송(commit)이라고 부르는데, 여러분이 만든 변화를 저장소에 다시 보내는 과정이다. 프로젝트, 모듈, 파일 대부분의 버전 관리 시스템의 저장 구조에서 가장 작은 단위는 파일이다. 하지만 파일은 상당히 작은 단위다. 보통 한 프로젝트에는 파일이 몇백 몇천개 있기 마련이고, 일반적인 회사에는 프로젝트가 수십 개 있기 마련이다. 다행스럽게도 거의 모든 버전 관리 시스템이 저장소에 구조를 만들어 파일들을 정리하게 해 준다. 보통 프로젝트를 가장 큰 단위로 삼아 작업 파일들을 분류하며, 그 아래 단위는 모듈이다.CVS에서는 저장소 관리자가 프로젝트를 여러 모듈로 나눌 수 있다. 모듈이란 파일들의 모임으로, 이 파일들은 모듈 이름을 통해 하나의 단위로써 체크아웃된다. 모듈 간에 위계구조를 만들어도 되지만 꼭 필요하지는 않다. 동일한 파일이나 파일 집합이 서로 다른 모듈에 여러 번 나와도 된다. 심지어 프로젝트 사이에서 코드를 공유하는 것도 모듈을 사용하여 가능하다. 버전 이야기는 언제 나오는가? CVS에서 파일의 첫 번째 버전은 개정판 번호 1.1을 받는다. 만약 이 파일이 변경돼서 체크인되면, 변경된 버전은 1.2를 받는다. 다음 변경은 1.3을 받으며, 이런 식으로 계속된다. 그리고 개정판 번호마다 파일이 체크인된 날짜와 시각, 그리고 개발자가 어떤 변경을 했는지에 대해 달아놓은 주석이 함께 저장된다.이 체계를 사용해서 버전 관리 시스템은 다음과 같은 것을 할 수 있다. 파일의 특정 개정판을 받아온다. 어떤 시스템의 모든 소스코드를 두 달 전 모습 그대로 체크아웃한다. 버전 1.3과 1.5 사이에 무엇이 변경되었는지 보여준다. 꼬리표 개정판 번호도 좋지만, 사람은 1.47 같은 번호보다 “?PreRelease2″ 같은 이름을 더 잘 기억한다. 버전 관리 시스템은 일단의 파일들이나 모듈이나 전체 프로젝트를 대상으로 어떤 특정 시점에 이름을 붙이게 해 준다. 특정 파일에 “?PreRelease2″란 꼬리표를 붙여 놓으면 나중에 이 꼬리표 이름으로 해당 파일들의 꼬리표를 붙인 시점의 개정판을 체크아웃할 수 있다. 브랜치 일반적인 개발 환경이라면, 개발자들은 대부분 동일한 코드 기반을 가지고 작업한다. 체크아웃하고, 개정판을 만들고, 변경 사항을 다시 체크인해 넣으면 모두가 서로의 작업을 공유한다. 이런 개발 흐름을 가리켜서 중심 개발축(mainline)이라고 부른다. 아래의 그림이 중심 개발축을 나타낸 그림이다. 위 그림에서 시간은 왼쪽에서 오른쪽으로 흐른다. 가로로 놓인 두꺼운 직선이 시간 흐름에 따른 코드의 발전, 즉 개발의 중심축을 나타낸다. 개발자 한 사람 한 사람은 이 중심 개발축으로부터 자기 작업 공간으로 코드를 체크인하고 체크아웃한다. 이제 새 릴리즈가 곧 출시될 시점이라고 해보자. 개발자 가운데 일부가 하위팀을 만들어서 소프트웨어의 이번 릴리즈를 준비하며 마지막 버그를 잡고, 릴리즈 담당 공학자들과 협력하고, QA팀을 도울 것이다. 이 중요한 시기에는 안정성이 제일 중요하다. 다른 개발자가 이 시기에 코드를 수정하거나 다음 릴리즈용으로 기능을 추가하거나 한다면 이들의 노력에 방해만 될 것이다. 릴리즈가 만들어지는 동안 새로운 개발을 동결하는 것도 한 방법이지만, 사실 이 말은 나머지 팀원들보고 그동안 놀고 있으라는 말과 다름이 없다.소프트웨어 소스를 남는 컴퓨터에 복사한 다음 릴리즈 팀이 이 컴퓨터를 사용하는 방법도 있따. 하지만 이럴 경우, 이들이 릴리즈용으로 만든 복사본에서 만든 변경 사항을 어떻게 해야할까? 어떻게 이 변경 사항들의 기록을 남기고 관리할까? 릴리즈 코드에서 버그를 발견했는데 이 버그가 중심 개발축에도 있다면, 버그 고친 코드를 효과적이고 안전하게 중심 개발축에 병합하는 방법은 무엇일까? 그리고 소프트웨어 릴리즈가 출시된 다음, 고객이 보고하는 버그는 어떻게 고칠까? 릴리즈를 출시할 당시 상태 그대로의 소스 코드를 나중에도 찾을 수 있다고 어떻게 보장하겠는가? 버전 관리 시스템에 내장된 브랜치 나누기 기능을 사용하는 것이 훨씬 나은 선택이다. 브랜치 나누기는 어떤 사건 때문에 시간 흐름이 갈라져버린다는, 과학 소설에 자주 나오는 진부한 수법과 좀 비슷하다. 그 시점부터 두 종류의 병행 미래가 생긴다. 다른 사건이 또 일어나면, 미래 가운데 하나가 또 갈라진다. 제품의 새 버전을 곧 릴리즈하는 어떤 팀을 예로 들어보자. 지금까지 모든 팀원은 위 그림에 나온 코드 공통 흐름인 중심 개발축에서 작업했다. 하지만 릴리즈 하위팀이 이제 중심 개발축에서 분리되어 나오고 싶어한다. 저장소에 브랜치를 하나 만드는 것이 방법이다. 지금부터 릴리즈 작업이 끝날 때까지 릴리즈 하위팀은 이 브랜치로부터 체크아웃하고 이 블랜치에 체크인한다. 애플리케이션이 릴리즈된 후에도 이 브랜치는 그대로 살아남는다. 고객이 버그를 보고하면 개발팀은 이 릴리즈 브랜치에서 버그를 고친다. 아래 그림에 이 상황이 나온다. 브랜치는 완전히 별개의 저장소와 다름없어서, 어떤 브랜치에서 작업하는 사람은 다른 브랜치나 중심 개발축에서 일하는 사람과 전혀 상관없이 자기 브랜치의 코드만 보고 작업한다. 브랜치마다 자신만의 기록을 남기며 개정판을 기록하고 유지하는 일도 독립적으로 수행한다. 릴리즈를 만들 때 원하는 것이 바로 이것이다. 릴리즈를 만드는 팀에게는 출시를 위해 마무리 손질을 할 안정적인 코드 기반이 생긴다. 중심 개발팀도 릴리즈가 만들어지는 동안 코드를 동결할 필요없이 중심 개발축 코드를 계속 수정할 수 있다. 그리고 고객이 나중에 그 릴리즈에 있는 버그를 보고해 올 경우 개발팀이 릴리즈 브랜치의 코드로 돌아와 작업하면 중심 개발축에서 그동안 새로 개발된 코드를 포함하지 않으며 버그만 고쳐진 개선된 릴리즈를 출시할 수 있다. 브랜치는 고유한 꼬리표로 다른 브랜치와 구별되며, 브랜치 안에 있는 파일의 개정판 번호에는 숫자가 추가로 붙는다. 예를들어 어떤 파일의 개정판 번호가 1.14인데 그 시점에서 브랜치를 하나 만들었다면, 그 브랜치에서의 개정판 번호는 1.14.2.1이 되지만, 중심 개불축에서는 여전히 1.14로 남는다. 중심 개발축에서 이 파일을 고치면 개정판 번호가 1.15로 올라가고, 브랜치에서의 이 파일을 고치면 개정판 번호는 1.14.2.2가 된다. 병합하기 어떤 브랜치에 속한 파일의 체크인이나 체크아웃은 그 브랜치를 대상으로만 할 수 있지만, 한 개발자의 컴퓨터에 브랜치를 여러 개 체크아웃하는 일은 어렵지 않다(물론 각각 하드디스크의 다른 디겍터리에 해야겠지만). 이렇게 하면 개발자 한 명이 중심 개발축 작업과 어떤 릴리즈 브랜치의 버그 수정 작업을 동시에 할 수 있다.더 좋은 방법이 있는데, 버전 관리 시스템이 지원하는 병합을 이용하는 것이다. 예를 들어 릴리즈 브랜치에서 버그를 하나 고쳤는데 이 버그가 중심 개발축 코드에도 있다는 것을 알게 되었다고 하자. 그러면 버전 관리 시스템에게 코드를 어떻게 바꾸면 버그 있는 코드에서 없는 코드가 되는지 계산한 다음, 그것을 그대로 중심 개발축 코드에 적용하라고 지시할 수 있다. 이러면 시스템의 여러 버전을 왔다 갔다 하면서 복사와 붙여넣기할 일이 거의 없어진다. 3. CVS 시작하기 시스템에 CVS가 설치되지 않았으면 아래 홈페이지에서 다운로드 받아 CVS를 설치하자. CVS는 윈도우/유닉수 두가지를 지원한다. CVS를 설치하고 나면, 잊지말고 다앙한 CVS 프로그램의 경로를 PATH에 추가해서 명령줄에서 그 프로그램들을 사용할 수 있게 만들어 놓는다. CVS homepage : http://www.cvshome.org WinCVS : http://www.wincvs.org (윈도우 기반의 GUI 제품) Tortoise CVS : http://www.tortoisecvs.org (윈도우 기반의 GUI 제품) 저장소 만들기 CVS가 동작하려면 저장소가 필요하다. CVS 저장소는 컴퓨터의 일반 파일 시스템에 그냥 파일과 디렉토리의 구조로 존재한다. CVS에게는 이 트리 구조의 최상위 디렉토리를 어디로 할 것인지만 말해 주면 된다. 저장소를 만드는 가장 간단한 방법은 명령줄에서 “cvs init” 명령을 사용하는 것이다. “sandbox” 디렉토리를 저장소로 사용한다고 했을 때, 아래와 같이 하면 된다. 유닉스 : cvs -d ~/sandbox init 윈도우 : cvs -d c:\sandbox init -d(Descination) 파라미터는 CVS에게 저장소의 위치를 알려준다. 간단한 프로젝트 만들어 사용하기 새로운 프로젝트를 저장소에 넣어보겠다. 제일 먼저 할 일은 파일을 몇 개 만들고 이 파일들을 저장소의 sesame 프로젝트에 들여오는 작업이다(sesame : 프로젝트 이름). 그럼, 이제 막 sesame 프로젝트를 시작했다고 생각해보자. 아직 저장소에는 프로젝트가 없는데, 그 프로젝트에 넣을 것이 아직 없기 때문이다. 우선 컴퓨터에 임시 디렉토리를 하나 만든다. 그리고 이 디렉토리에 Color.txt와 Number.txt 이렇게 파일을 두 개 만든다. Color.txt 파일 : black brown red orange yellow green Number.txt 파일 : zero one two three four 이제 CVS에게 저장소에 새로운 프로젝트를 만들도록 이 파일들을 들여오라고 지시해야 한다. 그러기 위해선 cvs import 명령을 써야한다. 먼저 두 파일이 들어있는 임시 디렉토리로 간다. 윈도우 : cvs -d c:\sandbox import -m " " sesame sesame initial 유닉스 : cvs -d ~/sandbox import -m " " sesame sesame initial -d 파라미터는 cvs init 때와 마찬가지로 CVS에게 저장소가 어디에 있는지 알리는 역할을 한다. 그 다음에 나오는 -m(Message)과 빈 문자열은 이 들여오기에 대해 기록해 둘 메시지를 다는 일을 한다. 다음 파라미터 sesame는 저장소 안에서 프로젝트에 붙을 이름이다. 앞으로 프로젝트를 참조할 때는 늘 이 이름을 쓰므로, 현명하게 이름을 짓도록 한다. 마지막 파리미터 두 개는 꼬리표이다. 이제 sesame라는 프로젝트를 work라는 디렉토리에 체크아웃 해 보겠다. work라는 디렉터리로 이동하자. -d 선택사항은 CVS에게 저장소 위치가 어디인지 알려주고 co는 체크아웃(check out)을 나타내며, sesame는 프로젝트의 이름이다. cvs -d ~/sandbox co sesame 이제 처음에 저장소에 들여온 파일 두 개로 구성된 sesame 프로젝트의 지역 복사본이 생겼다. 다음으로 변경된 파일을 저장소에 갱신하는 방법을 알아보겠다. 우선 지역 복사본의 상태 혹은 파일의 상태를 알아보려면 아래와 같은 명령어를 사용할 수 있다. 파일명은 옵션이다. cvs status 파일명 또한 지역 복사본에서 파일이 변경되었을 경우 변경된 이유를 잊어버렸다면 cvs diff 명령으로 파일의 저장소 버전과 지역 복사본 사이의 차이점을 볼 수 있다. cvs diff 파일명 cvs diff --side-by-side 파일명 cvs diff -rHEAD 파일명 설명 : -rHEAD는 선택사항으로 CVS에게 우리가 지정한 파일의 지역 복사본을 비교하고 싶은 대상이 저장소에서 있는 가장 최신 개정판(브랜치의 머리부분)이라고 말해준다. CVS는 지역복사본을 비교할 때 지역 복사본의 개정판 번호와 동일한 번호의 저장소 개정판과 비교한다. 예를들어, 지역 복사본의 개정판 번호가 1.1이고 저장소의 개정판 번호가 1.2(체크아웃하고나서 다른 누군가에 의해 업데이트 된 경우)인 경우, -rHEAD 선택 사항이 없으면 CVS는 저장소의 최신 버전이 1.2 개정판이라고 하더라도 1.1 개정판과 비교한다. 이걸 막기 위해서 -rHEAD를 사용해야 한다. 이제 저장소에 파일을 갱신하는 명령을 알아보자. cvs commit 명령을 사용한다. cvs commit -m "고객이 4가지 색을 더 원함" commit 기능은 우리가 고친 모든 변경 내용을 저장소에 저장하기 위해 사용되는 명령이다. 특정 파일의 역사를 알고자 할 경우 log 명령어를 사용한다. cvs log 파일명 cvs log -r1.5 파일명 충돌 해결 두 사람이 동시에 동일한 파일을 고치면 어떻게 되는가? 여기에는 두 가지 상황이 존재한다.첫째는 변경한 위치가 서로 겹치지 않는 상황이다. 이 경우에 나중에 갱신하고자 하는 사용자는 아래와 같은 메시지를 보게 된다(충돌 파일 : Number.txt). work/sesame> cvs commit -m "Zero를 강조할 필요가 있음" cvs commit: Examining cvs commit: Up-to-date check failed for 'Number.txt' cvs [commit aborted]: correct above errors first! Number.txt 파일의 지역 복사본을 저장소에 있는 최신 버전으로 갱신하라는 거다. 갱신하여 보자. work/sesame> cvs update cvs update: Updating RCS file: /Users/dave/sandbox/sesame/Number.txt,v retrieving revision 1.2 retrieving revision 1.3 Merging differences between 1.2 and 1.3 into Number.txt M Number.txt 추가 메시지에 주목하라. CVS는 단순히 저장소 버전으로 우리 지역 파일을 갱신하는 대신, 저장소 버전과 우리가 변경한 내용을 병합했다고 말한다. 파일을 살펴보면 우리가 고친 변경 내용과 먼저 갱신한 사용자가 고친 내용을 모두 포함함을 볼 수 있다. 이제 우리가 고친 내용을 저장소에 갱신하면 된다. cvs commit -m "Zero를 강조할 필요가 있음" 두 번째는 변경한 위치가 서로 겹치는 경우이다(같은 줄을 수정한 경우). 이 경우에 나중에 갱신하고자 하는 사용자는 아래와 같은 메시지를 보게 된다(충돌 파일 : Number.txt). work/sesame> cvs commit -m "Zero를 강조할 필요가 있음" cvs commit: Examining cvs commit: Up-to-date check failed for 'Number.txt' cvs [commit aborted]: correct above errors first! 이미 본 적 있는 메시지다. 위에서처럼 저장소에 있는 변경 내용을 받기 위해 갱신을 해보자. work/sesame> cvs update cvs update: Updating RCS file: /Users/dave/sandbox/sesame/Number.txt,v retrieving revision 1.2 retrieving revision 1.3 Merging differences between 1.2 and 1.3 into Number.txt rcsmerge : warning: conflicts during merge cvs update: conflicts found in Number.txt C Number.txt CVS가 저장소의 개정판과 지역 변경 내용을 병합하는 도중에 충돌을 발견했다고 보고한다. 대개 충돌은 두 개발자 사이에 일종의 오해가 있을 때 일어난다. CVS는 무엇이 충돌했는지 보여주기 위해 파일에 특별한 표시를 남긴다. 지금 경우 Number.txt를 보면, 다음처럼 되어있다. Number.txt 파일 : ZERO <<<<<<< Number.txt ichi ======= >>>>>>> 1.3 two three four five SIX <<<<<<<와 >>>>>>> 사이 줄이 충돌이 일어난 장소다. 이 두 기호 사이에 우리의 변경 내용뿐만 아니라 그것돠 충돌한 저장소의 변경 내용도 함께 나온다. 이런 경우 해당 파일에 대한 로그를 확인해 볼 수 있다. 충돌을 해결한 경우 Number.txt를 고쳐서 CVS의 충돌 표시 기호를 제거하고 고객이 요청한 대로 파일을 변경한다. 충돌 표시 기호를 제거했으니, 이제 이 파일을 전송할 수 있다. cvs commit -m "one은 일본어로, two는 이탈리아어로" CVS 실용주의실용주의 프로그래머CVS