git 정말 필요한가?

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

이 글은 총 4부 중 마지막인, git이 필요한지에 대한 개인적인 의구심을 정리해 봤습니다.

요즘 git에 대해 이것저것 실험삼아 해보고, 자료를 보면서 드는 생각은

“Git이 정말 필요한가?”

이다. 서브버전과 비교하여 장단점을 비교하기에 앞서 git의 구조와 CI(Continuous Integration) 관점에서 전체 아키텍처를 한번 살펴보자. 이 글은 git을 쓰려고 시도한 후배님들과 대화한 내용을 중심으로 썼기에, 잘못된 부분이 있을 수도 있다.

프로젝트 공유

우선 2명의 개발자가 협업할 때 로컬 저장소(master)와 원격 저장소(remote repository)간의 구조와 처리 흐름은 다음과 같다.

git_share_project

  1. 개발자 A가 자신의 로컬 머신에서 git 프로젝트를 생성(init)한다.
  2. 그리고 몇가지 작업을 한 후, 프로젝트를 공유하기 위해 원격 서버에 프로젝트를 푸시한다.
  3. 개발자 B는 원격 저장소의 프로젝트를 자신의 로컬 저장소로 복제(clone)한다.

따라서 각 개발자는 자신만의 저장소(master)에서 변경작업을 하고 커밋을 하며, 로컬에서 작업 이력을 관리할 수 있게 된다. 여기에 git의 장점 2가지가 있다.

  • 빠르다
  • 네트워크가 안되더라도 작업 이력을 관리할 수 있다.

장점을 좀더 면밀히 검토해 보자. 우선 커밋이 로컬 머신에서 일어나므로, 빠를 수 밖에 없다. 네트워크 IO가 아니라 단순히 디스크 IO만 발생하기 때문이다. 하지만 서브버전을 쓰면서 느리다는 느낌을 받은 적은 거의 없다. 파일이 많으면 오래 걸릴 수도 있겠지만, 오히려 그 시간에 허리 한번 필 수 있으니 건강에는 도움이 되겠다. 즉 사용성 관점에서 크게 와닿지 않은 빠름일 뿐이다.

네트워크가 안된다? 북한도 인터넷이 되는 마당에, 네트워크는 어디를 가야 안되는지 모르겠다. 전혀 공감되는 장점이 아니다.

프로젝트 수정

그럼 이제 자신의 마스터 브랜치에서 프로그램을 수정한다고 해보자.

git_modify_project

프로젝트의 기능을 추가하거나 변경하는 경우 아래와 같은 흐름을 따르게 된다.

  • 마스터 브랜치에서 새로운 브랜치를 만든다.
  • 개발을 완료한 후 커밋을 한다.
  • 브랜치를 마스터 브랜치에 합친다.
  • 브랜치는 정리한다.
  • 변경된 내용을 중앙 저장소에 푸쉬한다.

유심히 볼 점은 중앙 저장소에 올리기 위해서는 2단계를 거친다는 점이다. 먼저 로컬 저장소에 커밋 한 후, 원격 저장소에 푸시한다는 점이다. 이 점이 중요한데, 이를 위해서는 전체 CI 구조를 이해해야 한다.

CI 관점에서 본 git

전체 CI 구조를 보면 다음과 같다.

git_as_ci

  • jenkins 서버(빌드 서버)에서 형상 서버(Git 서버)에서 소스 코드를 풀링한다.
  • 풀링된 코드를 빌드하여 실행 파일을 만든다.
  • 실행 파일을 운영 서버에 배포한다.

이처럼 CI 환경을 구성할 때 가장 어려운 점은 빌드시 2가지 문제가 흔히 발생한다는 점이다. 첫 번째는 컴파일 에러이며, 두번째는 개발이 완료되지 않아 배포하지 말아야 할 기능이 실수로 배포된다는 점이다.

이러한 문제가 발생하는 원인은 개발자가 완성되지 않은 프로그램을 형상 서버에 커밋하기 때문이다. 그러면 개발자는 완료되지도 않은 프로그램을 왜 형상 서버에 커밋하는가? 그 이유는 2가지다.

첫 번째는 자신의 로컬 환경에 장애가 발생할 수도 있으므로, 완성되지 않았더라도 백업하기를 원하기 때문이다. 즉 형상 성버는 백업의 공간으로 지금까지 활용되어 왔다. 두 번째는 이력을 관리하기 위해서다. 퇴근하기 전, 또는 사용자 스토리 중 하나의 기능이 완료가 되었을 때, 해당 시점의 프로그램에 대한 스냅샷을 저장하기 위해서다.

