SVN 기본 용어와 명령어

SVN 저장소, 리비전, trunk, branches, tags의 의미와 import, checkout, commit, update, diff, blame, lock 같은 기본 명령어 사용법을 정리한다.

SVN(Subversion)은 중앙 저장소를 기준으로 소스 코드와 변경 이력을 관리하는 버전 관리 시스템이다. Git처럼 각 개발자가 전체 저장소 이력을 로컬에 복제하는 방식이 아니라, 중앙 저장소(repository)에 있는 프로젝트를 작업 사본(working copy)으로 받아와 수정하고 다시 커밋하는 흐름으로 사용한다.

이 문서는 SVN을 처음 사용하는 개발자가 자주 마주치는 용어와 기본 명령어를 실무 흐름에 맞춰 정리한다.

기본 용어

Repository

Repository는 프로젝트의 파일과 변경 이력을 저장하는 중앙 저장소이다. 소스 코드뿐 아니라 파일 추가, 수정, 삭제, 디렉토리 구조 변경 같은 이력도 함께 저장된다.

SVN 저장소는 보통 서버에 두고 여러 개발자가 네트워크를 통해 접근한다. 접근 방식은 환경에 따라 http://, https://, svn://, svn+ssh://, file:// 등을 사용할 수 있다.

https://svn.example.com/repos/sample
svn+ssh://svn.example.com/svn/sample
file:///var/svn/sample

운영 환경에서는 프로젝트 단위로 저장소를 분리하거나, 하나의 저장소 안에 여러 프로젝트 디렉토리를 두는 방식 중 하나를 선택한다. 권한 관리, 백업 범위, 배포 단위가 다르면 저장소를 분리하는 편이 관리하기 쉽다.

Revision

Revision은 저장소의 변경 번호이다. SVN에서는 파일마다 따로 리비전 번호가 증가하는 것이 아니라, 저장소에 커밋이 한 번 발생할 때마다 저장소 전체의 리비전 번호가 증가한다.

예를 들어 sample.c 한 파일만 수정해서 커밋해도 저장소 리비전은 r10에서 r11로 증가한다. 이 리비전 번호를 이용하면 특정 시점의 소스를 다시 받거나, 변경 이력을 비교할 수 있다.

svn log -r 10:20
svn diff -r 10:11 sample.c
svn checkout -r 10 https://svn.example.com/repos/sample/trunk sample-r10

trunk

trunk는 프로젝트의 중심 개발 라인이다. 일반적으로 일상적인 개발 작업은 trunk에서 진행한다.

sample/
  trunk/
    src/
    pom.xml

팀 규칙에 따라 다르지만, trunk는 항상 빌드 가능한 상태를 유지하는 것이 좋다. 큰 기능 개발이나 장기간 수정은 branches로 분리한 뒤 안정화되면 trunk로 병합한다.

branches

branchestrunk에서 분기한 개발 라인이다. 릴리스 유지보수, 대규모 기능 개발, 실험성 작업처럼 trunk와 분리해서 작업해야 할 때 사용한다.

sample/
  branches/
    feature-payment/
    release-1.0/

브랜치는 디렉토리 복사처럼 보이지만 SVN 내부에서는 변경 이력을 보존하는 복사로 관리된다. 브랜치를 만들 때는 일반 파일 복사가 아니라 svn copy를 사용해야 한다.

svn copy https://svn.example.com/repos/sample/trunk \
  https://svn.example.com/repos/sample/branches/feature-payment \
  -m "Create feature-payment branch"

tags

tags는 릴리스 시점의 소스를 보관하는 디렉토리이다. 예를 들어 0.1, 0.2, 1.0 같은 릴리스 버전을 태그로 남긴다.

sample/
  tags/
    0.1/
    0.2/
    1.0/

SVN의 태그도 기술적으로는 저장소 안의 복사본이다. 따라서 권한이나 팀 규칙으로 태그 디렉토리를 수정하지 못하게 막아두는 것이 좋다. 태그는 배포 시점의 스냅샷으로 취급하고, 태그 아래에서 직접 개발하지 않는다.

