출처: GIT CLI - Branch & Conflict by 이고잉, 생활코딩, CC BY
지금까지 만들던 버전에 이어서 서로 다른 여러 작업을 진행해야 하는 경우가 있다. 이럴 때 저장소를 복제하지 않고 동일한 효과를 낼 수 있는 방법이 바로 브랜치(branch)이다. 브랜치는 같은 뿌리에서 나왔지만 서로 다른 역사를 써가고 있는 버전들을 말한다. 지금부터 Git의 브랜치를 만들고, 이를 병합하는 방법을 알아보자! 그리고 서로 다른 브랜치가 같은 파일의 같은 위치를 수정하려고 할 때 발생하는 충돌을 어떻게 해결할 수 있는지도 알아보자!
우리가 제품 설명서를 만드는데, 각 고객사마다 서로 다른 내용을 추가해야 하는 상황이라고 생각해보자. 브랜치를 이용하면 굉장히 세련되게 이 문제를 해결할 수 있다.
명령어 | 용도 |
---|---|
git branch [브랜치명] | 새로운 브랜치 생성 |
git branch | 브랜치들의 목록 확인 (현재 브랜치는 * 표시) |
git checkout [브랜치명] | 브랜치 전환 (해당 브랜치의 마지막 커밋 상태로 전환) |
git log --all --graph --oneline | 커밋 로그를 그래프 모양으로 확인 |
서로 다른 4개의 브랜치를 생성하고, apple이라는 브랜치로 전환한 모습이다.
master 브랜치로 돌아와서 work.txt 파일의 내용을 변경한 뒤에 커밋을 수행했다. 이제 apple 브랜치로 전환한 뒤에 apple.txt 파일을 새로 만들고 work.txt 내용을 수정해보자!
apple work 4
라는 커밋을 수행한 뒤에 다시 master 브랜치로 전환하면 어떤 일이 발생할까?
놀랍게도 apple 브랜치에서 작업했던 내용은 모두 사라지고, master 브랜치에서 작업한 내용만 남아있다! 이처럼 우리는 checkout 명령을 통해 해당 브랜치에서 작업했던 내용으로 순간이동할 수 있다. 나머지 브랜치들에 대해서도 동일한 작업을 반복한 뒤에 커밋 로그를 확인해보면 다음과 같다.
우리는 더 이상 저장소를 복제할 필요 없이 하나의 저장소 안에서 공통의 작업을 공유하면서도, 각자 자신의 작업을 갖고 있는 별도의 평행우주를 만들 수 있다. 이것은 깃이 가져온 혁명적인 기능이다.
병합하려고 하는 브랜치들의 공통 조상을 base라고 한다. 깃은 병합에 필요한 대부분의 작업을 자동화 해주며, 특정 부분만 직접 수정해달라고 우리에게 요청한다.
master 브랜치에는 master.txt 파일을, o2 브랜치에는 o2.txt 파일을 만든 다음에 master 브랜치에 o2 브랜치를 병합해보자.
git merge [병합할 브랜치명]
// 현재 브랜치와 지정한 브랜치를 병합
그러면 위와 같이 새로운 버전이 생성되며, 이 버전은 o2 work 2
와 master work 2
를 공통 조상으로 한다. 그리고 각 브랜치에 있는 o2.txt와 master.txt 파일은 서로 독립적이어서 영향을 미치지 않는다.
cf) 핵심 정보만 보여주기 위해서 git lg
엘리어스를 git l = log --all --graph --oneline
으로 변경했다.
master로 브랜치를 바꾼 뒤에 git merge o2
를 하면 master 브랜치에 o2 브랜치가 병합되면서 새로운 버전이 생성된다. work.txt 파일의 내용을 보면, 각 브랜치에서 따로 따로 수정했던 내용이 합쳐진 것을 확인할 수 있다.
서로 다른 브랜치가 같은 파일의 같은 부분을 수정하려고 하면 conflict가 발생하는데, 나머지는 모두 깃이 알아서 자동으로 병합을 해주더라도 이 부분만큼은 우리가 직접 수정해줘야 한다! conflict를 사고가 아니라 깃이 제공하는 혁명적인 기능이라고 생각하자.
hear와 there는 브랜치이고, base는 두 브랜치의 공통 조상이다. hear와 there 브랜치 둘만의 차이를 비교할 때는 4개 중에 1개만 어떤 것으로 변경할지 정할 수 있다.
반면에, base의 상태까지 고려하면 4개 중에 3개를 어떤 것으로 변경할지 정할 수 있다. 즉, hear와 there 두 브랜치에서 모두 동일한 부분을 변경한 경우에만 우리가 직접 충돌을 해결해주면 된다. 나머지는 깃이 알아서 병합을 해준다.
3 way merge를 제공하는 여러가지 툴 중에서 한 가지를 선택해서 사용하면 편리하다.
https://stackoverflow.com/questions/572237/whats-the-best-three-way-merge-tool
git init
으로 새로운 저장소를 생성하면, .git 디렉토리에 HEAD라는 파일이 생기며, 이것은 기본적으로는 master 브랜치를 가리키고 있다. 초기에는 이 master 브랜치 위에서 버전을 만들어나간다.
이 상태에서 git checkout google
로 브랜치를 전환한다는 것은, HEAD가 가리키는 브랜치가 google로 바뀌며 이 저장소의 최신 상태가 버전 2라는 것을 의미한다.
여기서 새로운 버전 3을 만들면, google 브랜치는 3번을 가리킨다.
다시 master 브랜치로 체크아웃 한다는 것은, HEAD가 가리키는 브랜치가 master로 바뀌고 이 저장소의 버전도 2번으로 휙 바뀌는 것이다.
결국 checkout이라는 것은 HEAD의 값을 바꾸는 것이다. 이때 HEAD는 브랜치뿐만 아니라, 버전을 가리킬 수도 있다. git checkout 1
은 1번이라는 이름의 버전을 직접 가리킨다. 이처럼 HEAD가 가리키는 값이 브랜치로부터 떨어져 있는 걸 detached 상태라고 한다.
checkout이 HEAD의 값을 바꾸는 change의 느낌이라면, reset은 브랜치가 가리키는 버전을 바꿔서 그 이후의 버전을 삭제하는 delete의 느낌이다.
위 그림에서 checkout master
를 하면 HEAD가 가리키는 브랜치가 google에서 master로 바뀌고, 저장소의 상태도 3번이 아닌 2번으로 바뀐다. 반면에, reset 1
을 하면 그 이후에 생성된 버전인 2, 3번은 google 브랜치로부터의 연결이 끊기기 때문에 삭제된다.