이러한 점에서 볼 때 git은 이 문제를 매끄럽게 해결한다. 자신의 로컬에 저장소를 따로 가지므로, 백업을 위한 공간인 동시에 로컬에서 이력을 관리할 수도 있게 된다.

사실 이러한 상황 뒤에는 커밋(commit)이 가지는 두 가지 의미가 이전까지는 제대로 정의되지 않아서라고 본다. 서브버전을 예로 들어보자. 서브버전에서 커밋이라는 행위에 2가지 의미가 내포된다. 첫 번째는 내가 완성한 프로그램을 운영서버에 배포하기 원한다는 뜻이며, 따라서 형상 서버에 올리겠다라는 행위다. 두 번째는 좀 전에도 얘기한 백업과 이력을 관리해 달라고 요청하는 것이다.

결국 서브버전을 CI 환경에 중심 형상 서버로 사용하게 되면, 개발자가 커밋한 행위가 배포를 요청한 것인지, 아니면 이력을 관리해달라고 요청한 것인지 구분이 불가능하다. 이로 인해 빌드하거나 운영 서버에 새로운 기능이 배포될 때 마다 늘 지옥문이 열리곤 한다.

git의 경우 커밋이 가지는 2가지 의미를 완전히 분리된 액션으로 만들었다. 즉 커밋은 자신의 로컬 저장소에만 요청하며, 배포는 원격 저장소에 푸시를 통해 요청하는 것이다. 사실 이러한 이해가 git의 사상에 맞는지는 제대로 활용한 경험이 없어 아직 모르겠다. 하지만 얼추 맞지 않을까?

한국 자바 직장인 개발자를 위한 내용

리눅스 머신에서 자바 개발을 하는 직장인 개발자를 본 적이 있는가? 결국 나처럼 윈도우에서 이클리스 기반으로 자바를 사용해 프로그래밍을 할 것이다. git이 윈도우를 공식적으로 지원하지 않기 때문에 윈도우 버전인 egit 이클립스 플러그인을 설치해서 개발을 해야 한다. 들은 얘기로 윈도우 64비트에서 egit이 제대로 동작하지 않는다. 이런 상황에서 꼮 git이어야만 할까? 잘 모르겠다.

크몽 재능

나도 git 좀 써보자 – 사용하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

이 글은 총 4부 중, 2번째 git 사용하기와 관련한 내용입니다.

설치가 끝났으니 이제 프로젝트를 하나 만들어서 기본적인 활용법을 익혀보자.

저장소 만들기

서브버전을 사용해 왔다면 아마 저장소는 원격 서버에서 호스팅하고, 자신의 로컬 머신에는 복사본 또는 working copy를 유지했을 것이다. 반면 git에서는 복사본 또는 working tree뿐만 아니라, 저장소까지도 자신의 로컬 머신의 .git 디렉토리에 유지한다.

일단 비어 있는 프로젝트를 하나 만들자.

C:\Documents and Settings>mkdir my-project
C:\Documents and Settings>cd my-project
C:\Documents and Settings\my-project>git init
Initialized empty Git repository in C:/Documents and Settings/my-project/.git/

프로젝트 디렉토리 안에서 git init 명령어를 실행하면, git 저장소가 생성된다.

파일 추가하고 변경하기

비어 있는 프로젝트에 index.html 파일을 아래와 같이 만들어서 추가하자.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World
</body>
</html>

이제 프로젝트에 변경 사항이 생겼으므로, 즉 파일이 하나 추가되었으므로 git에 변경사항이 발생했음을 알려주자. 새로운 관리 대상이 생겼으므로, 대상 추가 작업 및 커밋 작업 2단계를 거쳐야 한다.

C:\Documents and Settings\my-project>git add index.html
C:\Documents and Settings\my-project>git commit -m "add new index.html"
[master (root-commit) 9bb9136] add new index.html
1 file changed, 9 insertions(+)
create mode 100644 index.html

커밋 로그를 보면,

C:\Documents and Settings\my-project>git log
commit 9bb9136e023cddf5db8457069be9c317bbc3ab2b
Author: socurites <socurites@gmail.com>
Date: Mon Jun 10 15:51:57 2013 +0900

add new index.html