svn copy https://svn.example.com/repos/sample/trunk \
  https://svn.example.com/repos/sample/tags/1.0 \
  -m "Tag release 1.0"

기본 작업 흐름

SVN의 기본 작업은 저장소에서 소스를 받고, 로컬에서 수정한 뒤, 변경 내용을 확인하고, 중앙 저장소에 커밋하는 순서로 진행한다.

svn checkout svn+ssh://svn.example.com/svn/sample/trunk sample
cd sample

svn update
svn status
svn diff

svn add new-file.c
svn commit -m "Add new file"

커밋 전에는 항상 svn update로 최신 변경을 반영하고, svn statussvn diff로 내가 올릴 변경을 확인하는 습관을 들이는 것이 좋다.

주요 명령어

import

svn import는 아직 SVN으로 관리되지 않는 로컬 디렉토리를 저장소에 처음 등록할 때 사용한다. 프로젝트를 최초로 저장소에 올릴 때 주로 사용하며, 이미 체크아웃된 작업 사본에서 일상적으로 사용하는 명령은 아니다.

svn import sampledir svn+ssh://svn.example.com/svn/sample/trunk \
  -m "Initial import"

import는 로컬 디렉토리를 저장소에 올리지만, 그 로컬 디렉토리를 자동으로 작업 사본으로 바꾸지는 않는다. 최초 등록 후 실제 개발은 다시 checkout을 받아서 진행하는 것이 안전하다.

svn checkout svn+ssh://svn.example.com/svn/sample/trunk sample

checkout

svn checkout은 저장소의 파일을 로컬 작업 사본으로 받아오는 명령어이다. 작업 사본에는 실제 소스 파일과 함께 SVN이 변경 상태를 추적하기 위한 메타데이터가 포함된다.

svn checkout svn+ssh://svn.example.com/svn/sample/trunk sample

짧게 co 별칭을 사용할 수도 있다.

svn co svn+ssh://svn.example.com/svn/sample/trunk sample

체크아웃한 디렉토리 안에서는 일반 파일처럼 코드를 수정할 수 있다. 다만 파일 추가, 삭제, 이동, 이름 변경은 가능한 한 SVN 명령어로 처리해야 저장소 이력이 정확히 남는다.

export

svn export는 버전 관리 메타데이터 없이 순수한 파일만 가져올 때 사용한다. 배포용 소스 묶음이나 임시 확인용 디렉토리가 필요할 때 유용하다.

svn export svn+ssh://svn.example.com/svn/sample/trunk sample-release

특정 리비전의 파일만 가져올 수도 있다.

svn export -r 120 svn+ssh://svn.example.com/svn/sample/trunk sample-r120

export로 받은 디렉토리는 작업 사본이 아니므로 svn update, svn commit, svn status 같은 작업을 할 수 없다. 개발을 계속할 디렉토리가 필요하면 export가 아니라 checkout을 사용한다.

update

svn update는 저장소의 최신 변경 사항을 현재 작업 사본에 반영한다. 여러 사람이 같은 저장소를 사용하는 환경에서는 수정 전과 커밋 전에 실행하는 것이 좋다.

svn update

짧게 up 별칭을 사용할 수 있다.

svn up

특정 리비전으로 작업 사본을 맞출 때는 -r 옵션을 사용한다.

svn update -r 120

업데이트 중 같은 파일의 같은 부분을 여러 사람이 수정했다면 충돌이 발생할 수 있다. 충돌이 발생하면 파일 내용을 확인해 직접 정리하고, 해결 상태를 SVN에 알려준 뒤 커밋한다.

svn status
svn resolve --accept=working sample.c
svn commit -m "Resolve conflict in sample.c"

status

svn status는 작업 사본에서 변경된 파일 상태를 확인한다. 커밋하기 전에 반드시 확인해야 하는 명령어이다.

svn status

자주 보는 상태 문자는 다음과 같다.

상태 의미
M 수정됨
A 추가 예약됨
D 삭제 예약됨
? SVN이 추적하지 않는 파일
! SVN이 추적하지만 로컬에서 사라진 파일
C 충돌 발생

