'Part 1: 시작'에 해당되는 글 1건

  1. 2010.11.26 [펌] Subversion 사용자를 위한 Git, Part 1: 시작
98..Etc/Git2010. 11. 26. 16:34
반응형

무료 오픈 소스 VCS(Version Control System)에 익숙하지 않은 개발자의 경우 과거에 유명했던 CVS(Concurrent Versions System) 대신 Subversion을 비상업용 표준 VCS로 사용하고 있다. CVS는 제한적이기는 하지만 지금도 유용하게 사용되고 있다. 하지만 Subversion에는 웹 서버에서 간단한 설치 작업만 수행하면 된다는 매력적인 장점이 있다. Subversion에도 약간의 문제가 있기는 하지만 대부분의 경우 정상적으로 작동한다. 이 기사에서는 이러한 문제에 대해서도 설명한다.

그렇다면 또 다른 도구가 필요한 이유가 무엇일까? Git(대문자 "G", git는 명령행 도구임)는 많은 면에서 Subversion보다 좋게 설계되었으며 많은 분산 VCS 중 하나이다. 분산 VCS 중 필자가 처음으로 경험한 제품은 Arch/tla였으며 Mercurial, Bazaar, darcs 등도 사용해 보았다. 여러 가지 이유로(이에 대해서는 관련된 본문에서 설명한다.) Git는 많은 인기를 얻고 있으며 Subversion과 함께 개인 또는 기업 VCS로서 가장 많이 사용되고 있는 제품으로 인정 받고 있다.

Subversion 사용자가 Git에 관심을 보이는 두 가지 중요한 이유는 다음과 같다.

  • Subversion의 일부 기능이 제한적이기 때문에 Git로 이동하려고 한다.
  • Git에 대한 호기심이 있어서 Subversion과 비교해 보고 싶다.

아마도 Git가 비교적 새롭게 떠오르는 기술이기 때문에 이력서에 넣고 싶다는 세 번째 이유도 있을 것이다. 이 이유가 주 목적이 아니길 바란다. Git를 배우는 것은 개발자가 가장 큰 보람을 느낄 수 있는 일 중 하나이다. 지금 당장 Git를 사용하지 않는다고 하더라도 이 분산 VCS에 담겨 있는 개념과 워크플로우는 향후 10년 이내에 IT 산업의 대부분의 분야에서 겪게 될 지리적인 대규모 분산 환경에서의 거대한 변화에 대응하기 위한 필수 지식이 될 것이다.

중요한 이유가 아닐 수도 있겠지만 마지막으로 들 수 있는 이유는 다음과 같다. Linux 커널 개발자가 아닐 경우 Git를 사용하여 커널 및 기타 수많은 주요 프로젝트를 관리하고 있기 때문에 기여도를 높이기 위해 Git에 익숙해지고 싶다는 것이다.

이 기사는 입문 수준에서 중급 수준 사이의 Subversion 사용자를 위한 것이다. 따라서 Subversion에 대한 입문자 수준의 지식과 버전 제어 시스템에 대한 일반적인 지식이 있어야 한다. 이 기사의 정보는 주로 UNIX® 계열(Linux® 및 Mac OS X) 시스템의 사용자를 대상으로 하며 Windows® 사용자를 고려한 부분도 조금 있다.

이 시리즈의 Part 2에서는 Git의 고급 사용 방법 즉, 분기 병합, 차이 생성 등을 비롯한 일반적인 작업에 대해 설명한다.

Subversion 및 Git 기초

앞으로는 편의를 위해 "Subversion"을 "SVN"으로만 쓰겠다.

git-svn

git-svn을 들어본 적이 있을 것이다. 이 도구를 사용하면 Git에서 Subversion 저장소를 사용할 수 있다. 유용한 경우도 있기는 하지만 절반만 분산된 중앙 집중식 VCS를 사용하는 것은 분산 VCS로 전환하는 것과는 다른 방법이다.