와 같이 index.html 파일이 추가되었다는 기록을 확인할 수있다.

파일을 변경하기 전에, 현재 작업중인 working tree의 상태를 살펴보자.

C:\Documents and Settings\my-project>git status
# On branch master
nothing to commit, working directory clean

보다시피 working tree는 저장소와 현재 동기화 된 상태임을 알 수 있다. 이제 index.html을 아래와 같이 수정한 후,

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World
Hello Changed World
</body>
</html>

working tree의 상태를 다시 살펴보면,

C:\Documents and Settings\my-project>git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: index.html
#
no changes added to commit (use "git add" and/or "git commit -a")

index.html 파일이 변경되었으며, 커밋을 하려면 스테이징이 필요하다는 메시지가 나온다.

git의 경우 파일을 세 군데에 저장한다.

  • 작업 트리(working tree)
    로컬에서 작업하는 공간으로, 서브버전에서는 working copy라고도 부른다.
  • 스테이징 영역(staging area)
    작업 트리와 저장소 사이의 버퍼 공간으로, 커밋할 대상을 올려두는 위치다.
  • 저장소(repository)
    실제 파일이 관리되는 공간이다.

git add 명령어를 사용하면, 작업 트리의 파일이 스테이징 영역으로 이동시킨다. 즉 파일을 스테이징하게 된다.

C:\Documents and Settings\my-project>git add index.html
C:\Documents and Settings\my-project>git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#

이제 파일을 저장소로 커밋하자.

C:\Documents and Settings\my-project>git commit -m "add body text" index.html
[master 0ca802d] add body text
1 file changed, 1 insertion(+)

C:\Documents and Settings\my-project>git status
# On branch master
nothing to commit, working directory clean

브랜치(branch) 만들기

이제 프로젝트의 스프린트가 마무리되었고, 릴리즈팀에서 릴리즈해야 한다고 해보자. 하지만 릴리즈와 돵시에프로젝트에 새로운 기능을 추가해야 한다고 해보자. 만약 새로운 기능을 추가할 때까지 릴리즈를 늦추어야 한다면, 사용자는 개발이 이미 완료된 기능을 다음 릴리즈까지 기다리게 하는 셈이다. 따라서 이번 이번 스프린트의 결과는 그대로 릴리즈하면서 동시에 새로운 기능을 개발하려고 할 때, 브랜치를 사용하면 효과적이다.

C:\Documents and Settings\my-project>git branch RB_1.0 master

git branch 명령어는 2개의 인자를 받는데, 첫 번째 인자는 브랜치명이며 두번째 인자는 브랜치할 프로젝트 명이다. 서브버전에서는 마스터 브랜치를 트렁크(trunk)라고 불렀다.

이제 릴리즈팀에서는 RB_1.0 브랜치에서 릴리즈를 하도록 내버려 두고, 개발팀은 마스터에서 새로운 기능을 추가한다. 예를 들어 index.html 파일을 수정해야한다고 해보자.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World
Hello Changed World
Hello New World
</body>
</html>

변경사항을 마스터 브랜치에 커밋한다.


C:\Documents and Settings\my-project>git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: index.html
#
no changes added to commit (use "git add" and/or "git commit -a")

C:\Documents and Settings\my-project>git commit -a -m "new world"
[master f8e64db] new world
1 file changed, 2 insertions(+)

C:\Documents and Settings\my-project>git status
# On branch master
nothing to commit, working directory clean

이와 동시에 릴리즈팀에서도 릴리즈 브랜치를 약간 수정할 작업이 생겼다. 작업 트리를 릴리즈 브랜치로 변경해 보자.

C:\Documents and Settings\my-project>git checkout RB_1.0
Switched to branch 'RB_1.0'

마스터에서 브랜치로 스위칭하면, 현재 수정한 index.html 파일의 내용이 변경 전의 내용으로 갱신된다. 왜냐하면 릴리즈 브랜치에서는 변경 작업이 발생하지 않았기 때문이다. 따라서 브랜치를 사용하면 릴리즈와 개발을 동시에 진행할 수 있게 된다.

이제 릴리즈 브랜치에서도 index.html을 약간 수정하자.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World
Hello Changed World
Hello Release World
</body>
</html>

그리고 변경사항을 커밋한다.

C:\Documents and Settings\my-project>git commit -a -m "release world"
[RB_1.0 d22029a] release world
1 file changed, 2 insertions(+)

릴리즈 태깅(tagging)하기

