comment 0

A Successful Git Branching Model (번역)

원문 : http://nvie.com/posts/a-successful-git-branching-model/

잘 하지도 못하는 영어로 낑낑대며 번역을 한번 해보았습니다. 잘못된 부분이나 오역이 있으면 주저하지 말고 댓글 달아주세요.
그리고 중간 중간 이해가 안가는 부분도 있었는데 설명해주시면 정말 감사하겠습니다^^

A Successful Git Branching Model

이 포스트에서 저는 1년전 저의 모든 프로젝트(직장, 개인적인 프로젝트 둘다)에 소개했고 성공적이었다고 드러난 개발 모델을 제시하겠습니다. 저는 이 프로젝트들에 대해 써보려고 했으나, 지금까지 완벽히 쓸 시간이 없었네요. 저는 이 프로젝트에 대한 디테일은 설명하지 않고, 단지 브랜칭 전략과 배포 관리에 대해서만 이야기 하겠습니다.

Git Branching Model

Why Git?

중앙집중 소스코드 관리 시스템과 비교하여 Git 이 가지는 단점과 장점을 심도있게 토론한 내용을 보려면, 이 페이지[]를 확인하세요. 여기에선 아주 뜨거운 논쟁이 오갔습니다. 개발자로써, 저는 오늘날 존재하는 다른 형상관리 시스템 중 Git 을 선호합니다. Git 은 머징과 브랜칭에 대한 개발자의 관념을 통째로 바꿔놨습니다. CVS나 Subversion 과 같은 오래된 형상관리시스템에서 저는 merging 과 branching 은 할때마다 무서웠고 (“merge conflict 를 조심하세요!, 이것들이 당신 발목을 잡을꺼에요”) 아주 가끔씩만 하는걸로 알고 있었어요.

하지만 Git 을 쓰면, 이러한 액션들은 매우 비용이 적게 들고 단순하며, 이러한 것들(merging 과 branching)은 당신의 일상업무에 매우 중요한 부분으로 자리잡을 것입니다. 예를들면, CVS/Subversion을 다룬 책들에서는, branching 과 merging 은 후반부의 챕터(그것도 고급 유저를 위해)에서나 다루어지지만, 모든 Git 관련 서적에서는 한 3장쯤에 매우 빨리 나옵니다.

결과적으로 이 단순함과 자연스러운 반복때문에 branching 과 merging 은 더이상 두려워 할 것이 아니게됩니다. 형상관리도구는 다른 기능들보다도 branching과 merging 을 쉽게 할 수 있어야 합니다.

툴에 대해서는 이것으로 됐고, 개발모델을 봅시다. 제가 여기서 소개하려고 하는 모델은 올바르게 관리되는 소프트웨어 개발 과정을 위해 모든 팀 구성원이 따라야 하는 과정의 집합에 지나지 않습니다.

Decentralized but centralized

우리가 사용하고 이 브랜칭 모델에 잘 맞는 저장소 설정은 중앙의 ‘truth’ repo 입니다. 이 저장소는 오직 하나의 중앙 저장소이여야 한다는 것을 기억하세요. (Git 은 DVCS(Decentralized Version Control System) 이므로 기술적인 측면에서 중앙저장소는 없습니다.). 우리는 이 저장소를 모든 Git 유저에게 익숙한 origin 이라고 하겠습니다.

centralized-decentralized

각 개발자는 origin 을 pull 또는 push 합니다. 하지만 중앙집중식 push-pull 관계는 논외로 하고서라도, 각각의 개발자들은 다른 동료의 변경내역을 pull 해서 sub team 을 만들 수도 있습니다. (잘 모르겠네요;;)But besides the centralized push-pull relationships, each developer may also pull changes from other peers to form sub teams. 예를들면, 두명 이상의 개발자들이 하나의 상당히 큰 새로운 기능을 구현하기 위해 같이 일할때, 아직 완성되지 않은 진행중인 작업내역을 origin에 push 하기 전에는 유용합니다. 위의 그림을 보면, Alice 와 Bob 팀, Alice 와 David, Clair 와 David 로 이루어지는 sub팀이 있습니다.

기술적으로, 이것은 Alice 가 Git remote 저장소 이릅을 bob 으로 짓고, Bob의 저장소를 가르키고 있는 것 뿐이고, 그 반대도 마찬가지입니다.

The main branches

Main Branch

이 개발 모델은 이미 존재하는 모델로부터 떠올렸습니다. 중앙 저장소는 두개의 없어지지 않는(infinite lifetime을 갖는) 메인 브랜치를 가지고 있습니다 :

  • master
  • develop

