Git branch와 merge, rebase

Jeongkuk Seo
sjk5766
Published in
8 min readJan 5, 2019

Git에서 branch에 대해 알아보고 브랜치와 브랜치를 합치는 방법에 대해 알아보고 실습을 해보겠습니다.

Git의 branch

Git에서 commit 할 경우 데이터 스냅샷에 대한 포인터, 저자나 커밋 메시지 같은 메타데이터, 이전 커밋을 가리키는 포인터등을 저장합니다. Git에서 branch란 커밋 사이를 이동할 수 있는 포인터의 개념입니다. 아래 그림은 master에서 두 번의 commit 후 testing branch를 생성한 경우 입니다. master의 commit f30ab 상태에서 브랜치 testing을 생성하였으므로 testing 브랜치가 f30ab를 가리키고 있습니다.

Git에서는 현재 작업하는 브랜치를 가리키는 HEAD라는 포인터가 있습니다. 위 그림에선 HEAD 포인터가 master를 가리키고 있습니다. 위 상태를 만들기 위한 명령어는 아래와 같습니다.

git init
git commit // 98ca9
git commit // 34ac2
git commit // f30ab
git branch testing

아래 명령어로 HEAD 포인터가 testing 브랜치를 가리키도록 할 수 있습니다.

git checkout testing

브랜치를 만드는 이유는 특정 기능을 만들어 master 브랜치에 합치거나, 급한 버그를 해결하기 위해 브랜치를 만들기도 합니다. master 브랜치와 다른 브랜치를 합치는 작업을 merge라고 하는데 Git에서 merge 작업에 크게 두 가지 방식이 있습니다.

Git에서 브랜치를 합치는 방법

Git에서 브랜치를 합치는 방법에는 fast-forward와 3-way merge, rebase가 있습니다. 각각에 대해 알아 보겠습니다.

master 브랜치에서 작업을 하다가 이슈를 해결하기 위해 iss53 이라는 브랜치를 생성하고 iss53 브랜치에서 commit을 한다면 아래 그림과 같습니다.

개발 과정에서 급한 버그가 발생해서 hotfix 브랜치를 만들고 버그를 수정하여 commit 하면 아래와 같은 상태가 됩니다.

master 브랜치로 이동하여 급한 hotfix 브랜치를 머지할 때 다음과 같은 화면을 볼 수 있습니다.

위에서 보이는 fast-forward는 merge 방식을 알려주는데 위의 상황에서 C4의 커밋은 C2의 커밋을 기반으로 합니다. 따라서 C2를 가리키는 master가 단순히 C4 커밋을 가리키는 걸로 merge가 이뤄집니다. 개념상 merge 할 뿐 실제로 fast-forward 방식은 master 브랜치가 가리키는 포인터만 C4로 이동할 뿐입니다.

master에서 hotfix에서 수정한 내용을 merge 했으므로 hotfix 브랜치는 지우겠습니다.

git branch -d hotfix

이제 3-way merge에 대해 알아보기 위해 iss53 브랜치에서 commit을 하겠습니다.

master 브랜치가 C2를 가리킨다면 fast-forward merge가 가능하겠지만 C4의 상태에서는 연결되는 포인터가 없어 3-way merge가 발생합니다.

3개의 commit은 master와 iss53 브랜치, 이 둘의 공통적인 부모 커밋인 C2 총 3개를 가지고 merge가 발생합니다.

git 로그를 확인하면 merge 했다는 메시지를 확인할 수 있습니다.

commit fc7ff546c36d28eb9f3c43928d9c5b65fc28ed90
Merge: 041a79d 447b1c5
Author: sjk5766
Date: Sat Jan 5 14:38:45 2019 -0800
Merge branch ‘iss53’

위 에서 merge의 두 가지 방식에 대해 알아봤습니다. 위 case는 모두 충돌 없이 merge되었지만, 만약 충돌이 발생하면 어떻게 될지 상황을 만들어 보겠습니다. master와 iss53 브랜치에서 동일 파일에 변경을 가하고 commit 해보겠습니다.

merge는 실패했고 git status로 상태를 확인해보면 Unmerged 된 파일의 정보를 확인할 수 있습니다.

Unmerged 파일을 보면 중복된 내용을 확인할 수 있습니다. << HEAD 와 === 사이의 내용은 HEAD가 가리키는 master의 내용이며 ===와 >> iss53 사이의 내용은 iss53 브랜치의 내용입니다.

이 부분을 merge 하기 위해선 수동으로 << HEAD, ===, >> iss53 을 제거합니다. 충돌 난 부분 중 필요한 부분만 남기고 commit 합니다.

[root@localhost git_test]# cat README2
hi\n
asdasd
aa
bb\n
aaa
aab
[root@localhost git_test]# git commit -a -m “merge”
[master 6a7bb1c] merge
[root@localhost git_test]#
[root@localhost git_test]#
[root@localhost git_test]# git status
# On branch master
nothing to commit (working directory clean)

한 가지 궁금증이 들었습니다. master와 iss53 브랜치에서 동일한 파일의 다른 line을 변경하면 어떻게 될까? 가령 master에서는 file에서 5번째 라인을 변경합니다. iss53 브랜치는 동일 파일의 4번째 라인을 변경합니다. 결과적으로 merge는 충돌없이 성공합니다. 따라서 Git은 merge 할 때 file의 line 단위로 충돌이 발생하지 않으면 정상적으로 merge 가 발생하는 것을 알 수 있습니다.

이제 마지막으로 rebase에 대해 알아보겠습니다. rebase의 경우 앞의 merge 방식과는 조금 다릅니다. 아래와 같이 C2에서 브랜치를 만들어서 commit하여 C4로 진행하고 master에서 commit 하여 C3으로 진행합니다.

이 상태에서 브랜치에서 rebase를 하게 되면 공통 부모인 C2로 이동한 뒤 현재 브랜치가 가리키는 커밋까지 diff를 만들어 임시로 저장한다. 그 뒤 합쳐야 할 브랜치로 fast-forward 한 뒤에 임시로 저장한 diff를 반영합니다. 아래는 experiment 브랜치에서 master를 rebase한 경우입니다. 공통 부모인 C2로 이동한 뒤 experiment 브랜치가 가리키는 C4까지 변경사항을 임시로 저장합니다. 그리고 master를 가리킨후 변경사항을 반영하여 브랜치를 합치게 됩니다.

rebase가 위에서 언급한 3-way merge에 비해 가지는 장점은 깨끗한 히스토리를 남깁니다. 가령 3-way merge로 브랜치를 합칠 경우 아래와 같이 로그가 남습니다.

commit fc7ff546c36d28eb9f3c43928d9c5b65fc28ed90
Merge: 041a79d 447b1c5
Author: sjk5766
Date: Sat Jan 5 14:38:45 2019 -0800
Merge branch ‘iss53’ <<- merge 작업에 의한 추가적인 히스토리commit dc397a63c8cc78898bb829328ce8176474ccd574
Author: sjk5766
Date: Sat Jan 5 15:50:43 2019 -0800
3 <<- commit 메시지

반대로 rebase의 경우 merge에 따른 추가적인 히스토리가 남지 않습니다.

--

--