브랜치 관리
git branch : 아무런 옵션없이 실행시 브랜치의 목록을 보여준다.
$ git branch
iss53
* master
testing
-v 옵션 : 브랜치마다 마지막 커밋 메시지도 함께 보여준다.
$ git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
--merged 옵션 : 현재 checkout 한 브랜치를 기준으로 이미 Merge 한 브랜치 목록 확인
- * 기호가 붙어 있지 않은 브랜치는 git brandch -d 명령으로 삭제해도 되는 브랜치이다.
$ git branch --merged
iss53
* master
--no-merged 옵션 : 현재 checkout한 브랜치에 Merge하지 않은 브랜치 목록 확인
$ git branch --no-merged
testing
Merge하지 않은 커밋을 담고 있기 때문에 브랜치 삭제가 되지 않는다.
- -D 옵션 : 브랜치를 강제로 삭제하려면 이 옵션을 사용한다.
$ git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.
testing 브랜치 위치에서 master 브랜치 기준으로 머지하지 않은리스트를 보려면 다음과 같이 하면 된다.
$ git checkout testing
$ git branch --no-merged master
topicA
featureB
브랜치 Workflow
Long-Running 브랜치
Git은 3-way Merge를 사용하므로 장기간 걸쳐서 한 브랜치를 다른 브랜치와 여러번 Merge 하는 것도 가능하다.
- master 브랜치 : 배포할 코드만 Merge 해서 안정된 코드만 둔다.
- develop 또는 next 브랜치 : 개발을 진행하고 안정화하는 브랜치. 테스트를 거쳐서 안정적이라고 판단되면 master 브랜치에 Merge 한다.
- topic 브랜치 : 짧은 호흡브랜치
개발 브랜치는 공격적으로 히스토리를 만들어 나가아가고 안정 브랜치는 이미 만든 히스토리를 뒤따르며 나아간다.
- 이 흐름은 실험실에서 충분히 테스트하고 실전에 배치하는 과정과 비슷하다.
코드를 여러 단계로 나누고 안정성을 높이면서 운영해 갈 수 있다. 큰 규모의 프로젝트라면 proposed 혹은 pu(proposed updates) 라는 이름의 브랜치를 두어 next나 master 브랜치에 아직 Merge할 준비가 되지 않은 것을 일단 Merge 시킨다.
중요한 개념은 브랜치를 이용해 여러단계에 걸쳐서 안정화해 나아가면서 충분히 안정화 됐을 때 안정 브랜치로 Merge한다는 점이다.
토픽 브랜치
앞서 사용한 iss53, hotfix 와 같이 어떤 한가지 주제나 작업을 위해 만든 짧은 브랜치를 말한다.
master 브랜치를 checktout한 상태에서 어떤 작업을 한다고 해보자.
- 한 이슈를 처리하기 위해서 iss91 브랜치를 만들고 해당 작업을 한다.
- 같은 이슈를 다른 방법으로 해결해 보고 싶을 때도 있다. iss92v2 브랜치를 만들고 다른 방법을 시도해 본다.
- 확신할 수 없는 아이디어를 적용해 보기 위해 다시 master 브랜치로 되돌아가서 dumbida 브랜치를 하나 더 만든다.
이슈를 처리했던 방법 중 두 번째 방법인 iss91v2 브랜치가 괜찮아서 적용하기로 결정을 내렸다.
그리고 아이디어를 확신할 수 없었던 dumbidea 브랜치를 같이 하는 개발자에게 보여줬더니 썩 괜찮다는 방응을 얻었다.
iss91(C5, C6 커밋도 함께) 브랜치는 버리고 다른 두 브랜치를 Merge 하면 다음과 같다.
지금까지 한 작업은 전부 로컬에만 처리한다는 것을 꼭 기억하자. 로컬 저장소에서만 브랜치를 만들고 Merge했으며 서버와 통신을 주고 받는 일은 없었다.
리모트 브랜치
리모트 브랜치란 리모트 저장소에 있는 브랜치를 말한다.
- 사실 리모트 브랜치도 로컬에 있지만 멋대로 옮기거나 할 수 없고 리모트 저장소와 통신하면 자동으로 업데이트 된다.
- 리모트 브랜치는 브랜치 상태를 알려주는 책갈피라고 볼 수 있다.
- 이 책갈피로 리모트 저장소에서 마지막으로 데이터를 가져온 시점의 상태를 알 수 있다.
리모트 브랜치의 형식은 (remote)/(branch) 형식으로 되어 있다.
- origin의 master 브랜치를 보고 싶다면 orgin/master라는 이름으로 브랜치를 확인하면 된다.
- 다른 팀원과 함께 어떤 이슈를 구현할 때
- 팀원이 iss53 브랜치를 서버로 Push 했고, 당신도 iss53 브랜치가 있다고 가정하자
- 이때 서버가 가리키는 iss53 브랜치는 로컬에서 origin/iss53이 가리키는 커밋이다.
git.ourcompany.com Git 서버가 있다
- 이 서버의 저장소를 Clone하면 Git은 자동으로 origin 이라는 이름을 붙인다.
- origin으로부터 저장소 데이터를 모두 내려받고 master 브랜치를 가리키는 포인터를 만든다.
- 이 포인터는 origin/master라고 부르고 멋대로 조종할 수 없다.
- Git 로컬의 master 브랜치가 origin/master를 가리키게 한다.
- 이제 이 master 브랜치에서 작업을 시작할 수 있다.
로컬 저장소에서 어떤 작업을 하고 있는데 동시에 다른 팀원이 git.ourcompany.com 서버에 Push 하고 master 브랜치를 업데이트 한다.
- Git Local에는 서버 저장소로부터 어떤 데이터도 주고 받지 않아서 orgin/master 포인터는 그대로이다.
git fetch origin : 리모트 서버로 부터 저장소를 동기화 한다.
- 우선 origin 서버의 주소 정보(git.ourcompany.com)를 찾는다.
- 현재 로컬의 저장소가 갖고 있지 않은 새로운 정보가 있으면 모두 내려받고, 받은 데이터를 로컬 저장소에 업데이트한다.
- 그러고 나서 origin/master 포인터의 위치를 최신 커밋으로 이동시킨다.
리모트 저장소를 여러 개 운영하는 상황을 이해할 수 있도록 개발용으로 사용할 Git 저장소를 팀 내부에 하나 더 추가해보자.
git.team1.ourcompany.com 서버를 현재 작업 중인 프로젝트에 팀의 저장소를 추가한다.
- git remote add teamone git://git.team1.ourcompany.com을 실행한다.
git fetch teamone으로 teamone 서버의 데이터를 내려 받는다.
- 명령을 실행해도 teamone 서버의 데이터는 모두 origin 서버에도 있는 것들이서 아무것도 내려 받지 않는다.
- 해시가 31b8e인 커밋은 origin 서버에도 있다.
- 하지만, 이 명령은 teamone/master 브랜치가. teamone 서버의 master 브랜치가 가리키는 커밋을 가리키게 한다.
- Git 로컬에 있는 31b8e 커밋에 teamone/master 브랜치가 가르키게 하도록 한다.
Push하기
다음과 같이 명령을 사용한다. git push (remote) (branch)
git push origin serverfix 의 의미
- Git은 serverfix 브랜치 이름을 refs/heads/serverfix:refs/heads/serverfix로 확장한다.
- refs/heads 의 의미는 나중에 자세히 알아본다.
- 이것은 serverfix 로컬 브랜치를 서버로 Push하는데 리모트의 serverfix 브랜치로 업데이트 한다는 것을 의미한다.
- git push origin serverfix:serverfix 도 같은 의미이다.
- 로컬 브랜치의 이름과 리모트 서버의 브랜치 이름이 다를 때 필요하다.
- 예) git push origin serverfix:awesomebranch
- 로컬 브랜치의 이름과 리모트 서버의 브랜치 이름이 다를 때 필요하다.
$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
* [new branch] serverfix -> serverfix
나중에 누군가 저장소를 Fetch하고 나서 서버에 있는 serverfix 브랜치를 접근할 때 origin/serverfix라는 이름으로 접근할 수 있다.
- Fetch 명령으로 serverfix 브랜치가 생기는 것이 아니라 수정 못하는 origin/serverfix 브랜치 포인터가 생기는 것이다.
$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
* [new branch] serverfix -> origin/serverfix
새로 받은 브랜치의 내용을 Merge 하려면 git merge origin/serverfix 명령을 사용한다.
git checkout -b serverfix origin/serverfix : Merge 하지 않고 리모트 브랜치에서 시작하는 새 브랜치를 만들려면 다음과 같은 명령을 사용한다.
- origin/serverfix에서 시작하고 수정할 수 있는 serverfix 라는 로컬 브랜치가 만들어진다.
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
브랜치 추적
트래킹 브랜치는 리모트 브랜치와 직접적인 연결고리가 있는 로컬 브랜치이다.
- 리모트 브랜치를 로컬 브랜치로 Checkout하면 자동으로 트래킹(Tracking) 브랜치가 만들어진다.
- 트래킹 브랜치에서 git push 명령을 내려도 Git은 연결고리가 있어서 어떤 리모트 저장소에 Push해야 하는 지 알 수 있다.
- git pull 명령을 내리면 리모트 저장소로 부터 데이터를 내려받아 연결된 리모트 브랜치와 자동으로 Merge한다.
- 서버로 부터 저장소를 Clone해올 때도 Git은 자동으로 master 브랜치를 origin/master 브랜치의 트래킹 브랜치로 만든다.
- 그래서 git push, git pull 명령에 추가적인 argument 없이도 동작한다.
트래킹 브랜치는 직접 만들 수도 있다.
- origin/master 뿐만 아니라 다른 저장소의 다른 브랜치도 추적(Tracking)하게 할 수 있다.
- git checkout -b [branch] [remotename]/[branch] 명령으로 트래킹 브랜치를 만들수 있다.
- --track 옵션 : Git 1.6.2 이상에서는 -b 대신 해당 옵션도 가능하다.
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
리모트 브랜치와 다른 이름으로 브랜치를 만들려면 로컬 브랜치의 이름을 다음과 같이 다르게 지정한다.
- 이제 sf 브랜치에서 Push나 Pull하면 자동으로 origin/serverfix에 데이터를 보내가나 가져온다.
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
리모트 브랜치 삭제
동료와 협업하기 위해 리모트 브랜치를 만들었다가 작업을 마치고 master 브랜치로 Merge 했다. 협업하는 데 사용했던 그 리모트 브랜치는 이제 안정화 됐으므로 삭제할 수 있다.
git push [remotename] :[branch] : serfix라는 리모트 브랜치를 삭제하려면 다음과 같이 실행한다.
- git push [remotename] [localbranch]:[branch] 형식으로 일단 기억하자
- [localbranch] 부분을 비워둔 채로 실행하면 '로컬에서 빈 내용을 리모트의 [remotebranch]에 채워넣어라' 라는 뜻이 된다.
$ git push origin :serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
Rebase하기
한 브랜치에서 다른 브랜치로 합치는 방법은 Merge와 Rebase가 있다.
Rebase의 기초
우선 두개의 나뉘어진 브랜치가 있다.
Merge일 경우는 다음과 같이 만들어진다.
$ git checkout master
$ git merge experiment
Rebase는 다음과 같은 명령을 사용한다.
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Rebase로 머지 되는 과정은 다음과 같다.
- 일단 두 브랜치가 나뉘기 전인 공통 커밋(C2)으로 이동한다.
- 공통 커밋(C2)부터 지금 Checkout한 브랜치가 가리키는 커밋(exprement)까지 diff를 차례로 만들어 어딘가에 임시로 저장해 놓는다.
- Rebase할 브랜치(experiment) 가 합칠 브랜치(master)가 가리키는 커밋을 가리키게 한다.
- 아까 저장해 놓았던 변경사항을 차례대로 적용한다.
- 그 후 master 브랜치를 Fast-forward시킨다.
C3' 로 표시된 내용은 Merge 예제에서 살펴본 C5 커밋에서의 내용과 같을 것이다. Merge이든 Rebase든 둘 다 합치는 관점에서는 서로 다를 게 없다. 하지만 Rebase가 좀 더 깨끗한 히스토리를 만든다. Rebase한 브랜치의 Log를 살펴보면 히스토리가 선형적이다. 일을 병렬로 동시에 진행해도 Rebase하고 나면 모든 작업은 차례대로 수행된 것처럼 보인다.
Rebase는 보통 리모트 브랜치에 커밋을 깔끔하게 적용하고 싶을 때 사용한다. 아마 이렇게 Rebase하는 리모트 브랜치는 직접 관리하는 것이 아니라 그냥 참여하는 브랜치일 것이다. 메인 프로젝트에 패치를 보낼 준비가 되면 하는 것이 Rebase이니까 브랜치에서 하던 일을 완전히 마치고 origin/master로 Rebase한다. 프로젝트 관리자는 어떠한 통합작업도 필요없다. 그냥 master 브랜치를 Fast-foward시키면 된다.
Rebase를 하던지, Merge를 하든지 최종 결과물은 같고 커밋 히스토리만 다르다는 것이 중요하다. Rebase의 경우는 브랜치의 변경사항을 순서대로 다른 브랜치에 적용하면서 합치고 Merge의 경우는 두 브랜치의 최종 결과만을 가지고 합친다.
좀 더 Rebase
Rebase는 단순히 브랜치를 합치는 것만 아니라 다른 용도로도 사용할 수 있다.
다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치 같은 히스토리가 있다고 하자.
- server 브랜치를 만들어서 서버 기능을 추가하고
- 그 브랜치에서 다시 client 브랜치를 만들어 클라이언트 기능을 추가한다.
- 마지막으로 server 브랜치로 돌아가서 몇 가지 기능을 더 추가한다.
이때 테스트가 덜 된 server 브랜치는 그대로 두고 client 브랜치만 master 로 합치려는 상황을 생각해보자.
- server 와는 아무 관련이 없는 client 커밋은 C8, C9 이다.
- 이 두 커밋을 master 브랜치에 적용하기 위해서 --onto 옵션을 사용하여 아래와 같은 명령을 실행한다
$ git rebase --onto master server client
이 명령은 master 브랜치부터 server 브랜치와 client 브랜치의 공통 조상까지의 커밋을 client 브랜치에서 없애고 싶을 때 사용한다. client 브랜치에서만 변경된 패치를 만들어 master 브랜치에서 client 브랜치를 기반으로 새로 만들어 적용한다. 조금 복잡하긴 해도 꽤 쓸모 있다.
이제 master 브랜치로 돌아가서 Fast-forward 시킬 수 있다
$ git checkout master
$ git merge client
server 브랜치의 일이 다 끝나면 git rebase [basebranch] [topicbranch] 라는 명령으로 Checkout 하지 않고 바로 server 브랜치를 master 브랜치로 Rebase 할 수 있다. 이 명령은 토픽(server) 브랜치를 Checkout 하고 베이스(master) 브랜치에 Rebase 한다.
$ git rebase master server
server 브랜치의 수정사항을 master 브랜치에 적용했다.
그리고 나서 master 브랜치를 Fast-forward 시킨다.
$ git checkout master
$ git merge server
모든 것이 master 브랜치에 통합됐기 때문에 더 필요하지 않다면 client 나 server 브랜치는 삭제해도 된다. 브랜치를 삭제해도 커밋 히스토리는 최종 커밋 히스토리 같이 여전히 남아 있다.
$ git branch -d client
$ git branch -d server
Rebase의 위험성
이미 공개 저장소에 Push한 커밋을 Rebase하지 마라
이 주의 사항을 지키지 않으면 사람들에게 욕을 먹을 것이다.
Rebase는 기존의 커밋을 그대로 사용하는 것이 아니라 내용은 같지만 다른 커밋을 새로 만든다. 새 커밋을 서버에 Push하고 동료 중 누군가가 그 커밋을 Pull해서 작업을 한다고 하자. 그런데 그 커밋을 git rebase로 바꿔서 Push해버리면 동료가 다시 Push했을 때 동료는 다시 Merge해야 한다. 다시 동료가 다시 Merge 한 내용을 Pull하면 내 코드는 정말 엉망이 된다.
이미 공개 저장소에 Push한 커밋을 Rebase하면 어떤 결과가 초래되는지 예제를 통해 알아보자.
중앙 저장소에서 Clone하고 일부 수정을 하면 커밋 히스토리는 다음과 같다.
이제 팀원 중 누군가 커밋, Merge 하고 나서 서버에 Push한다. 이 리모트 브랜치를 Fetch, Merge 하면 다음과 같다.
그런데 Push 했던 팀원은 Merge 한 일을 되돌리고 다시 Rebase 한다. 서버의 히스토리를 새로 덮어씌우려면 git push --force 명령을 사용해야 한다. 이후에 저장소에서 Fetch 하고 나면 아래 그림과 같은 상태가 된다.
기존 커밋이 사라졌기 때문에 이미 처리한 일이라고 해도 다시 Merge 해야 한다. Rebase는 커밋의 SHA-1 해시를 바꾸기 때문에 Git은 새로운 커밋으로 생각한다. 사실 C4는 이미 히스토리에 적용되어 있지만, Git은 모른다.
다른 개발자와 계속 일하려면 이런 Merge도 해야만 한다. Merge하면 C4와 C4' 커밋 둘 다 히스토리에 남게 된다. 실제 내용과 메시지가 같지만 SHA-1 해시 값이 전혀 다르다. git log로 히스토리를 확인해보면 저자, 커밋 날짜, 메시지가 같은 커밋이 두개 있을 것이다. 이렇게 되면 혼란스럽다. 게다가 이 히스토리를 서버에 Push하면 같은 커밋이 두 개 있기 때문에 다른 사람들도 혼란 스러워한다.
Push하긴 전에 정리하려고 Rebase 하는 것은 괜찮다. 또 절대 공개하지 않고 혼자 Rebase하는 경우도 괜찮다. 하지만 이미 공개하여 사람들이 사용하는 커밋을 Rebase하면 틀림없이 문제가 생길 것이다.
'책 > 프로 Git' 카테고리의 다른 글
분산 환경에서의 Git-3 - 프로젝트에 기여하기 - 비공개 대규모 팀 (0) | 2020.07.28 |
---|---|
분산 환경에서의 Git-1 - 분산 환경에서의 Workflow (0) | 2020.07.28 |
Git 브랜치-1 (0) | 2020.07.27 |
Git의 기초 (0) | 2020.07.26 |
Git의 시작 (0) | 2020.07.24 |