master 브랜치의 orgin 은 Git 유저들에게 익숙할 것 입니다. master 브랜치와 병렬로 develop 이라는 브랜치가 존재합니다.

orgin/master 는 항상 실서비스에 나갈 수 있는(production-ready) 상태인 HEAD 소스코드를 가지고 있는 메인 브랜치입니다.

origin/develop은 다음 배포를 위해 마지막으로 수정된 사항이 있는 HEAD 소스코드를 가지고 있는 메인브랜치 입니다. 어떤 사람들은 이것을 “integration branch” 라고 부르지요. 이것(origin/develop)은 매일 발생하는 소프트웨어에 대한 수정사항을 포함하고 있는 소프트웨어 배포버전(nighly build) 가 build 되는 곳입니다.

develop 브랜치에 있는 소스코드가 안정적인 시점에 도달하여 배포할 준비가 되면, 모든 변경사항들은 master로 merge되어야 하고 그때 배포번호(release number)로 tag 를 매깁니다. 이것이 어떤식으로 이루어지는지는 계속해서 이야기 할것입니다.

그러므로, 변경사항들이 master로 머지될때마다, 이것은 하나의 실서비스 배포 인 것입니다. (변경사항들이 master로 머지될 때를, 실서비스 배포로 정의한다) 이론적인 이 사항을 엄격히 적용하기 위해서, 우리는 master 에 커밋이 발생할때마다 자동으로 빌드하고 production server 에 자동으로 배포하는 Git 의 hook script 를 사용할 수 있습니다.

Supporting branches

masterdevelop 브랜치에 이어, 우리의 개발모델은 팀 구성원 간에 병렬적인 개발, 기능들에 대한 쉬운 트래킹, production 배포 준비, 그리고 현재 서비스되고 있는 실서비스에 생긴 문제를 빠르게 고칠 수 있도록 도움을 주기 위해 다른 supporting 브랜치들을 사용합니다.

우리가 사용할 이 브랜치들의 종류는 다음과 같습니다.

  • Feature branches
  • Release branches
  • Hotfix branches

이 각각의 브랜치들은 특정한 목적을 가지고 있고 어떤 브랜치들은 이 브랜치들의 originating 브랜치가 될 수도 있고 어떤 브랜치는 반드시 이 브랜치의 merge target 이 되어야 하기 때문에 엄격한 규칙에 묶여 있습니다. 이것들에 대해서는 곧 살펴보도록 하죠.

이 브랜치들을 기술적 관점에서 결코 “특별한” 것들이 아닙니다. 브랜치 타입은 우리가 어떻게 사용 하느냐에 따라 분류되는 것이고 이것들을 단순한 Git 브랜치일 뿐입니다.

Feature branches

  • May branch off from: develop
  • Must merge back into : develop
  • Branch naming convention : master, develop, release-*, hotfix-* 를 제외한 이름

Feature Branches

Feature 브랜치(아니면 때때로 topic 브랜치라고 불리는)는 추후 배포를 위하여 새롭게 추가되는 기능을 개발할때 사용됩니다. 하나의 feature의 개발을 시작할때는 이 feature 가 합쳐질 target release 가 무엇인지 모릅니다. feature 브랜치의 핵심은 그 기능이 개발 중에는 계속해서 존재하지만, 결국에는 develop 브랜치에 merge 되거나(그 기능이 다음 배포에 확실히 추가될것이라면) 제거(그 기능을 개발해놓고 테스트 해보니 기대에 못미칠 경우)된다는 것입니다.

Feature 브랜치는 기본적으로 developer 의 저장소에만 존재하며, origin 에는 존재하지 않습니다. (즉 로컬저장소에만 존재하는 브랜치이며, 리모트에는 존재하는 경우가 없다는 말!)

Creating a feature branch

새로운 feature 에 대한 개발을 시작할때는, develop브랜치에서 브랜치를 땁니다.

$ git checkout -b myfeature develop
> Switched to a new branch "myfeature"

Incorporating a finished feature on develop

완료된 feature 들은 develop브랜치로 merge하고 다음 배포에 확실히 반영합니다.

$ git checkout develop
Switched to branch 'develop`
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

--no–ff 플래그는 merge 작업이 fast-forward 로 동작할지라도 항상 새로운 커밋객체를 만들도록 합니다. 이것은 예전에 존재했던 feature branch 가 정보를 잃지 않게 해주며 feature branch 에 커밋됐던 모든 내용을 하나로 묶어 줍니다.
비교 :
Merge without Fast Forward

