개요

매번 소스코드를 압축하고 뒤에다가 날자를 붙이는 번거로운 작업을 하면서 프로젝트를 진행 할 수는 없다.

현재 많이 쓰이고 있는 CVS나 SVN과 다른 분산 버전 관리 시스템인 Git(깃)을 소개한다.

Git vs CVS, SVN

cvs와 svn의 경우 저장소(repository)가 원격 서버에 있는 방식이다. 즉, 중앙에 집중적인 버전 관리 시스템을 가지고 있는 것이다.

이러한 중앙 집중식 저장소는 컴퓨터에 직접 접근해야 하는 문제는 개선 했지만 여전히 한계가 있다. 먼저, 사용자는 최신 버전의 코드만 가지고 있다. 변경 이력을 보려면 저장소에 정보를 요청해야 하는 것이다. 즉, 반드시 네트워크상에서 원격 저장소로 접근해야 한다는 점이 문제인 것이다.

하지만 Git(깃)이 따르는 모델인 분산 버전 관리 시스템에서는 모든 팀원이 변경 사항을 전송하는 중앙 저장소를 가지는 대신, 각자가 프로젝트의 전체 이력이 있는 자신만의 저장소를 가진다. 커밋할 때는 원격 저장소에 연결할 필요가 없이 변경 사항을 지역 저장소에 기록한다.

은행으로 비유하자면, 중앙 집중식 시스템이 팀 구성원 모두가 사용하는 하나의 은행을 가지는 것이라면, 분산 시스템은 각 개발자가 자신만의 개인 은행을 가지는 것과 같다.

이쯤 되면 다른 사람들이 작업한 변경 사항을 어떻게 동기화 해야 할지, 그리고 내가 수정한 변경 사항을 다른 사람들이 제대로 가지고 있을지 궁금할 법하다. 각 개발자는 여전히 중앙 프로젝트 저장소에 변경 사항을 전송 한다. 개발자가 직접 변경 사항을 중앙 저장소에 전송 하려고 접속할 수 있다. 이런 행동을 Git에서는 푸싱(Pushing)이라고 한다. 또는, 변경 사항을 모아둔 패치를 만들어 프로젝트를 관리하는 사람에게 제출해서 중앙 저장소에 반영하게 할 수 있다.

Git의 작업 트리

사용자가 모든 변경 작업을 수행하는 위치는 작업트리이다.

작업 트리란 결국 저장소를 바라보는 자신의 현재 시점이다. 작업 트리는 소스코드, 빌드 파일, 단위 테스트 등 프로젝트의 모든 파일을 가지고 있는 것이다.

몇몇 버전 관리 시스템에서는 작업트리를 작업 복사본(working copy)이라고 한다. 다른 버전 관리 시스템을 사용하다가 Git을 처음 사용하는 사람들은 저장소와 작업 트리를 구분하는 데 혼란을 겪는다. Subversion(SVN)과 같은 버전 관리 시스템에서 저장소는 '저 너머'의 다른 서버에 존재한다.

하지만 Git 에서 '저 너머'란 로컬 컴퓨터의 프로젝트 디렉터리에 있는 .git/ 디렉터리를 의미한다. 즉 저장소의 이력을 보고 변경 사항을 살펴보려고 다른 서버에 있는 저장소와 통신하지 않아도 된다.

그럼 이 작업 트리를 처음 가져오려면 어떻게 해야 할까? Git에게 자신의 프로젝트를 저장소에 초기화 하도록 요청하거나, 기존 저장소의 프로젝트를 복제(clone)할 수 있다.

복제하기는 지역 저장소(현재 본인의 로컬 컴퓨터)를 만든 후 개발의 기본 흐름인 마스터 브랜치에서 복사본을 체크아웃(check out)한다. Check out은 git이 사용자의 작업 트리를 저장소의 특정 시점과 일치하도록 변경하는 작업이다.

