1. Merge와 Conflicts

이번 글에서는 local repository에서 branch를 생성하여 작업을 한 후, 두 브랜치를 통합하는 merge에 대해 알아보도록 하겠습니다.


merge를 하는 도중에 충돌( conflicts )이 발생할 수 있습니다.

현재 브랜치와 병합 하고자 하는 브랜치에서 같은 파일의 같은 위치를 수정했을 경우, 사용자가 어느 것을 선택할 것인지 Git은 알 수가 없습니다.

그래서 사용자에게 충돌난 부분을 직접 수정해서 충돌을 해결하라고 하는 것이죠.


실습을 통해 merge하는 방법과 충돌을 해결하는 방법에 대해 알아보도록 하겠습니다.




2. 명령어

  • git merge
    • {대상브랜치} : 현재브랜치에서 대상브랜치를 병합시킴
    • --squash : 대상브랜치를 병합할 때, 커밋 이력을 모두 제거하고 작업된 내용만 병합
    • --no-ff : fast-forward 방식으로 병합할 때, 병합된 것임을 알리는 커밋 메시지 생성





3. Fast-forward 방식으로 병합

1) master 브랜치 작업


실습 집행을 위해 master 브랜치에서 위와 같이 파일을 작성한 후, commit을 합니다. ( 꼭 HTML 문서일 필요는 없습니다. )

# git checkout master

# git add .

# git commit -m "메시지"




2) 새로운 브랜치 생성해서 작업

다음으로 master 브랜치에서 새로운 브랜치인 test1 브랜치를 생성합니다.

그러면 master 브랜치의 파일들과 커밋 이력을 그대로 복사되고, 독립적인 작업공간이 생성됩니다.

# git checkout -b test1

# git branch



test1 브랜치에서 파일 내용을 다음과 같이 수정하고, 커밋하여 새로운 버전을 생성합니다.

# git add . # git commit -m "test1 메시지"





3) git merge

지금까지 내용을 요약하면 다음과 같습니다.

  • master 브랜치는 <span> 요소의 내용이 없지만, test1 브랜치에는 존재하는 상황입니다.
  • test1 브랜치가 master 브랜치보다 한 번 더 commit을 했습니다.

이제 master 브랜치에 test1 브랜치를 merge하도록 하겠습니다.

그러기 위해서는 먼저 master branch로 이동을 해야 합니다.

# git checkout master # git merge test1


그러면 Fast-forward 메시지가 출력이 됩니다.

Fast-forward란 merge 할 브랜치( 대상 브랜치, test1 브랜치 )의 commit이 현재 branch( 기준 브랜치, master 브랜치 )의 commit 보다 앞서가 있기 때문에, 기준 브랜치의 커밋을 대상 브랜치 commit으로 이동하겠다는 의미입니다.


이를 그림으로 표현하면 다음과 같습니다.

즉, "master 브랜치의 HEAD를 test1 브랜치의 HEAD로 이동하겠다 "는 뜻입니다.


실제로 master 브랜치의 파일을 열어보면, test1 브랜치에서 작성된 내용이 저장되어 있습니다.

가장 최근의 커밋 메시지도 test1 브랜치에서 작성한 커밋 메시지가 표시되구요.


이것이 fast-forward 방식의 merge입니다.

즉, 테스트를 진행할 브랜치를 만들어서 테스트가 성공적으로 끝나면 원래 브랜치에 통합하는 방식으로 사용하면 좋겠네요.






4. 충돌이 발생하는 병합

그런데 기준 브랜치와 대상 브랜치가 같은 파일의 같은 부분을 수정하면 충돌이 발생합니다.


1) master 브랜치 작업

이전 예제에 이어서 master 브랜치로 이동( git checkout master )한 후의 파일에 다음과 같이 작성합니다.



<div> 요소에 class 속성에 foo를 추가했고, </div>와 <span> 사이에 공백을 줬습니다.

그리고 나서 commit을 합니다.

# git add .

# git commit -m "master message"

그러면 현재 test1 브랜치보다 master 브랜치의 버전이 더 높은 상황이 됩니다.