예시는 다음과 같다.

$ svn status
M       src/main.c
A       src/new-file.c
?       build.log

?로 표시되는 파일은 커밋되지 않는다. 새 파일을 저장소에 올리려면 svn add를 먼저 실행해야 한다.

add

svn add는 새 파일이나 디렉토리를 버전 관리 대상으로 등록한다. 등록만 한다고 저장소에 바로 올라가는 것은 아니며, 이후 commit을 해야 저장소에 반영된다.

svn add sample.c
svn commit sample.c -m "Add sample.c"

디렉토리를 추가하면 내부 파일도 함께 추가 대상으로 잡힐 수 있으므로, 커밋 전 svn status로 불필요한 파일이 포함되지 않았는지 확인한다. 빌드 산출물, 로그, IDE 설정 파일처럼 저장소에 올리지 않을 파일은 svn:ignore 속성이나 클라이언트 ignore 설정으로 제외한다.

commit

svn commit은 작업 사본의 변경 사항을 저장소에 반영한다. 커밋이 성공하면 저장소 리비전이 증가한다.

svn commit -m "Fix login validation"

짧게 ci 별칭을 사용할 수 있다.

svn ci -m "Fix login validation"

특정 파일만 커밋할 수도 있다.

svn commit src/login.c -m "Fix login validation"

커밋 메시지는 변경 이유를 알 수 있게 작성한다. fix, update, test처럼 의미가 부족한 메시지만 남기면 나중에 svn log로 이력을 추적하기 어렵다.

커밋 전 점검 흐름은 다음과 같다.

svn update
svn status
svn diff
svn commit -m "Fix login validation"

log

svn log는 저장소의 변경 이력을 확인한다.

svn log

변경 파일 목록까지 보고 싶으면 -v 옵션을 사용한다.

svn log -v

특정 리비전 범위나 파일에 대한 로그만 볼 수도 있다.

svn log -r 30:100 sample.c

원격 저장소 URL을 직접 지정하면 작업 사본이 없는 환경에서도 이력을 확인할 수 있다.

svn log https://svn.example.com/repos/sample/trunk

diff

svn diff는 작업 사본의 변경 내용을 비교한다. 기본 실행 시 로컬 변경과 기준 리비전의 차이를 보여준다.

svn diff

특정 파일만 비교할 수 있다.

svn diff sample.c

특정 리비전과 비교할 때는 -r 또는 --revision 옵션을 사용한다.

svn diff -r 4 sample.c
svn diff -r 10:20 sample.c

커밋 전에는 svn status로 변경 파일 목록을 확인하고, svn diff로 실제 코드 변경을 검토한다. 이 과정에서 디버그 코드, 임시 로그, 불필요한 포맷 변경이 섞여 있는지 확인할 수 있다.

blame

svn blame은 파일의 각 라인이 어떤 리비전에서 누구에 의해 변경되었는지 보여준다. 문제가 생긴 코드의 변경 배경을 찾을 때 유용하다.

svn blame sample.c

일부 SVN 클라이언트에서는 praise, annotate, ann 별칭도 사용할 수 있다. 단, blame 결과만 보고 작성자를 비난하기보다는 당시 로그와 관련 변경 파일을 함께 확인해야 한다.

svn log -r 120 -v
svn diff -c 120 sample.c

lock과 unlock

svn lock은 특정 파일을 잠가 다른 사용자가 수정하지 못하게 할 때 사용한다. 주로 병합이 어려운 바이너리 파일, 디자인 파일, 문서 파일에 사용한다.

svn lock sample.docx -m "Edit release note"

잠금을 해제할 때는 svn unlock을 사용한다.

svn unlock sample.docx

텍스트 소스 코드는 일반적으로 잠금보다 병합을 전제로 작업하는 편이 효율적이다. 모든 파일을 습관적으로 잠그면 협업 속도가 떨어질 수 있으므로, 잠금이 필요한 파일 유형을 팀 규칙으로 정해두는 것이 좋다.

delete, move, copy

SVN에서 파일 삭제, 이동, 복사는 일반 파일 시스템 명령어보다 SVN 명령어를 사용하는 편이 안전하다. 그래야 저장소 이력이 올바르게 남는다.