위 그림에서 오른쪽 그림의 경우(In the latter case), 커밋 객체들이 함께 기능을 구현했기 때문에 git 의 history 를 볼 수가 없습니다. 일일이 하나씩 모든 로그메시지를 봐야하죠. 오른쪽 그림과 같은 상황에서 merge했던 feature(i.e. group of commits) 을 원복시키려면 이건 정말 골치 아픈 일입니다. 하지만 –no-ff flag 와 함께 merge 됐던 왼쪽 그림과 같은 경우에는 쉽게 원복 시킬 수 있죠.

네, 이것은 몇몇 (텅빈) commit object들을 생성할 수 있습니다, 하지만 실보단 득이 훨씬 더 크죠.

안타깝게도 저는 –no-ff 를 git merge 의 기본값으로 바꾸는 방법을 찾지 못했네요. 하지만 git merge 는 정말 –no-ff 와 함께 쓰여야 합니다.

Release branches

  • May branch off from: develop
  • Must merge back into : develop 과 master
  • Branch naming convention : release-*

Release branch 들은 새로운 production 릴리즈를 준비하는데 도움을 줍니다. Relase branch 들은 마지막순간에 실수를 저지르지 않도록 도와줍니다. 게다가, 릴리즈를 위한 자잘한 버그의 수정이나 메타데이터(version number, build dates, etc)를 준비하는 것을 가능하게 해줍니다. 이러한 것들을 release branch 에서 수행함으로써, develop 브랜치는 다음번의 큰 릴리즈를 위한 기능들을 받아들이기가 매우 용이해집니다.

develop 브랜치로부터 새로운 브랜치를 따는 결정적인 순간은 새로운 릴리즈를 위해 개발하던 것이 안정적인 상태에 도달 했을 때입니다. build될 release(release-to-be-built)를 위한 모든 기능들은 이시점에 develop 브랜치로 반드시 적절한 시기에 merge 되어야 합니다. future 릴리즈에 반영될 기능들은 develop 브랜치로 merge 되면 안됩니다. 이것들은 새로운 릴리즈 브랜치가 새롭게 브랜치가 따질때까지 기다려야 합니다.

이것은 정확히 버전번호를 부여받는 다음 배포를 위한 release branch 가 시작될 순간입니다. 그때까지는, develop branch 가 “next release”를 위한 변경사항들을 반영하지만, release branch 가 시작되기 전까지는 이 “next release”가 확실히 0.3버전 이나 1.0 버전이 될지는 알 수 없습니다. 이러한 결정은 release branch 의 시작에서 이루어지며 버전 번호를 매기는 프로젝트 룰에 따릅니다. (이 문단은 이해가 잘 안가네요. 밑에 원문을 달아둡니다.)

It is exactly at the start of a release branch that the upcoming release gets assigned a version number—not any earlier. Up until that moment, the develop branch reflected changes for the “next release”, but it is unclear whether that “next release” will eventually become 0.3 or 1.0, until the release branch is started. That decision is made on the start of the release branch and is carried out by the project’s rules on version number bumping.

Creating a release branch

Release branch들은 develop branch 로부터 만들어집니다. 예를 들면, 버전 1.1.5가 현재의 production release 버전이고 우리는 큰 release 를 앞두고 있습니다. develop의 상태는 “next release”를 위한 준비가 되어 있고 우리는 이것이 버전 1.2로 하기로 결정(1.1.6이나 2.0으로 하지않고) 했습니다. 그래서 우리는 브랜치를 따고 새로운 버전번호를 반영하는 이름을 release branch에 주었습니다.

$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

새로운 브랜치를 만든 후에 그걸로 전환하고, 우리는 그 버전번호로 bump 합니다. 여기에서, bumt-version.sh는 working copy 의 파일들에 새로운 버전을 반영시키는 (가상의) 쉘 스크립트 입니다. (이것은 물론 일일히 손으로 바꿀 수 있습니다.–the point being that some files change.) 그다음에, bumped 버전번호는 commit 됩니다.

이 새로운 브랜치는 그 release 가 확실히 출시될때까지 한동안 존재할 수 있습니다. 그 동안에, 버그픽스들이 이 브랜치(release branch)에 반영될 수 있습니다. (develop 브랜치에 하는게 아니고). 여기에 크나큰 새로운 기능을 추가하는것은 엄격히 금지됩니다. 이런 큰 기능들은 반드시 develop 브랜치에 merge되어야 하므로 다음 big release 까지 기다립니다.

Finishing a release branch