그렇다면 SVN의 장점은 무엇일까? 이미 알고 있을지도 모르지만 VCS는 파일에 대한 것이 아니라 바로 변경 사항에 대한 것이다. 중앙 서버에서 실행되는 SVN은 변경 사항을 데이터 저장소에 추가한 후 변경 작업이 발생할 때마다 사용자에게 스냅샷을 제공할 수 있으며 이 스냅샷에는 개정 번호가 있다. 그리고 이 개정 번호는 SVN과 사용자 모두에게 매우 중요한 요소이다. 변경 사항이 차례로 적용되기 때문에 개정 번호를 통해 최신 변경 사항을 확인할 수 있다.

Git에도 변경 사항 추적이라는 비슷한 목표가 있다. 그러나 중앙 집중식 서버를 사용하지는 않는다. 이 점이 매우 중요한 차이점이다. 즉, SVN은 중앙 집중식인 반면 Git는 분산식이다. 따라서 Git에는 "최신 개정"이 없기 때문에 증가하는 개정 번호를 제공하는 방법도 없다. 물론 고유 개정 ID가 있기는 하지만 SVN 개정 번호처럼 유용하지는 않다.

Git에서 중요한 작업은 더 이상 커미트가 아니다. 여기에서 중요한 작업은 바로 병합이다. 누구나 저장소를 복제한 후 복제본에 대해 커미트를 수행할 수 있다. 그리고 저장소의 소유자는 병합된 변경 사항을 되돌릴 수 있다. 또는 개발자가 변경 사항을 저장소에 다시 push할 수 있다. 여기에서는 마지막 방법인 authorized-push 모델에 대해서만 설명한다.


SVN에서 디렉토리 관리하기

먼저 SVN을 사용하여 디렉토리의 컨텐츠를 추적하는 일반적인 간단한 예제부터 살펴보자. SVN 서버, 파일이 있는 디렉토리 그리고 해당 서버에서 적어도 하나의 경로에 대한 커미트 권한이 있는 계정이 있어야 한다. 먼저 다음과 같이 디렉토리를 추가하고 커미트하자.


Listing 1. SVN 아래에 디렉토리 설정하기
% svn co http://svnserver/...some path here.../top
% cd top
% cp -r ~/my_directory .
% svn add my_directory
% svn commit -m 'added directory'

이렇게 디렉토리를 설정한 후에는 이 디렉토리에서 커미트된 파일의 최신 버전을 확인하고, 파일을 삭제하고, 파일의 이름을 바꾸고, 새 파일 또는 디렉토리를 작성하고, 기존 파일에 변경 사항을 커미트하는 등의 작업을 수행할 수 있다.


Listing 2. SVN에서의 기본적인 파일 작업
# get latest
% svn up
# what's the status?
% svn st
# delete files
% svn delete
# rename files (really a delete + add that keeps history)
% svn rename
# make directory
% svn mkdir
# add file
% svn add
# commit changes (everything above, plus any content changes)
% svn commit

이러한 명령에 대해서 자세히 설명하지는 않겠지만 잘 알고 있어야 한다. svn help COMMAND를 입력하면 이들 명령에 대한 설명을 볼 수 있으며 자세한 정보를 보려면 설명서를 참조해야 한다.


Git에서 디렉토리 관리하기

이 섹션도 SVN 예제와 같은 방식으로 설명한다. 앞에서와 마찬가지로 디렉토리에 이미 데이터가 있다고 가정한다.

여기에서는 무료 github.com 서비스를 원격 서버로 사용하지만 원한다면 사용자의 고유 서버를 설정할 수도 있다. GitHub를 사용하면 원격 Git 저장소를 쉽게 관리할 수 있다. 이 기사의 집필 당시를 기준으로 무료 계정의 경우 최대 300MB의 데이터를 저장할 수 있으며 저장소가 공용이어야 한다. 필자는 "tzz"라는 사용자로 등록한 후 "datatest"라는 공용 저장소를 작성하여 편하게 사용하고 있다. 그리고 공용 SSH 키를 제공했다. 이 키가 아직 없다면 새롭게 생성해야 한다. Gitorious 서버나 repo.or.cz를 사용할 수도 있으며 git.or.cz Wiki에서 Git 호스팅 서비스 목록을 볼 수 있다(참고자료의 링크 참조).