svn delete old-file.c
svn move old-name.c new-name.c
svn copy trunk branches/feature-a

삭제나 이동도 즉시 저장소에 반영되는 것은 아니다. 작업 사본에 변경 예약 상태로 남고, commit을 해야 최종 반영된다.

svn status
svn commit -m "Rename old-name.c to new-name.c"

파일 백업과 복구

저장소 전체를 백업하거나 다른 서버로 이전할 때는 svnadmin dumpsvnadmin load를 사용한다. 이 명령은 일반 작업 사본에서 실행하는 svn 명령이 아니라, 서버의 저장소 디렉토리를 직접 다루는 관리자 명령이다.

svnadmin dump /var/svn/sample > sample.dump

덤프 파일을 새 저장소로 복원할 때는 먼저 저장소를 만들고 load를 실행한다.

svnadmin create /var/svn/sample-new
svnadmin load /var/svn/sample-new < sample.dump

운영 저장소를 백업할 때는 다음 사항을 함께 확인한다.

  • 저장소 경로와 프로젝트 URL을 혼동하지 않는다.
  • 백업 파일을 다른 서버나 스토리지에 보관한다.
  • 복구 테스트를 정기적으로 수행한다.
  • 저장소 hook, 접근 권한 설정, Apache 또는 svnserve 설정도 함께 백업한다.

자주 쓰는 작업 예시

새 파일을 만든 경우

새 파일은 SVN이 자동으로 추적하지 않는다. 파일을 추가한 뒤 커밋해야 저장소에 올라간다.

touch sample.c
svn status
svn add sample.c
svn commit sample.c -m "Add sample.c"

기존 파일을 수정한 경우

이미 SVN이 추적 중인 파일은 수정 후 바로 상태와 차이를 확인하고 커밋할 수 있다.

svn update
vi sample.c
svn status
svn diff sample.c
svn commit sample.c -m "Fix sample calculation"

저장소의 최신 내용을 반영하는 경우

다른 개발자의 변경을 가져올 때는 svn update를 사용한다.

svn update

충돌이 발생하면 충돌 파일을 정리한 뒤 해결 상태로 표시하고 커밋한다.

svn status
vi sample.c
svn resolve --accept=working sample.c
svn commit sample.c -m "Resolve merge conflict"

실무 주의사항

커밋 전에는 update, status, diff를 확인한다

SVN은 중앙 저장소를 기준으로 협업하므로 커밋 전 최신 변경을 반영하는 습관이 중요하다. svn update 없이 오래된 작업 사본에서 커밋하면 충돌을 늦게 발견하거나, 다른 변경의 맥락을 놓칠 수 있다.

svn update
svn status
svn diff

파일 작업은 SVN 명령어로 처리한다

파일 추가는 svn add, 삭제는 svn delete, 이동과 이름 변경은 svn move를 사용한다. 운영체제의 rm, mv, 파일 탐색기만 사용하면 SVN이 의도한 변경을 정확히 추적하지 못할 수 있다.

trunk, branches, tags 규칙을 팀에서 정한다

SVN은 디렉토리 구조를 강제하지 않는다. trunk, branches, tags는 널리 쓰이는 관례이지만, 실제 운영 방식은 팀 규칙으로 정해야 한다.

일반적인 규칙은 다음과 같다.

  • trunk는 항상 빌드 가능한 상태로 유지한다.
  • 장기 기능 개발은 branches에서 진행한다.
  • 배포된 소스는 tags에 남기고 수정하지 않는다.
  • 태그 생성과 릴리스 브랜치 생성 권한을 제한한다.

바이너리 파일은 잠금 정책을 정한다

이미지, 문서, 디자인 파일처럼 자동 병합이 어려운 파일은 svn lock을 사용하는 편이 안전할 수 있다. 반대로 텍스트 소스 코드에 잠금을 남발하면 병렬 개발이 어려워진다. 파일 유형별로 잠금 사용 여부를 정하고, 잠금 메시지를 남기는 규칙을 둔다.

참고