2) test1 브랜치 작업

다음으로 test1 branch로 브랜치를 이동( git checkout test1 )한 후,

아래와 같이 <div> 요소의 class 속성에 goo를 추가하고, </div>와 <span> 사이의 공백을 없애겠습니다.



마찬가지로 commit을 합니다.

# git add .

# git commit -m "master message"

그러면 master 브랜치와 test1 브랜치의 commit 수가 같습니다.

즉, fast-forward 방식으로 병합이 되지 않을 것입니다.





3) git merge

이제 똑같이 master branch로 이동해서 test1 브랜치를 merge하도록 하겠습니다.

# git checkout master # git merge test1


merge 명령어를 실행하면 충돌이 발생했다면서, merge를 진행하지 않습니다.


현재 상황은 다음과 같습니다.

  • master 브랜치와 test1 브랜치의 커밋횟수가 같습니다.
  • master 브랜치와 test1 브랜치는 <div> 요소의 class 속성 값을 각각 다른 값으로 동시에 수정했습니다.

따라서 Git은 사용자에게 어떤 branch의 내용을 선택할 것인지 제시합니다.

merge를 진행했던 기준 브랜치는 master 이므로 <<<< HEAD 영역이 master 브랜치의 내용을 의미합니다.

<<<<< HEAD 


=========


만약 협업 과정에서 이러한 충돌이 발생했을 경우에는 동료들과 함께 충돌을 해결해야 합니다.

코드를 비교해 가면서 필요 없는 코드를 일일이 지우는 작업이죠.

이는 귀찮은 작업이기 때문에 최대한 작업 내용이 겹치지 않도록 하는 것이 좋습니다.


이렇게 충돌이 발생한 후, 해결이 됐으면 commit을 하면 됩니다.





5. merge와 관련된 옵션들

merge를 할 때 사용하면 괜찮은 옵션 2가지를 소개하려고 합니다.


1) --squash

이 옵션은 대상 브랜치의 모든 커밋을 하나의 커밋으로 합쳐서 merge 하는 방식입니다.

즉, 대상 브랜치에서 작업했던 히스토리를 하나의 메시지로 압축시키는 것이죠.


이 옵션은 테스트 브랜치에서 원래 브랜치에 병합할 때 유용한 방식입니다.

즉, 애초에 하나인 브랜치가 임시로 빠져나와서 다시 통합할 때 사용하는 것이 좋습니다.


예를 들어, master 브랜치가 있고, 이를 그대로 복사한 child 브랜치가 있다고 가정하겠습니다.

그리고 child 브랜치에서 커밋을 5번을 했다고 했을 때, 당연히 master branch보다 커밋 수가 앞서 있겠죠?

이 때 master 브랜치에서 --squash 옵션을 이용해서 child 브랜치를 병합하면, child 브랜치의 5번의 커밋 내역은 무시되고 파일 수정 이력만 받게 됩니다.

따라서 깔끔한 히스토리와 함께 merge를 할 수 있게 됩니다.


예제를 보시면, 처음 child 브랜치는 2개의 커밋이 있습니다.

master 브랜치에서 --squash 옵션으로 child 브랜치를 병합하면, child 브랜치의 작업 본은 담겨 있으므로 modified가 된 것을 확인할 수 있습니다.

그리고 커밋을 하여 로그를 확인했더니 child 브랜치의 2개의 커밋내용이 싹 사라졌습니다.




2) --no-ff

fast-forward 관계에서 merge를 하면 merge 커밋이 생략되는데, --no-ff 옵션을 주면 merge할 때 커밋을 생성합니다.

전자의 경우는 --ff 옵션과 같습니다.



fast-forward 관계일 때 옵션 없이 그냥 merge 할 때는 아래와 같이 브랜치가 갈라지지 않지만,


--no-ff 옵션을 추가하면 다음과 같이, "Merge branch 'child2'"라는 커밋 이력이 추가되는 것을 확인할 수 있습니다.





이상으로 branch를 merge 하는 방법과 충돌을 해결하는 방법에 대해 알아보았습니다.