GitHub의 장점은 친숙하다는 것이다. Git를 설정하고 저장소를 초기화하는 데 필요한 명령을 정확하게 알려 준다. 이제 이 과정을 자세히 살펴보자.

먼저 각 플랫폼에 해당하는 Git를 설치하고 초기화해야 한다. Git 다운로드 페이지(참고자료 참조)에서 플랫폼에 따른 다양한 옵션을 볼 수 있다. (Mac OS X의 경우 필자는 port install git-core 명령을 사용했다. 하지만 MacPorts를 먼저 설정해야 한다. 또한 Git 다운로드 페이지에 연결된 독립 실행형 MacOS X Git 설치 프로그램을 사용하면 대부분의 경우 쉽게 설치할 수 있다.)

설치한 후에는 다음과 같은 명령을 사용하여 기본 설정을 수행해야 한다(고유 사용자 이름 및 이메일 주소 선택).


Listing 3. 기본적인 Git 설정
% git config --global user.name "Ted Zlatanov"
% git config --global user.email "tzz@bu.edu"

이미 SVN과의 차이점을 알았을 것이다. SVN에서는 사용자 ID가 서버측에 있으며 이 ID를 사용해야만 한다. 하지만 Git에서는 원한다면 The Wonderful Monkey Of Wittgenstein이 될 수도 있다. (다행히 필자는 유혹에 넘어가지 않았다.)

이제 데이터 파일을 설정한 후 이 파일을 사용하여 저장소를 초기화할 차례이다. (GitHub에서는 공용 SVN 저장소에서도 가져오므로 유용하게 사용할 수 있다.)


Listing 4. 디렉토리 설정 및 첫 번째 커미트
# grab some files
% cp -rp ~/.gdbinit gdbinit
% mkdir fortunes
% cp -rp ~/.fortunes.db fortunes/data.txt
# initialize
% git init
# "Initialized empty Git repository in /Users/tzz/datatest/.git/"
# add the file and the directory
% git add gdbinit fortunes
% git commit -m 'initializing'
#[master (root-commit) b238ddc] initializing
# 2 files changed, 2371 insertions(+), 0 deletions(-)
# create mode 100644 fortunes/data.txt
# create mode 100644 gdbinit

위 출력을 보면 파일 모드에 대한 정보를 볼 수 있다. 여기서 100644는 파일의 권한 비트를 나타내는 8진수 버전이다. 이에 대해서는 크게 신경 쓰지 않아도 되지만 2371 insertions의 의미가 궁금할 것이다. 두 개의 파일만 변경되었을 뿐이다. 이 숫자의 실제 의미는 삽입된 행의 번호이다. 그리고 아무 것도 삭제하지 않았다.

새로운 변경 사항을 GitHub 서버에 push하려면 어떻게 해야 할까? 설명서를 보면 "origin"이라는 원격 서버를 추가하는 방법을 알 수 있다. 이 경우 원격 서버에 원하는 이름을 지정할 수도 있다. 그리고 GIT 명령에 대해 알고 싶은 경우에는 예를 들어, git remote에 대한 정보가 필요한 경우에는 git remote --help 또는 git help remote를 입력하면 된다. 이 방법은 명령행 도구에서 일반적으로 사용되는 방법으로 SVN에서도 유사한 방법으로 필요한 정보를 얻을 수 있다.


Listing 5. 원격 서버에 변경 사항 push하기
# remember the remote repository is called "datatest"?
% git remote add origin git@github.com:tzz/datatest.git
# push the changes
% git push origin master
#Warning: Permanently added 'github.com,65.74.177.129' (RSA) to the list of known hosts.
#Counting objects: 5, done.
#Delta compression using 2 threads.
#Compressing objects: 100% (4/4), done.
#Writing objects: 100% (5/5), 29.88 KiB, done.
#Total 5 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# * [new branch]      master -> master