release branch 가 real release 할 상태가 되면, 몇가지 작업이 수행되어야 합니다. 첫째, 이 release branch는 master로 merge 되어야 합니다. (master의 모든 커밋은 새로운 release 가 된다는 정의를 기억하세요). 그 다음, master에 커밋된 것은 나중에 쉽게 참조되기 위해 반드시 tag 되어야 합니다. 마지막으로, release branch 에 생긴 버그fix들은 미래의 release 에도 포함되도어야 하므로 develop 브랜치로 머지합니다.

The first two steps in Git:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

이 release 는 이제 끝나고, 나중에 참조하기 위해 태그도 만들었습니다.
Edit : -s or -u 플래그를 써서 태그를 암호화할 수도 있습니다.

release branch 의 변경내역들을 보존하기 위해서, develop으로 머지해야 합니다.
In Git:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

여기서 merge 를 하는 동안 conflict 가 날 수도 있습니다. (아마도 버전넘버를 바꿔서..) 만약 그렇다면 고치고 커밋하세요.

이제 정말 끝났고 release branch는 더이상 필요 없으니 제거해도 됩니다.

$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

Hotfix branches

  • May branch off from : master
  • Must merge back into : develop and master
  • Branch naming convention : hotfix-*

Hotfix Branches

Hotfix 브랜치들은 계획되지 않았던 것만 빼고 새로운 production release 를 위한 것이라는 점에서 release 브랜치와 닮았습니다. 이 hotfix 브랜치들은 실서비스 중인 production 버전의 의도치 않은 상태에 즉각적으로 대응하기 위해 만들어졌습니다. production 버전에서 심각한 버그가 발견되어 즉각적으로 고쳐야 할때, hotfix 브랜치들은 해당 버그가 존재하는 master 브랜치에서 브랜치를 따서 만들어집니다.

이 hotfix branch 의 핵심은 한 사람이 production 을 고치고 있을때 다른 팀 구성원들이 지속적으로 일을 할 수 있다는 점입니다.

Create the hotfix branch

hotfix 브랜치들은 master브랜치로부터 만들어집니다. 예를 들면, version 1.2 가 현재 실서비스중인 production release 인데 심각한 버그가 문제를 일으키고 있다고 합시다. 그러나 develop 브랜치의 변경내역들은 아직 안정적이지 못합니다. 우리는 그때 (master브랜치로부터) hotfix 브랜치를 따서 문제를 고칠 수 있습니다.

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

브랜치를 딴 다음에 버전번호를 bump 하는 것을 잊지 맙시다!

그러면, 버그를 고치고 이 내용을 하나나 여러개의 분리된 커밋으로 commit 합니다.

$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

Finishing a hotfix branch

버그픽스를 모두 마쳤으면, 이 버그픽스는 master브랜치로 다시 머지되어고, 또한! 다음번 release 에도 반영될 수 있도록 develop브랜치로도 머지되어야 합니다. 이것은 완벽히 release 브랜치가 끝났을때랑 비슷한 작업입니다.

첫째, master에 머지하고 태그를 땁니다.

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

Edit : -s or -u 플래그를 써서 태그를 암호화할 수도 있습니다.

다음으로, 버그픽스를 develop에도 반영합니다.

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

이 규칙에 대해서 한가지 예외는 release branch 가 현재 존재할때는, hotfix change 들은 develop branch 가 아닌 그 release branch 에 merge 되어야 한다는 것입니다. 버그픽스를 release 브랜치에 backmerging 하는것은 결과적으로 release 브랜치가 끝날때 버그픽스들이 develop에 머지 될 것입니다. (만약 develop의 작업들이 이 버그픽스를 즉각적으로 반영되어야하고 release 브랜치가 finish 될때까지 기다릴 수 없다면, 당신이 버그픽스를 안적하게 develop으로 머지할 수 있습니다.)

마지막으로, 이 임시 브랜치(hotfix 브랜치)를 제거합니다.

$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

Summary

지금까지 살펴본 이 branching model 이 딱히 놀랍거나 특별해보이지 않을지 몰라도, 이 포스트의 처음 부분에 첨부된 “큰 그림”은 우리의 프로젝트에서 정말 엄청나게 유용했었습니다. 이것은 이해하기 쉬운 우아한 mental model(?)을 만들어주었고 팀구성원들이 모두가 branching과 releasing process에 대해 동일한 수준의 이해를 할 수 있도록 해주었습니다.

high-quality PDF 버전이 여기 있습니다. 가서 벽에 붙여놓고 언제나 참조하세요.

Update : 그리고 요청한 사람들을 위해: 여기 gitflow-model.src.key 메인 다이어그램 이미지 입니다.

댓글을 남겨주세요. 저에게 큰 힘이 됩니다^^