본문 바로가기
Book Study/Pro Git

[Pro Git] ch03 - Git 브랜치 (branch, merge)

by 랩린안 2022. 10. 2.

출처 : Pro Git (p75 ~p92)

- 저자 : Scott Chacon, Ben Straub

 

기존에 헷갈리던 게 이해가 된 부분만 기록합니다. 중간에 비어있는 부분들이 있습니다.

빨간글씨로 보충 설명을 적습니다. 정확하지 않을 수 있습니다.

 

 

모든 버전 관리 시스템은 브랜치를 지원한다. 개발을 하다 보면 코드를 여 러 개로 복사해야 하는 일이 자주 생긴다. 코드를 통째로 복사하고 나서 원래 코드와는 상관없이 독립적으로 개발을 진행할 수 있는데, 이렇게 독립적으로 개발하는 것이 브랜치다. 사람들은 브랜치 모델이 Git의 최고의 장점이라고, Git이 다른 것들과 구분 되는 특징이라고 말한다. 당최 어떤 점이 그렇게 특별한 것일까. Git의 브랜치 는 매우 가볍다. 순식간에 브랜치를 새로 만들고 브랜치 사이를 이동할 수 있 다. 다른 버전 관리 시스템과는 달리 Git은 브랜치를 만들어 작업하고 나중에 Merge 하는 방법을 권장한다. 심지어 하루에 수십 번씩해도 괜찮다. Git 브랜 치에 능숙해지면 개발 방식이 완전히 바뀌고 다른 도구를 사용할 수 없게 된 다.

 

브랜치란 무엇인가

Git이 브랜치를 다루는 과정을 이해하려면 우선 Git이 데이터를 어떻게 저장 하는지 알아야 한다. Git은 데이터를 Change Set이나 변경사항으로 기록하지 않고 일련의 스냅샷으로 기록한다는 것을 Chapter 1 에서 보여줬다. 커밋하면 Git은 현 Staging Area에 있는 데이터의 스냅샷에 대한 포인터, 저자나 커밋 메시지 같은 메타데이터, 이전 커밋에 대한 포인터 등을 포함하는 커밋 개체(커밋 Object)를 저장한다.

 

Git의 브랜치는 커밋 사이를 가볍게 이동할 수 있는 어떤 포인터 같은 것이 다. 기본적으로 Git은 master 브랜치를 만든다. 최초로 커밋하면 Git은 master 라는 이름의 브랜치를 만든다. 커밋을 만들 때 마다 브랜치가 자동으로 가장 마지막 커밋을 가리키게 한다.

 

 

Git 버전 관리 시스템에서 “master” 브랜치는 특별하지 않다. 다른 브랜치와 다른 것이 없다. 다만 모든 저장소에서 “master” 브랜치가 존재하는 이유는 git init 명령으로 초기화할 때 자동으로 만들어진 이 브랜치를 애써 다른 이름으로 변경하지 않기 때문이다

 

> 이제는 기본 브랜치명은 main으로 바뀌었다. master는 인종차별적 요소를 가지고 있기 때문이다.

 

브랜치와 커밋 히스토 리

새 브랜치 생성하기 브랜치를 하나 새로 만들면 어떨까. 아래와 같이 git branch 명령으로 testing 브랜치를 만든다.

 

$ git branch testing

 

새로 만든 브랜치도 지금 작업하고 있던 마지막 커밋을 가리킨다.

 

한 커밋 히스토리를 가리키는 두 브랜치

 

 

지금 작업 중인 브랜치가 무엇인지 Git은 어떻게 파악할까. 다른 버전 관리 시스템과는 달리 Git은 HEAD라는 특수한 포인터가 있다. 이 포인터는 지금 작 업하는 로컬 브랜치를 가리킨다. 브랜치를 새로 만들었지만, Git은 아직 master 브랜치를 가리키고 있다. git branch 명령은 브랜치를 만들기만 하고 브랜치를 옮기지 않는다.

 

현재 작업 중인 브랜 치를 가리키는 HEAD