github.com이 아직 알려진 호스트가 아니기 때문에 OpenSSH에서 경고가 발생하지만 걱정하지 않아도 된다.

Git 메시지는 말 그대로 완벽하다. SVN 메시지와는 달리 쉽게 이해할 수 있다. Git는 멘타트를 위해 멘타트가 작성한 프로그램이기 때문이다. Frank Herbert의 Dune 세계에서 휴먼 컴퓨터로 훈련 받은 인물이라면 아마도 고유 버전의 Git를 이미 작성했을 것이다. 그럴 수 있는 능력을 지닌 존재이니까 말이다. 그런 존재가 아닌 우리는 Git에 사용되는 델타 압축 및 수많은 스레드와 별로 관련이 없으며 알려고 하면 도리어 머리만 아플 뿐이다.

SSH를 통해 push 작업이 수행되었지만 HTTP, HTTPS, rsync 및 file 등의 다른 프로토콜도 사용할 수 있다(git push --help 참조).

SVN과 Git의 차이점 중 가장 중요하고 기본적인 차이점은 바로 커미트에 있다. SVN의 커미트는 "이 변경 사항을 중앙 서버에 push하는 것"을 의미한다. SVN에서는 커미트 전까지의 변경 사항이 언제라도 사라질 수 있다. 하지만 Git에서는 커미트가 로컬에서 수행되고 로컬 저장소를 사용하기 때문에 원격측에서 어떤 일이 발생하더라도 문제가 되지 않는다. 원격 서버와의 상호 작용 없이 변경 사항을 되돌리고, 분기를 만들고, 분기에 대한 커미트를 수행하는 등의 작업을 수행할 수 있다. Git를 사용하여 push 작업을 수행하면 로컬 저장소의 상태와 원격 서버를 효과적으로 동기화할 수 있다.

이제 마지막으로 발생한 작업에 대한 Git 로그를 살펴보자.


Listing 6. Git 로그
% git log
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing

로그에는 커미트에 대한 정보만 있다. 그리고 SVN 개정 번호와는 달리 임의로 생성된 긴 커미트 ID를 볼 수 있다. 하지만 git push를 통한 동기화에 대한 정보가 없다는 것을 알 수 있다.


Git를 통해 협업하기

지금까지는 Git를 SVN에 대한 대안으로 사용해 왔다. 이제 흥미를 높이기 위해 사용자와 변경 세트를 추가해 보자. 이를 위해 저장소를 다른 시스템에 체크아웃한다. 이 시스템에서는 Ubuntu GNU/Linux가 실행되고 있으므로 git 대신 git-core를 설치해야 한다.


Listing 7. 다른 Git ID 설정 및 저장소 체크아웃하기
% git config --global user.name "The Other Ted"
% git config --global user.email "tzz@bu.edu"
% git clone git@github.com:tzz/datatest.git
#Initialized empty Git repository in /home/tzz/datatest/.git/
#Warning: Permanently added 'github.com,65.74.177.129' (RSA) to the list of known hosts.
#remote: Counting objects: 5, done.
#remote: Compressing objects: 100% (4/4), done.
#Indexing 5 objects...
#remote: Total 5 (delta 0), reused 0 (delta 0)
# 100% (5/5) done
% ls datatest
#fortunes  gdbinit
% ls -a datatest/.git
# .  ..  branches  config  description  HEAD  hooks  index  info  logs  objects  refs
% ls -a datatest/.git/hooks
# .  ..  applypatch-msg  commit-msg  post-commit  post-receive post-update
#  pre-applypatch  pre-commit  pre-rebase  update

다시 한번 이 시스템에서 SSH를 통해 GitHub와 작업한 적이 없음을 나타내는 OpenSSH 경고가 표시된다. git clone 명령은 SVN checkout과 유사하지만 병합된 버전의 컨텐츠(특정 개정 또는 최신 개정의 스냅샷)를 가져오는 대신 전체 저장소를 가져온다.