결국 버전관리 시스템은 결국 이 변경사항을 어떻게 추적하는지가 전부이다. 이제 본격적으로 변경사항을 어떻게 추적하는지에 대해서 설명 하도록 하겠다.

파일 다루고 동기화 하기

버전 관리 시스템을 사용하는 주된 이유는 변경을 계속해서 추적하기 위함이다. 개발자는 소스 코드를 변경하고, 잘못된 영향(side effect)을 끼치지는 않았는지 단위 테스트를 수행한 다음 commit한다.

pulling : 다른 개발자가 변경사항을 푸싱 한것을 가져오는 작업을 말한다. CVS, SVN에서의 update와 유사한 기능을 한다.

□ 프로젝트, 디렉터리, 파일 추적하기

여기서는 저장 대상을 구조화하는 방법에 대해서 이야기 해보자.

Git는 제일 원초적인 방법으로 저장한 파일의 내용을 분석해서 그 변경이력을 추적하게 된다.

여타 다른 버전관리 시스템에서는 파일 자체를 추적하는데, git은 내용을 추적하기 때문에 다르다고 할 수 있다.

즉 models.py 파일을 추적하는 대신 models.py에 있는 변수나 함수 등을 구성하는 각 문자와 줄을 추적하며 이름, 파일 모드(file mode), 심볼릭 파일 여부와 같은 메타 데이터(metadata)를 models.py에 추가 한다. 이것은 미묘한 차이점이지만 중요하다.

이러한 점은 기술적으로도 큰 이점이다. 저장소의 전체 이력을 저장하는데 필요한 공간이 줄어든다. 그리고 두 파일 사이에서 함수나 클래스의 이동을 알아내거나 어디에서 복사된 코드인지 결정하는 것과 같은 작업이 가능해졌으며 빠르다.

결국 , 프로젝트란 저장소에 파일과 디렉터리를 조직화한 형태에 불과하다.

□ 태그를 이용해 마일스톤 추적하기

프로젝트를 진행함에 따라 마일스톤(milestone)에 도달하게 된다. 애자일 방법론을 따른다면 매주마다 새로운 기능을 추가하는 주간 개발 주기를 따를 태고, 수 개월에 한 번씩 업데이트를 릴리스하는 기존의 방법론 중 하나를 따를 수도 있다.

둘 중 어느 경우라도 특정 마일스톤을 달성했을 때의 저장소 상태를 추적할 수 있어야 한다. 태그(tag)를 이용해 이러한 상태를 추적한다. 태그에 저장소 이력의 특정 위치를 기록해두면 나중에 편리하게 참조할 수 있다.

태그는 저장소 이력의 특정 시점을 기록하는 용도로 사용하는 이름일 뿐이며, 공개 릴리스 같은 주요 마일스톤이나 버그 수정 같이 사소한 것들도 표현할 수 있다.

쉽게 기억할 수 있는 이름을 특정 리비전의 태그에 부여함으로써 저장소의 이력을 계속해서 추적할 수 있다.

□ 브랜치로 분기 이력 만들기

만약 달력 프로그램을 다시 작성하려면 팀원의 동의를 구해야 하며 변경 사항을 추적해야 한다. 코드 전체를 다른 디렉터리에 복사해두고 바꾸기 시작해도 되지만, 이렇게 변경하면 내역을 추적할 방법도 없고, 더 중요하게는 이것 저것 실험하면서 했던 실수를 되돌릴 수도 없다는 문제가 생긴다.

이런 때 브랜치가 필요하다. 브랜치를 생성하려면 파일이 분기하는 위치가 저장소에 기록된다. 브랜치는 다른 브랜치와 분리하여 내용에 대한 변경 사항을 지속적으로 추적한다. 그래서 분기 이력을 생성할 수 있다.

마스터 브랜치(master branch)는 개발의 기본 줄기이다. // 다른 버전 관리 시스템(svn)에서는 트렁크(trunk)라고 부리기도 한다.

브랜치를 생성하면 기본 줄기에서 분기된다.

브랜치는 계속 남겨둘수도 있고, 바로 지워 버릴 수도 있다.