이제 릴리즈 준비가 끝난 브랜치에 태깅을 해보자. 태깅이란 거창한게 아니라 단순히 이름, 즉 태그를 붙여서 저장소 이력의 특정 지점을 알기 쉽게 표시하는 것이다.


C:\Documents and Settings\my-project>git tag my-project-1.0 RB_1.0
C:\Documents and Settings\my-project>git tag
my-project-1.0

릴리즈가 완료되면, 릴리즈 브랜치에서 변경된 내용을 마스터 브랜치에도 반영을 해야 하는데, 이러한 작업을 브랜치 병합(merge) 또는 합치기라고 부른다. 먼저 작업 트리를 마스터 브랜치로 변경한다.

C:\Documents and Settings\my-project>git checkout master
Switched to branch 'master'

브랜치를 마스터에 합친다.

C:\Documents and Settings\my-project>git rebase RB_1.0
First, rewinding head to replay your work on top of it...

근데 문제가 생겼다. 릴리즈 브랜치와 마스터 브랜치에서 동일한 라인을 수정했기 때문에, 자동으로 합치는게 불가능해졌다. 이런 경우에는 사람이 직접 개입을 할 수밖에 없다.

일단 동일한 파일의 동일한 라인을 2사람이 동시에 변경할 가능성이 적기 때문에, 이러한 긍정정 잠금(optimistic lock) 기능이 효과적이라고 하는 말에 개인적으로는 동의하지 않는다. 변경 가능성이 적더라도 언제나 충돌이 발생하기 마련이다. 특히 공통 파일 같은 경우 충돌이 발생하면, 정말이지 지옥의 문이 열리는 경우가 한두번이 아니기 때문이다.

어쨌든 충돌을 해결해 보자. 우선 마스터 브랜치에서 파일을 아래와 같이 정리하자.

C:\Documents and Settings\my-project>git commit -a -m "rebase conflict, merged by hand"
[detached HEAD 899bddf] rebase conflict, merged by hand
1 file changed, 2 insertions(+)

C:\Documents and Settings\my-project>git status
# HEAD detached from d22029a
# You are currently rebasing.
# (all conflicts fixed: run "git rebase --continue")
#
nothing to commit, working directory clean

일단 최신의 파일로 변경한 후 커밋했지만, 저장소의 상태는 문제가 있어 보인다. 그리고 rebase 명령어를 사용하는 경우, 나도 모르게 마스터 브랜치에서 릴리즈 브랜치로 이동한 상태가 되버렸다. 일단 이부분도 강제적으로 해결하자.

C:\Documents and Settings\my-project>git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

899bddf rebase conflict, merged by hand

If you want to keep them by creating a new branch, this may be a good timeto do so with:

git branch new_branch_name 899bddf

Switched to branch 'master'

C:\Documents and Settings\my-project>git status
# On branch master
# You are currently rebasing.
# (all conflicts fixed: run "git rebase --continue")
#
nothing to commit, working directory clean

C:\Documents and Settings\my-project>git commit -a -m "rebase conflict, merged by hand"
# On branch master
# You are currently rebasing.
# (all conflicts fixed: run "git rebase --continue")
#
nothing to commit, working directory clean

C:\Documents and Settings\my-project>git status
# On branch master
# You are currently rebasing.
# (all conflicts fixed: run "git rebase --continue")
#
nothing to commit, working directory clean

그럼 이제 불필요한 릴리즈 브랜치는 삭제하자. 릴리즈 브랜치는 태깅해두었기 때문에, 해당 브랜치에서 수정 작업이 필요하다면 태깅한 브랜치에서 얼마든지 다시 분기할 수 있다.

C:\Documents and Settings\my-project>git branch -d RB_1.0
error: The branch 'RB_1.0' is not fully merged.
If you are sure you want to delete it, run 'git branch -D RB_1.0'.

C:\Documents and Settings\my-project>git branch -D RB_1.0
Deleted branch RB_1.0 (was d22029a).

이제 my-project-1.0이라는 이름으로 태깅한 릴리즈를 압축하자.

C:\Documents and Settings\my-project>git archive --format=zip --prefix=my-project-1.0/ my-project-1.0 > mysite-1.0.zip

원격 저장소 사용하기

이제 개발팀에 새로운 팀원이 추가되었고, 프로젝트를 다른 팀원과 함께 개발해야 한다고 해보자. 그러면 지금까지 로컬 저장소에서 관리하던 프로젝트를 팀원과 공유해야 한다. 즉 원격 저장소가 필요하다.