이 기사에서는 모든 컨텐츠를 볼 수 있도록 datatest/.git 디렉토리 및 hooks 서브디렉토리의 컨텐츠를 포함시켰다. Git에서는 기본적으로 모든 내용이 공개되지만 이에 반해 SVN에서는 저장소가 기본적으로 비공개로 설정되며 스냅샷에 대한 액세스만 허용된다.

부수적으로 Git 저장소에서 모든 커미트 또는 일부 커미트에 대해 규칙을 적용하려는 경우에는 hook를 사용할 수 있다. 후크는 SVN 후크와 매우 유사하며 두 후크 모두 "성공 시 0을 리턴하고, 실패 시 임의 값을 리턴하는" 표준 UNIX 규칙을 따른다. 이 기사에서는 후크에 대해 자세히 설명하지 않겠지만 Git를 팀에서 사용할 경우에는 후크에 대해 잘 알고 있어야 한다.

이제 활기에 찬 "The Other Ted"가 마스터 분기(SVN의 TRUNK에 해당)에 새 파일을 추가하고 새 분기를 작성한 후 gdbinit 파일에 변경 사항을 적용하려고 한다.


Listing 8. 파일 추가 및 새 분기 만들기
# get a file to add...
% cp ~/bin/encode.pl .
% git add encode.pl
% git commit -m 'adding encode.pl'
#Created commit 6750342: adding encode.pl
# 1 files changed, 1 insertions(+), 0 deletions(-)
# create mode 100644 encode.pl
% git log
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing
% git branch empty-gdbinit
% git branch
#  empty-gdbinit
#* master
% git checkout empty-gdbinit
#Switched to branch "empty-gdbinit"
% git branch
#* empty-gdbinit
#  master
% git add gdbinit
% git commit -m 'empty gdbinit'
#Created commit 5512d0a: empty gdbinit
# 1 files changed, 0 insertions(+), 1005 deletions(-)
% git push
#updating 'refs/heads/master'
#  from b238ddca99ee582e1a184658405e2a825f0815da
#  to   675034202629e5497ed10b319a9ba42fc72b33e9
#Generating pack...
#Done counting 4 objects.
#Result has 3 objects.
#Deltifying 3 objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 0), reused 0 (delta 0)

예제가 너무 길어서 보다가 졸지 않았기를 바란다. 만일 졸았다면 변경 세트의 왈츠를 들으며 Git 저장소를 동기화하는 꿈을 꾸었기를 바란다. (하긴 앞으로 그런 꿈을 꾸게 될지도 모르겠다.)

먼저 파일을 추가하고(encode.pl, 단 한 줄) 커미트했다. 커미트 이후 GitHub에 있는 원격 저장소에서는 방금 전의 변경 사항을 모른다. 그런 다음 empty-gdbinit이라는 새 분기를 작성한 후 이 분기로 전환했다. (필자는 git checkout -b empty-gdbinit을 사용하여 이 작업을 수행했다.) 이 분기에서 필자는 gdbinit 파일을 비우고 해당 변경 사항을 커미트했다. 마지막으로 원격 서버에 push했다.

마스터 분기로 전환하면 로그에 빈 gdbinit이 표시되지 않을 것이다. 따라서 각 분기에는 당연히 고유 로그가 있다.


Listing 9. 분기 간 로그 살펴보기
# we are still in the empty-gdbinit branch
% git log
#commit 5512d0a4327416c499dcb5f72c3f4f6a257d209f
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    empty gdbinit
#
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing
% git checkout master
#Switched to branch "master"
% git log
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing

push를 수행하면 Git가 GitHub의 서버에 "새 파일 encode.pl이 있어"라고 말한다.

이제 GitHub의 웹 인터페이스에 encode.pl이 표시되지만 아직도 GitHub에는 하나의 분기만 있다. 왜 empty-gdbinit 분기가 동기화되지 않았을까? 왜냐하면 Git는 사용자가 분기와 변경 사항을 기본적으로 push하려고 한다고 가정하지 않기 때문이다. 이를 위해서는 모든 내용을 push해야 한다.