git log 명령에 --decorate 옵션을 사용하면 쉽게 브랜치가 어떤 커밋을 가리키는지도 확인할 수 있다.

 

$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project

“master”와 “testing” 이라는 브랜치가 f30ab 커밋 옆에 위치하여 이런식으 로 브랜치가 가리키는 커밋을 확인할 수 있다.

 

 

브랜치를 이동하면 워킹 디렉토리의 파일이 변경된다 브랜치를 이동하면 워킹 디렉토리의 파일이 변경된다는 점을 기억해두어야 한다. 이 전에 작업했던 브랜치로 이동하면 워킹 디렉토리의 파일은 그 브랜치에서 가장 마지 막으로 했던 작업 내용으로 변경된다. 파일 변경시 문제가 있어 브랜치를 이동시키 는게 불가능한 경우 Git은 브랜치 이동 명령을 수행하지 않는다.

프로젝트 히스토리는 분리돼 진행한다(Figure 3-9). 우리는 브랜치를 하나 만들어 그 브랜치에서 일을 좀 하고, 다시 원래 브랜치로 되돌아와서 다른 일 을 했다. 두 작업 내용은 서로 독립적으로 각 브랜치에 존재한다. 커밋 사이를 자유롭게 이동하다가 때가 되면 두 브랜치를 Merge 한다. 간단히 branch, checkout, commit 명령을 써서 말이다.

 

갈라지는 브랜치

git log 명령으로 쉽게 확인할 수 있다. 현재 브랜치가 가리키고 있는 히 스토리가 무엇이고 어떻게 갈라져 나왔는지 보여준다. git log --oneline --decorate --graph --all이라고 실행하면 히스토리를 출력한다.

 

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

실제로 Git의 브랜치는 어떤 한 커밋을 가리키는 40글자의 SHA-1 체크섬 파 일에 불과하기 때문에 만들기도 쉽고 지우기도 쉽다. 새로 브랜치를 하나 만 드는 것은 41바이트 크기의 파일을(40자와 줄 바꿈 문자) 하나 만드는 것에 불 과하다. 브랜치를 만들어야 하면 프로젝트를 통째로 복사해야 하는 다른 버전 관리 도구와 Git의 차이는 극명하다.

 

통째로 복사하는 작업은 프로젝트 크기에 따 라 다르겠지만 수십 초에서 수십 분까지 걸린다. 그에 비해 Git은 순식간이다. 게다가 커밋을 할 때마다 이전 커밋의 정보를 저장하기 때문에 Merge 할 때 어 디서부터(Merge Base) 합쳐야 하는지 안다. 이런 특징은 개발자들이 수시로 브랜치를 만들어 사용하게 한다. 이제 왜 그렇게 브랜치를 수시로 만들고 사용해야 하는지 알아보자

 

브랜치와 Merge 의 기초

 

실제 개발과정에서 겪을 만한 예제를 하나 살펴보자. 브랜치와 Merge는 보통 이런 식으로 진행한다.

 

1. 작업 중인 웹사이트가 있다.

2. 새로운 이슈를 처리할 새 Branch를 하나 생성한다.

3. 새로 만든 Branch에서 작업을 진행한다.

 

이때 중요한 문제가 생겨서 그것을 해결하는 Hotfix를 먼저 만들어야 한다. 그러면 아래와 같이 할 수 있다.

제품 사용 중에 발생하는 버그의 수정이나 취약점 보완, 또는 성능 향상을 위해 긴급히 배포되는 패치 프로그램

 

 

1. 새로운 이슈를 처리하기 이전의 운영(Production) 브랜치로 이동한다.

2. Hotfix 브랜치를 새로 하나 생성한다.

3. 수정한 Hotfix 테스트를 마치고 운영 브랜치로 Merge 한다.

4. 다시 작업하던 브랜치로 옮겨가서 하던 일 진행한다.

 

브랜치의 기초

 

먼저 지금 작업하는 프로젝트에서 이전에 커밋을 몇 번 했다고 가정한다.

 

현재 커밋 히스토리

