백엔드 개발 블로그
Git, Git Flow, Github Flow 본문
Git
버전 관리 시스템(VCS)는 실수 혹은 기타 이유로 인해 코드가 손상되었을 때 손쉽게 복구할 수 있으며, 여러 사람이 협업 시 누가 어떤 부분을 수정하고 이슈를 만들어냈는지 쉽게 파악이 가능하다.
단순히 폴더나 파일을 날짜별로 만드는 것도 일종의 VCS라고 할 수 있지만, 사람이 실수할 여지가 많다. 이를 위해 RCS 등의 로컬 버전 관리 시스템이 개발되었다. 하지만 시스템 외부의 사람들과 함께 협업하기에는 로컬 버전 관리 시스템의 한계가 뚜렷했다.
이러한 기능을 목적으로 Subversion과 같은 VCS가 만들어졌는데, 이는 중앙집중식 버전 관리 시스템(CVCS)로서 버전 데이터베이스는 오직 한 곳의 서버에 저장되고, 협업자들은 각각 코드를 checkout해서 작업이 가능했다.
하지만 CVCS 또한 중앙 서버가 다운되거나, 서버에 오류가 생기면 프로젝트에 차질이 생기는 것은 피할 수가 없었다. 프로젝트의 모든 이력이 한 곳에만 있을 경우 생기는 문제는 자명했다.
DVCS
그리하여 탄생한 것이 Git과 Mercurial과 같은 분산 버전 관리 시스템(DVCS)이다. 사용자가 서버에서 checkout을 한다는 것은 프로젝트의 이력을 로컬로 동기화 및 백업한다는 것이다. 서버가 다운되거나 문제가 생겨도 local의 이력을 복사하면 그만이다. 또 DVCS는 다수의 원격 저장소를 가질 수가 있기 때문에 유연한 프로젝트 관리가 가능하다.
DVCS는 분산 환경이라는 특성상, 변경은 시간축에 따른 선형 구조가 아닌 DAG 그래프와 같은 모양새를 갖는다. 이와 같은 구조는 여러 협업자가 다른 사람의 현재 작업 여부에 관계없이 자유로운 작업 후 손쉬운 병합을 가능케 한다. 다만 이러한 DVCS에 경험이 없는 사람이라면 직관적으로 life cycle을 이해하기 어려울 수 있다.
여기서 mercurial과 git의 차이가 드러나는데, mercurial은 최대한 쉽고 간편하게 쓸 수 있도록 DVCS임에도 기존 Subversion과 유사한 면모를 보인다. 반면에 git은 설계부터 많은 수에 브랜치를 사용하는 데에 중점을 두었으며 n-way merge 등 브랜치를 효과적으로 제어할 수 있다. 당연히 git의 경우 가파른 learning curve를 보인다.
내부 작동 방식에 있어서도 둘 DVCS의 차이가 있다. Git은 각 commit마다 전체 코드 형상을 저장하는 스냅샷 방식인 반면에, Mercurial은 patch 방식, 즉 달라진 부분만 저장하는 방식으로 변경 이력을 저장하기 때문에 일반적으로 git이 disk overhead가 더 크다고 생각할 수 있다. 하지만 git은 어느정도 시간이 흐른 후 garbage collection을 실행하여 최신의 snapshot을 제외한 예전의 이력은 patch 형태로 가공, gzip으로 압축하고 최근의 commit만을 snapshot으로 저장하기 때문에 최신의 commit을 살펴보는 데에는 빠르면서도 저장소의 크기를 줄일 수 있다.
단, Git은 변경 이력을 수많은 파일로 쪼개어 저장하는데, Windows를 사용하는 경우 그러한 파일 액세스가 느려 성능이 매우 떨어진다. Git for Windows와 같은 프로젝트에서는 리눅스와는 별도의 파일 캐시 기능을 이용하여 성능을 빠르게 한다.
Git Flow
http://nvie.com/posts/a-successful-git-branching-model/
Vincent Driessen이 쓴 branching 모델을 알아보자.
기존에 널리 사용되던 git의 브랜치 모델을 정리한 것으로서, git의 효율적인 브랜치 관리와 병합을 이용하여 프로젝트를 손쉽게 관리할 수 있다. Feature 개발과 release 및 버그 수정을 분리하여, 각 개발자는 repo의 release cycle을 기다릴 필요 없이 자신이 맡은 역할에 집중할 수 있다는 것이 장점이다. 각 브랜치의 병합 과정을 통해 프로젝트의 life cycle를 한눈에 볼 수도 있다.
우선 origin에 항상 존재하는 브랜치(main branch)는 origin/master
와 origin/develop
두 가지가 있다. master
는 항상 production-ready 상태여야 한다. 프로덕션 서버에 훅을 걸어 master에 푸쉬되면 자동으로 실서버에 deploy해도 될 정도를 말한다. develop
은 다음 release를 위한 코드가 모이는 곳이다. 혹자는 'integration branch'라고도 하며 nightly build는 항상 develop을 기반으로 한다.
위의 두 main branch를 보조하는 브랜치, supporting branch에는 다음의 3가지가 있다.
Feature Branches
다음 릴리즈 혹은 먼 후의 릴리즈를 위해 특정 기능을 개발하는 브랜치이다. 기능이 완성되기 전까지는 origin
에 남아있다가 develop
에 merge 되거나 혹은 어떤 사정에 의해 버려질 수 있다. 항상 develop
브랜치를 기반으로 해야하고 마지막엔 develop
브랜치에 merge 되어야 한다.
master
, develop
, release-*
, hotfix-*
를 제외한 그 어떤 이름이라도 좋다. Feature branch는 보통 origin에 없고 개발자의 레포에 존재한다. develop
에 병합할 때에는 반드시 git merge --no-ff
를 사용하자. Fast forward가 가능하면 merge commit이 남지 않는데, feature branch의 기록을 남기기 위해 fast forward를 막고 항상 commit을 남긴다.
- 기본 브랜치:
develop
- 병합 브랜치:
develop
- 네이밍 컨벤션:
master
,develop
,release-*
,hotfix-*
제외한 그 어떤 이름(feature-*
또한 많이 쓰임)
$ git checkout -b myfeature develop
$ 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
Release Branches
다음 릴리즈를 위해 코드 메타데이터(버전 넘버 등)을 수정하고 마이너 버그를 고칠 수 있는 브랜치이다. 기존의 release candidate이라고 볼 수 있다.
- 기본 브랜치:
develop
- 병합 브랜치:
develop
,master
- 네이밍 컨벤션:
release-*
$ 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(-)
릴리즈를 위한 메타 데이터를 수정하고 마이너 버그를 모두 고쳐 릴리즈할 준비가 되었을 때 develop
과 master
브랜치에 병합한다. master
브랜치를 기반으로 태그를 생성한다.
$ 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
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
이후 해당 브랜치를 삭제한다.
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).
Hotfix Branches
Hotfix 브랜치는 새로운 프로덕션 릴리즈를 준비하는 브랜치라는 점에서 release 브랜치와 유사하지만, 기존에 계획되지 않는다는 점이 다르다. 프로덕션 시스템이 잘 작동하지 않을 때 즉시 대응하기 위해 만들어진다. 기존 develop 브랜치는 hotfix의 진행상황을 신경쓰지 않고 계속 개발할 수 있다는 것이 장점이다.
- 기본 브랜치:
master
- 병합 브랜치:
develop
,master
- 네이밍 컨벤션:
hotfix-*
기본 브랜치가 master branch라는 점을 제외하면, 작업과 병합 과정은 release branch와 동일하다.
Git Flow Extension
위의 git flow를 손쉽게 사용할 수 있도록 구현한 extension이 있다.
https://github.com/petervanderdoes/gitflow-avh
Mac OS에서는 homebrew를 이용해 아래와 같이 설치할 수 있다.
$ brew install git-flow-avh
# 기존 git repo에서 git flow에 맞게 브랜치 생성
$ git flow init
# develop에서 새 feature 브랜치를 생성 후 전환
$ git flow feature start MYFEATURE
# MYFEATURE 브랜치를 develop에 병합 후 삭제
$ git flow feature finish MYFEATURE
# origin에 MYFEATURE를 게시하여 공동 작업자와 공유
$ git flow feature publish MYFEATURE
# origin에서 MYFEATURE를 pull
$ git flow feature pull MYFEATURE
# develop(혹은 BASE commit)에서 새 release 브랜치를 생성 후 전환
$ git flow release start RELEASE [BASE]
# release 공유
$ git flow release publish RELEASE
# release를 master에 병합 뒤 태그 생성. develop에 재병합 뒤 삭제
$ git flow release finish RELEASE
# 태그는 별도로 push해야 함
$ git push --tags
# hotfix start
$ git flow hotfix start VERSION [BASE]
# hotfix finish
$ git flow hotfix finish VERSION
Github Flow
Git Flow가 등장한 시기(2010)에는 유용한 브랜치 모델이고 실제로 많이 쓰이던 전략이었지만,
아래와 같은 현재의 트렌드에서는 적합하지 않은 전략일 수 있다.
- Github의 Pull Request를 대중적으로 사용한다.
- Web App이 유행하게 되면서 여러 버전을 동시에 관리하는 경우가 거의 없다.
- Continuous Delivery를 통해 자동 배포를 수행한다.
- 복잡한 브랜치 기반의 롤백이 불필요하다.
대신 Github Flow를 많이 사용한다.
master 브랜치 하나만을 고정하여 사용하고 나머지 브랜치는 feature 브랜치와 같이 자유롭게 사용하며,
Github의 PR 기능을 적극 활용하는 방식이다.
https://docs.github.com/en/get-started/quickstart/github-flow
Github flow와 유사하지만 production 브랜치를 따로 두는 GitLab flow 또한 있다.