Listing 10. 모두 push하기
% git push -a
#updating 'refs/heads/empty-gdbinit'
#  from 0000000000000000000000000000000000000000
#  to   5512d0a4327416c499dcb5f72c3f4f6a257d209f
#updating 'refs/remotes/origin/HEAD'
#  from 0000000000000000000000000000000000000000
#  to   b238ddca99ee582e1a184658405e2a825f0815da
#updating 'refs/remotes/origin/master'
#  from 0000000000000000000000000000000000000000
#  to   b238ddca99ee582e1a184658405e2a825f0815da
#Generating pack...
#Done counting 5 objects.
#Result has 3 objects.
#Deltifying 3 objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 1), reused 0 (delta 0)

여기에서 다시 한번 자세한 멘타트 인터페이스를 볼 수 있다. 하지만 충분히 이해할 수 있을 것이다. 멘타트는 아니지만 상식적으로 생각해 보면 0000000000000000000000000000000000000000은 일종의 특별한 초기 태그라는 것을 알 수 있다. 또한 Listing 9의 로그를 보면 5512d0a4327416c499dcb5f72c3f4f6a257d209f 태그가 empty-gdbinit 분기의 마지막이자 유일한 커미트라는 것도 알 수 있다. 나머지 부분도 대부분의 사용자에게는 아람말처럼 보이겠지만 염두에 두지 않아도 된다. 이제 GitHub에 새 분기와 변경 사항이 표시된다.

git mvgit rm을 사용하면 각각 파일을 관리하는 작업과 이름 바꾸기 및 제거 작업을 수행할 수 있다.


결론

이 기사에서는 기본적인 Git 개념을 설명한 후 Git를 사용하여 버전 제어 아래에 있는 간단한 디렉토리의 컨텐츠를 관리하는 방법을 설명하면서 Git와 Subversion도 함께 비교했다. 그리고 간단한 예제를 사용하여 분기에 대해서도 설명했다.

Part 2에서는 병합, 차이 생성 등을 비롯한 기타 Git 명령에 대해 설명한다. 이해하기 쉬운 Git 설명서나 튜토리얼을 읽어 보기를 권장한다. 이러한 자료는 모두 Git 홈 페이지에서 구할 수 있으므로 약간의 시간을 투자해서 살펴보기를 바란다. (아래 참고자료에서 링크를 볼 수 있다.) SVN 사용자라면 그 이상의 자료가 필요하지는 않을 것이다.

반면 Git는 다양한 기능을 갖춘 DVCS이므로 Git의 기능을 잘 알고 있으면 이러한 기능을 활용하여 VCS 워크플로우를 단순화하고 향상시키는 데 큰 도움이 될 것이다. 게다가 Git 저장소에 대한 꿈을 한두번 꿀지도 모르겠다.


참고자료

교육

제품 및 기술 얻기

  • Git를 다운로드하고 Git download 페이지를 비롯한 수많은 문서와 도구를 살펴보자.

  • developerWorks에서 직접 다운로드할 수 있는 IBM 시험판 소프트웨어를 사용하여 Linux와 관련된 후속 개발 프로젝트를 구현해 볼 수 있다.

토론

  • 사용자의 개인 프로파일과 사용자 정의 홈 페이지가 제공되는 My developerWorks community에서는 관심을 가지고 있는 developerWorks의 여러 주제를 추적할 수 있으며 다른 developerWorks 사용자들과 의견을 나눌 수도 있다.

필자소개

사진 - teodor zlatanov

Teodor Zlatanov는 1999년에 Boston University에서 컴퓨터 공학 석사 학위를 받았으며 1992년부터 Perl, Java, C 및 C++를 사용하는 프로그래머로 활약했다. 텍스트 구문 분석, 데이터베이스 아키텍처, 사용자 인터페이스 및 UNIX 시스템 관리에 대한 오픈 소스 활동에 관심을 두고 있다.


출처 : http://www.ibm.com/developerworks/kr/library/l-git-subversion-1/

Posted by 1010