원격 저장소를 만들었다면, 이제 원격 저장소에 지금가지 개발된 코드를 올린다. 그러면 새로운 팀원은 원격 저장소에서 코드를 복제한다. 이제 두명의 개발자가 동일한 프로젝트에서 작업하게 되었으며, 각각 자신만의 저장소를 가지게 되었다. 하지만 두 사람은 모두 변경된 사항을 원격에 푸시(push)해야만 서로간에 최신의 코드를 유지할 수 있다.

일단 원격 저장소 사용하는 법은 이어서 살펴보도록 하자.

크몽 재능

나도 git 좀 써 보자 – 설치하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

이 글은 총 4부로 구성됩니다.

CVS에서 SVN으로 넘어온지도 꽤 지났다. 그러면서 ant, hudson, jenkins, anthill.. 이제는 이름도 생각나지 않은 Cruise Control 등의 빌드 툴들을 사용해 봤다. 그렇게 시간이 지났고, 이제 git의 시대가 되었다.

관심 없었다. 서브버전(Subversion)으로도 충분히 큰 불편함이 없었기에. 이제 사람들이 모두 “짓허브”이 아니라 “깃허브”이라고 부르는 Github에 많은 프로젝트와 코드들이 쌓여간다.

이제는 더 뒤쳐질 수 없기에, 몇년 전에 사두었던 책, “Git, 분산 버전 관리 시스템”을 꺼내 들었다.

git_book

이제 나도 Github에서 단순히 ZIP 파일을 다운로드 하는게 아니라, 제대로 좀 써보자!!!!!!
는 생각으로 git 사용 이력을 여기에 남겨 본다.

먼저 git을 설치해야 한다. 맥북에는 이미 설치가 되어 있다. 이미 설치되었는지 궁금하다면, 버전을 확인해 보자.

island:~ socurites$ git --version
git version 1.8.1.2

윈도우에 git 설치하기

git은 리눅스를 기반으로 만들어졌기 때문에, 윈도우는 공식적으로 지원하지 않는다. 하지만 구글 코드에서 Git on MSys라는 이름의 프로젝트가 있다. Git on MSys는 git을 윈도우에 설치하여 쉽게 사용할 수 있도록 만들어졌다.

https://code.google.com/p/msysgit/‎에서 다운로드하여 쉽게 설치가 가능하다. 설치 과정에서 아래 창이 뜨면 2번째 옵션을 선택해야만, 커맨드 창에서도 git을 쉽게 이용할 수 있다.

git_window_install

이제 환경 설정을 해야 한다. 로컬 시스템의 전역 값 중에서 user.name과 user.email은 반드시 설정해야 한다.

island:~ socurites$ git config --global uer.name "socurites"
island:~ socurites$ git config --global user.name "socurites"
island:~ socurites$ git config --global user.email "socurites@gmail.com"
island:~ socurites$ git config --global --list
uer.name=socurites
user.name=socurites
user.email=socurites@gmail.com

잘못해서 오타가 났다. user.name을 입력해야 하는데, user.name을 입력했다. 불필요한 설정 값이므로 삭제하자.

island:~ socurites$ git config --global --unset uer.name
island:~ socurites$ git config --global --list
user.name=socurites
user.email=socurites@gmail.com

여기까지가 필수 전역 설정이다.

윈도우 사용자를 위한 추가 설정

git의 커밋 메시지의 기본 인코딩은 utf-8이다. 윈도우는 명령 프롬프트의 기본 인코딩이 cp949이므로, 커밋 메시지와 로그 메시지의 인코딩을 cp949로 변경해야 한다.

prompt>git config --global i18n.commitEncoding cp949
prompt>git config --global i18n.logOutputEncoding cp949

또한 mSysGit에서 로그 메시지를 콘솔로 출력할 때 less 명령어를 사용하는데, less 명령어가 한글을 제대로 보여주려면 마찬가지로 문자셋을 변경해 주어야 한다.

prompt> set LESSCHARSET=latin1

만약 환경 변수 값을 영구적으로 설정하고자 한다면, 시스템 환경 변수에 LESSCHARSET를 추가하고, 값으로는 latin1을 설정한다.

git_window_lesscharset

설정 파일은 사용자 홈디렉토리에 .gitconfig라는 이름으로로 저장된다.

크몽 재능