이슈 관리 시스템에 등록된 53번 이슈를 처리한다고 하면 이 이슈에 집중 할 수 있는 브랜치를 새로 하나 만든다. 브랜치를 만들면서 Checkout까지 한 번에 하려면 git checkout 명령에 -b라는 옵션을 추가한다.

 

$ git checkout -b iss53
Switched to a new branch "iss53"

위 명령은 아래 명령을 줄여놓은 것이다.

 

$ git branch iss53
$ git checkout iss53

 

브랜치 포인터를 새로 만듦

 

iss53 브랜치를 Checkout 했기 때문에(즉, HEAD는 iss53 브랜치를 가리킨다)

뭔가 일을 하고 커밋하면 iss53 브랜치가 앞으로 진행한다.

 

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

다른 상황을 가정해보자. 만드는 사이트에 문제가 생겨서 즉시 고쳐야 한 다. 버그를 해결한 Hotfix에 iss53이 섞이는 것을 방지하기 위해 iss53과 관련 된 코드를 어딘가에 저장해두고 원래 운영 환경의 소스로 복구해야 한다. Git 을 사용하면 이런 노력을 들일 필요 없이 그냥 master 브랜치로 옮기면 된다.

 

그렇지만, 브랜치를 이동하려면 해야 할 일이 있다. 아직 커밋하지 않은 파 일이 Checkout 할 브랜치와 충돌 나면 브랜치를 변경할 수 없다. 브랜치를 변 경할 때에는 워킹 디렉토리를 정리하는 것이 좋다. 이런 문제를 다루는 방법 은(주로, Stash이나 커밋 Amend에 대해) 나중에 “Stashing과 Cleaning” 에서 다룰 것이다. 지금은 작업하던 것을 모두 커밋하고 master 브랜치로 옮긴다.

 

 

 git checkout master
Switched to branch 'master'

 

이때 워킹 디렉토리는 53번 이슈를 시작하기 이전 모습으로 되돌려지기 때 문에 새로운 문제에 집중할 수 있는 환경이 만들어진다. Git은 자동으로 워킹 디렉토리에 파일들을 추가하고, 지우고, 수정해서 Checkout 한 브랜치의 마지 막 스냅샷으로 되돌려 놓는다는 것을 기억해야 한다. 이젠 해결해야 할 핫픽스가 생겼을 때를 예를 들어보자. hotfix라는 브랜치 를 만들고 새로운 이슈를 해결할 때까지 사용한다.

 

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'

[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)

master 브랜치에서 갈라져 나온 hotfix 브랜치

운영 환경에 적용하려면 문제를 제대로 고쳤는지 테스트하고 master 브랜 치에 합쳐야 한다. git merge 명령으로 아래와 같이 한다.

 

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Merge 메시지에서 “fast-forward”가 보이는가. Merge 할 브랜치가 가리키는 커밋이 현 브랜치 커밋의 Upstream 브랜치이기 때문에 master 브랜치 포인터 는 최신 커밋으로 이동한다. 이런 Merge 방식을 Fast forward라고 부른다. 다시 말해서 A 브랜치에서 다른 B 브랜치를 Merge 할 때 B가 A 이후의 커밋을 가리 키고 있으면 그저 A가 B의 커밋을 가리키게 할 뿐이다. 이제 hotfix는 master 브랜치에 포함됐고 운영환경에 적용할 수 있는 상태 가 되었다고 가정해보자.

Merge 후 hotfix 같 은 것을 가리키는 master 브랜치

급한 문제를 해결하고 master 브랜치에 적용하고 나면 다시 일하던 브랜치 로 돌아가야 한다. 이제 더 이상 필요없는 hotfix 브랜치는 삭제한다. git branch 명령에 -d 옵션을 주고 브랜치를 삭제한다.

 

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

이슈 53번을 처리하던 환경으로 되돌아가서 하던 일을 계속 하자.

 

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)

master와 별개로 진 행하는 iss53 브랜치

위에서 작업한 hotfix가 iss53 브랜치에 영향을 끼치지 않는다는 점을 이 해하는 것이 중요하다. git merge master 명령으로 master 브랜치를 iss53 브랜치에 Merge 하면 iss53 브랜치에 hotfix가 적용된다. 아니면 iss53 브 랜치가 master에 Merge 할 수 있는 수준이 될 때까지 기다렸다가 Merge 하면 hotfix와 iss53 브랜치가 합쳐진다.

 

 

