모든 내용은 https://git-scm.com/book를 참고하여 만들었습니다.
비공개 대규모 팀
이제 비공개 대규모 팀에서의 역할을 살펴보자. 이런 상황에는 보통 팀을 여러 개로 나눈다. 그래서 각각의 작은 팀이 서로 어떻게 하나로 Merge 하는지를 살펴본다.
John과 Jessica는 어떤 기능을 함께 작업하게 됐다. 물론 각각 다른 일도 한다. 이런 상황이라면 회사는 Integration-manager 워크플로를 선택하는 게 좋다. 작은 팀이 수행한 결과물은 Integration-Manager가 Merge 하고 공유 저장소의 master
브랜치를 업데이트한다. 팀마다 브랜치를 하나씩 만들고 Integration-Manager는 그 브랜치를 Pull 해서 Merge 한다.
두 팀에 모두 속한 Jessica의 작업 순서를 살펴보자. 우선 Jessica는 저장소를 Clone 하고 featureA
작업을 먼저 한다. featureA
브랜치를 만들고 수정하고 커밋한다.
# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
1 files changed, 1 insertions(+), 1 deletions(-)
이 수정한 부분을 John과 공유해야 한다. 공유하려면 우선 featureA
브랜치를 서버로 Push 한다. Integration-Manager만 master
브랜치를 업데이트할 수 있기 때문에 master
브랜치로 Push를 할 수 없고 다른 브랜치로 John과 공유한다.
$ git push -u origin featureA
...
To jessica@githost:simplegit.git
* [new branch] featureA -> featureA
Jessica는 자신이 한 일을 featureA
라는 브랜치로 Push 했다는 이메일을 John에게 보낸다. John의 피드백을 기다리는 동안 Jessica는 Josie와 함께 하는 featureB
작업을 하기로 한다. 서버의 master
브랜치를 기반으로 새로운 브랜치를 하나 만든다.
# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'
몇 가지 작업을 하고 featureB 브랜치에 커밋한다.
$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
1 files changed, 5 insertions(+), 0 deletions(-)
그럼 Jessica의 저장소는 그림 아래와 같다.
Figure 66. Jessica의 저장소.
작업을 마치고 Push 하려고 하는데 Jesie가 이미 일부 작업을 하고 서버에 featureBee 브랜치로 Push 했다는 이메일을 보내왔다.
Jessica는 Jesie의 작업을 먼저 Merge 해야만 Push 할 수 있다. Merge 하기 위해서 우선 git fetch
로 Fetch 한다.
$ git fetch origin
...
From jessica@githost:simplegit
* [new branch] featureBee -> origin/featureBee
Fetch 해 온 브랜치를 git merge
명령으로 Merge 한다.
$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
lib/simplegit.rb | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
Push 하려고 하는데 작은 문제가 생겼다. Jessica는 featureB
브랜치에서 작업을 했는데 서버에는 브랜치가 featureBee
라는 이름으로 되어 있다. 그래서 git push
명령으로 Push 할 때 로컬 브랜치 featureB
뒤에 콜론(:)과 함께 서버 브랜치 이름을 직접 지정해 준다.
$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
fba9af8..cd685d1 featureB -> featureBee
이것은 refspec 이란 것을 사용하는 것인데 Refspec 에서 자세하게 설명한다. 명령에서 사용한 -u
옵션은 --set-upstream
옵션의 짧은 표현인데 브랜치를 추적하도록 설정해서 이후 Push 나 Pull 할 때 좀 더 편하게 사용할 수 있다.
John이 몇 가지 작업을 하고 나서 featureA
에 Push 했고 확인해 달라는 내용의 이메일을 보내왔다. Jessica는 git fetch
로 Push 한 작업을 Fetch 한다.
$ git fetch origin
...
From jessica@githost:simplegit
3300904..aad881d featureA -> origin/featureA
어떤 것이 업데이트됐는지 git log
명령으로 확인한다.
$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date: Fri May 29 19:57:33 2009 -0700
changed log output to 30 from 25
확인을 마치면 로컬의 featureA
브랜치로 Merge 한다.
$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
lib/simplegit.rb | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
Jessica는 일부 수정하고, 수정한 내용을 다시 서버로 Push 한다.
$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
3300904..774b3ed featureA -> featureA
위와 같은 작업을 마치고 나면 Jessica의 저장소는 아래와 같은 모습이 된다.
Figure 67. 마지막 Push 하고 난 후의 Jessica의 저장소.
그럼 featureA
와 featureBee
브랜치가 프로젝트의 메인 브랜치로 Merge 할 준비가 되었다고 Integration-Manager에게 알려준다. Integration-Manager가 두 브랜치를 모두 Merge 하고 난 후에 메인 브랜치를 Fetch 하면 아래와 같은 모양이 된다.
Figure 68. 두 브랜치가 메인 브랜치에 Merge 된 후의 저장소.
수많은 팀의 작업을 동시에 진행하고 나중에 Merge 하는 기능을 사용하려고 다른 버전 관리 시스템에서 Git으로 바꾸는 조직들이 많아지고 있다. 팀은 자신의 브랜치로 작업하지만, 메인 브랜치에 영향을 끼치지 않는다는 점이 Git의 장점이다. 아래는 이런 워크플로를 나타내고 있다.
Figure 69. Managed 팀의 워크플로.
공개 프로젝트 Fork
비공개 팀을 운영하는 것과 공개 팀을 운영하는 것은 약간 다르다. 공개 팀을 운영할 때는 모든 개발자가 프로젝트의 공유 저장소에 직접적으로 쓰기 권한을 가지지는 않는다. 그래서 프로젝트의 관리자는 몇 가지 일을 더 해줘야 한다.
Fork를 지원하는 Git 호스팅에서 Fork를 통해 프로젝트에 기여하는 법을 예제를 통해 살펴본다. Git 호스팅 사이트(GitHub, BitBucket, repo.or.cz 등) 대부분은 Fork 기능을 지원하며 프로젝트 관리자는 보통 Fork 하는 것으로 프로젝트를 운영한다.
다른 방식으로 이메일과 Patch를 사용하는 방식도 있는데 뒤이어 살펴본다.
우선 처음 할 일은 메인 저장소를 Clone 하는 것이다. 그리고 나서 토픽 브랜치를 만들고 일정 부분 기여한다. 그 순서는 아래와 같다.
$ git clone (url)
$ cd project
$ git checkout -b featureA
# (work)
$ git commit
# (work)
$ git commit
Note
rebase -i
명령을 사용하면 여러 커밋을 하나의 커밋으로 합치거나 프로젝트의 관리자가 수정사항을 쉽게 이해하도록 커밋을 정리할 수 있다. 히스토리 단장하기 에서 대화식으로 Rebase 하는 방법을 살펴본다.
일단 프로젝트의 웹사이트로 가서 “Fork” 버튼을 누르면 원래 프로젝트 저장소에서 갈라져 나온, 쓰기 권한이 있는 저장소가 하나 만들어진다. 그러면 로컬에서 수정한 커밋을 원래 저장소에 Push 할 수 있다. 그 저장소를 로컬 저장소의 리모트 저장소로 등록한다. 예를 들어 myfork로 등록한다.
$ git remote add myfork (url)
자 이제 등록한 리모트 저장소에 Push 한다.
작업하던 것을 로컬 저장소의 master 브랜치에 Merge 한 후 Push 하는 것보다 리모트 브랜치에 바로 Push를 하는 방식이 훨씬 간단하다.
이렇게 하는 이유는 관리자가 토픽 브랜치를 프로젝트에 포함시키고 싶지 않을 때 토픽 브랜치를 Merge 하기 이전 상태로 master 브랜치를 되돌릴 필요가 없기 때문이다. (cherry-pick 명령은 Rebase와 Cherry-Pick 워크플로 에서 자세히 다룬다).
관리자가 토픽 브랜치를 Merge 하든 Rebase 하든 Cherry-Pick 하든지 간에 결국 다시 관리자의 저장소를 Pull 할 때는 토픽 브랜치의 내용이 들어 있을 것이다.
$ git push -u myfork featureA
Fork 한 저장소에 Push 하고 나면 프로젝트 관리자에게 이 내용을 알려야 한다. 이것을 ‘Pull Request’라고 한다. Git 호스팅 사이트에서 관리자에게 보낼 메시지를 생성하거나 git request-pull
명령으로 이메일을 수동으로 만들 수 있다. GitHub의 pull request
버튼은 자동으로 메시지를 만들어 주는데 관련 내용은 GitHub 에서 살펴볼 수 있다.
request-pull
명령은 아규먼트를 두 개 입력받는다. 첫 번째 아규먼트는 작업한 토픽 브랜치의 Base 브랜치이다.
두 번째는 토픽 브랜치가 위치한 저장소 URL인데 위에서 등록한 리모트 저장소 이름을 적을 수 있다. 이 명령은 토픽 브랜치 수정사항을 요약한 내용을 결과로 보여준다.
예를 들어 Jessica가 John에게 Pull 요청을 보내는 상황을 살펴보자. Jessica는 토픽 브랜치에 두 번 커밋을 하고 Fork 한 저장소에 Push 했다. 그리고 아래와 같이 실행한다.
$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40.
John Smith (1).
added a new function
are available in the git repository at.
git://githost/simplegit.git featureA
Jessica Smith (2).
add limit to log function
change log output to 30 from 25
lib/simplegit.rb | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
관리자에게 이 내용을 보낸다. 이 내용에는 토픽 브랜치가 어느 시점에 갈라져 나온 것인지, 어떤 커밋이 있는지, Pull 하려면 어떤 저장소에 접근해야 하는지에 대한 내용이 들어 있다.
프로젝트 관리자가 아니라고 해도 보통 origin/master
를 추적하는 master
브랜치는 가지고 있다. 그래도 토픽 브랜치를 만들고 일을 하면 관리자가 수정 내용을 거부할 때 쉽게 버릴 수 있다.
토픽 브랜치를 만들어서 주제별로 독립적으로 일을 하는 동안에도 주 저장소의 master 브랜치는 계속 수정된다.
하지만 주 저장소의 브랜치의 최근 커밋 이후로 Rebase 하면 깨끗하게 Merge 할 수 있다. 그리고 다른 주제의 일을 하려고 할 때는 앞서 Push 한 토픽 브랜치에서 시작하지 말고 주 저장소의 master 브랜치로부터 만들어야 한다.
$ git checkout -b featureB origin/master
# (work)
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
# (email maintainer)
$ git fetch origin
그림 5-16 처럼 각 토픽은 일종의 실험실이라고 할 수 있다. 각 토픽은 서로 방해하지 않고 독립적으로 수정하고 Rebase 할 수 있다.
Figure 70. featureB 수정작업이 끝난 직후 저장소의 모습.
프로젝트 관리자가 사람들의 수정 사항을 Merge 하고 나서 Jessica의 브랜치를 Merge 하려고 할 때 충돌이 날 수도 있다. 그러면 Jessica가 자신의 브랜치를 origin/master
에 Rebase 해서 충돌을 해결하고 다시 Pull Request을 보낸다.
$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA
위 명령들을 실행하고 나면 히스토리는 아래와 같아진다.
Figure 71. FeatureA에 대한 Rebase가 적용된 후의 모습.
브랜치를 Rebase 해 버렸기 때문에 Push 할 때 -f
옵션을 주고 강제로 기존 서버에 있던 featureA
브랜치의 내용을 덮어 써야 한다. 아니면 새로운 브랜치를(예를 들어 featureAv2
) 서버에 Push 해도 된다.
또 다른 시나리오를 하나 더 살펴보자. 프로젝트 관리자는 featureB 브랜치의 내용은 좋지만, 상세 구현은 다르게 하고 싶다. 관리자는 featureB
담당자에게 상세 구현을 다르게 해달라고 요청한다.
featureB
담당자는 하는 김에 featureB
브랜치를 프로젝트의 최신 master
브랜치 기반으로 옮긴다. 먼저 origin/master
브랜치에서 featureBv2
브랜치를 새로 하나 만들고, featureB
의 커밋들을 모두 Squash 해서 Merge 하고, 만약 충돌이 나면 해결하고, 상세 구현을 수정하고, 새 브랜치를 Push 한다.
$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
# (change implementation)
$ git commit
$ git push myfork featureBv2
--squash
옵션은 현재 브랜치에 Merge 할 때 해당 브랜치의 커밋을 모두 커밋 하나로 합쳐서 Merge 한다. 이 때 Merge 커밋은 만들지 않는다. 다른 브랜치에서 수정한 사항을 전부 가져오는 것은 똑같다.
하지만 새로 만들어지는 커밋은 부모가 하나이고 커밋을 기록하기 전에 좀 더 수정할 기회도 있다. 다른 브랜치에서 수정한 사항을 전부 가져오면서 그전에 추가적으로 수정할 게 있으면 수정하고 Merge 할 수 있다. 게다가 새로 만들어지는 커밋은 부모가 하나다. --no-commit
옵션을 추가하면 커밋을 합쳐 놓고 자동으로 커밋하지 않는다.
수정을 마치면 관리자에게 featureBv2 브랜치를 확인해 보라고 메시지를 보낸다.
Figure 72. featureBv2 브랜치를 커밋한 이후 저장소 모습.