또한 Git에서는 브랜치도 다른 모든 것과 마찬가지로 다른 사람과 공유하지 않고 지역적으로 생성할 수 있다. 지역 브랜치를 생성하고 공유하지 않는 데에도 나름의 이점이 있다. 개인적으로 몇 가지를 실험해보고, 만족스러운 수준이 된후에 공유할 수 있다. 실험한 내용이 제대로 동작하지 않으면 그냥 조용히 지워버리면 된다.

Git을 이용하면서 브랜치와 태그는 중요한 부분이기에 추후에 따로 자세히 설명 하도록 하겠다.

대부분의 브랜치는 초신의 내용을 유지할 수 있도록 다른 브랜치에 합쳐야 한다.

□ 합치기(Merge)

합치기를 이용해 여러 브랜치의 이력을 하나로 합친다. Git은 사용자가 직접하는 방식과 동일하게 이력을 합친다. Git은 두 브랜치의 변경 사항을 놓고 어디서 변경이 발생했는지 비교한다.

파일이 서로 다른 영역이 변경되었다면 Git이 알아서 합친다. 때로는 Git이 합칠 수 없는 상황이 발생 한다. 이때는 오류가 있음을 기록하고 사용자에게 충돌이 있음을 알린다.

즉, 같은 팀의 개발자가 코드에서 같은 위치를 서로 다르게 수정했다고 하자. Git은 이러한 상황을 보고 둘 중에 어느 것이 올바른지 결정할 수 없기 때문에 충돌로 표시하고, 사용자가 어떤 변경이 올바른지 알려주기를 기다린다.

Git의 막강한 기능은 바로 merge의 변경 이력 추적에 있다. merge 추적을 통해 이러한 작업을 처리하기 위해서는 손수 해야하는 불편함이 과거 버전관리 시스템에는 있었다. 하지만 Git의 경우 함께 합쳐진 커밋 내역을 주적하고, 이미 합쳐진 내용을 다시 합치지 않게 한다.

이게 가능하기 때문에 더 이상 브랜치 사용을 꺼려하지 않아도 되는 것이다.

(단, subversion 1.5.0부터는 merge trace 기능을 support한다.)

□ 잠금 옵션

도서관의 책을 빌리는것과 같은 것이다. 한번에 한사람만 책을 빌릴 수 있는 것이다. 이를 버전 관리에 적용할 경우

즉 한번에 한 사람만이 소스 코드를 수정 할 수 있다는 말이다.

strict locking : 특정 코드의 복사본은 한 번에 한 사람만 가질 수 있다. 이는 함께 일하는 팀원 간에는 그다지 효율적인 방식이 아닐 뿐더러 사로 느슨하게 연결된 분산 버전 관리 시스템 모델과도 어울리지 않는다.

optimistic locking : 대부분의 경우 서로 변경한 영역에 충돌이 없으리라고 가정하므로 다수의 개발자가 같은 파일에 있는 동일한 코드를 이용하는 것을 허용한다.

시나리오)

개발자 A와 B가 동시에 같은 파일을 복사본을 만들어서 수정한다. 이때 optimistic locking이기 때문에 동시에 2명이 체크아웃하는것을 허용한다.

그리고 개발자 A가 자신의 변경사항을 푸슁한다. 그리고 개발자 B가 푸슁하려고한다. 그러면 Git은 거부한다.

그러면 개발자 B는 A가 푸슁한 이력을 폴링해서 자신의 저장소에 반영해야한다. 그다음 다시 푸슁하면 성공한다.

복잡하지만 생각해보면 간단한 이력 업데이트 방식이다. 또한 한팀에서 두명이 동시에 같은 파일을 편집하는 경우도 흔치 않을 것이다.

지금 까지 거시적 관점에서 Git 즉, 분산 버전 관리 시스템의 구성을 보았다. 이제 다음 포스트에서 좀 더 상세한 Git의 사용법에 대해서 다루도록 하겠다.

+ Recent posts