Merge 의 기초

 

53번 이슈를 다 구현하고 master 브랜치에 Merge 하는 과정을 살펴보자. iss53 브랜치를 master 브랜치에 Merge 하는 것은 앞서 살펴본 hotfix 브랜 치를 Merge 하는 것과 비슷하다. git merge 명령으로 합칠 브랜치에서 합쳐 질 브랜치를 Merge 하면 된다.

 

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
README | 1 +
1 file changed, 1 insertion(+)

hotfix를 Merge 했을 때와 메시지가 다르다. 현재 브랜치가 가리키는 커밋 이 Merge 할 브랜치의 조상이 아니므로 Git은 Fast-forward로 Merge 하지 않는 다. 이 경우에는 Git은 각 브랜치가 가리키는 커밋 두 개와 공통 조상 하나를 사 용하여 3-way Merge를 한다.

 

커밋 3개를 Merge 한다.

 

단순히 브랜치 포인터를 최신 커밋으로 옮기는 게 아니라 3-way Merge 의 결과를 별도의 커밋으로 만들고 나서 해당 브랜치가 그 커밋을 가리키도록 이 동시킨다. 그래서 이런 커밋은 부모가 여러 개고 Merge 커밋이라고 부른다.

 

Merge 커밋

Git은 Merge 하는데 필요한 최적의 공통 조상을 자동으로 찾는다. 이런 기 능도 Git이 다른 버전 관리 시스템보다 나은 점이다. CVS나 Subversion 같은 버 전 관리 시스템은 개발자가 직접 공통 조상을 찾아서 Merge 해야 한다. Git은 다른 시스템보다 Merge가 대단히 쉽다. iss53 브랜치를 master에 Merge 하고 나면 더는 iss53 브랜치가 필요 없다. 다음 명령으로 브랜치를 삭제하고 이슈의 상태를 처리 완료로 표시한다.

 

 

$ git branch -d iss53

브랜치 관리

 

지금까지 브랜치를 만들고, Merge 하고, 삭제하는 방법에 대해서 살펴봤 다. 브랜치를 관리하는 데 필요한 다른 명령도 살펴보자. git branch 명령은 단순히 브랜치를 만들고 삭제하는 것이 아니다. 아무 런 옵션 없이 실행하면 브랜치의 목록을 보여준다.

 

$ git branch
 iss53
 * master
 testing

* 기호가 붙어 있는 master브랜치는 현재 Checkout 해서 작업하는 브랜치 를 나타낸다. 즉, 지금 수정한 내용을 커밋하면 master 브랜치에 커밋되고 포 인터가 앞으로 한 단계 나아간다. git branch -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

각 브랜치가 지금 어떤 상태인지 확인하기에 좋은 옵션도 있다. 현재 Checkout 한 브랜치를 기준으로 --merged와 --no-merged 옵션을 사용하여 Merge 된 브랜치인지 그렇지 않은지 필터링해 볼 수 있다. git branch -- merged 명령으로 이미 Merge 한 브랜치 목록을 확인한다.

 

$ git branch --merged
 iss53
* master

iss53 브랜치는 앞에서 이미 Merge 했기 때문에 목록에 나타난다. * 기호 가 붙어 있지 않은 브랜치는 git branch -d 명령으로 삭제해도 되는 브랜치 다. 이미 다른 브랜치와 Merge 했기 때문에 삭제해도 정보를 잃지 않는다. 반대로 현재 Checkout 한 브랜치에 Merge 하지 않은 브랜치를 살펴보려면 git branch --no-merged 명령을 사용한다.

 

$ git branch --no-merged
 testing

위에는 없었던 다른 브랜치가 보인다. 아직 Merge 하지 않은 커밋을 담고 있기 때문에 git branch -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'.

Merge 하지 않은 브랜치를 강제로 삭제하려면 -D 옵션으로 삭제한다.