하나의 branch에 다른 branch를 합치는 작업을 말한다.
각 브랜치에 커밋이 있는 경우, git merge 명령어를 사용하면 두 브랜치의 코드를 합쳐서 새로운 커밋을 만들어주는 방식이 바로 3-Way Merge 방식입니다. 이 경우 같은 파일에 대해 다른 변경사항이 있을 경우 “충돌”이 발생하게 됩니다. 위의 방법대로 해당 파일의 충돌을 해결한 후 커밋을 하면 됩니다.
기준이 되는 브랜치(Main, Master, etc)에는 신규 커밋이 존재하지 않고 다른 브랜치에만 커밋이 존재할 때 브랜치 병합을 하는 경우, 자동으로 Fast-Forward Merge가 이루어집니다. 새로운 커밋이 생기지 않고 HEAD의 위치만 변하게 됩니다.
rebase(재배치)는 브랜치가 뻗어나온 기준점을 변경하는 것을 말합니다. rebase를 이용하여 새로운 브랜치의 기준점을 최근 커밋으로 옮긴 후 Fast-Forward Merge를 하는 것입니다. 이러한 방식으로 병합을 하면 “병합 커밋을 보지 않는다”는 장점이 있습니다. 하나의 브랜치에서 작업한 것과 같은 효과를 얻을 수 있기에 커밋 로그가 깔끔해집니다. 다만, rebase는 브랜치끼리의 작업 내역 차이가 많은 경우 충돌이 많이 발생할 수 있습니다.
# practice1 브랜치로 이동 git switch practice1 # practice1 브랜치를 main 브랜치로 재배치 git rebase main # main 브랜치로 이동 git switch main # main 브랜치에서 practice1 브랜치를 fast-forward merge하기 git merge practice1
3-Way Merge의 가장 큰 단점은 병합한 브랜치들의 커밋 내역까지 모두 보여지기에 커밋 로그가 매우 복잡하고 지저분해진다는 것입니다. Squash and Merge 방식은 병합할 브랜치에 있는 모든 커밋들을 하나의 커밋으로 만든 후, 기준이 되는 브랜치에 추가하는 방식으로 병합합니다. 커밋 로그가 매우 깔끔해진다는 장점이 있으나, 병합할 브랜치의 모든 커밋 이력이 사라져버린다는 단점이 있습니다.
# main 브랜치로 이동 git switch main # --squash 옵션을 통한 병합 git merge --squash 브랜치명 # 기존의 커밋들을 하나로 합친 새로운 커밋 git commit -m "메세지"
그러나 브랜치를 서로 합칠 때는 주의해야할 것이 있습니다. 바로 “충돌”입니다. 충돌(Merge Conflict)이란 서로 다른 브랜치에서 같은 파일이나, 같은 줄을 수정하고 병합할 때 부딪히는 현상을 말합니다.
아래의 사진이 바로 충돌이 일어난 상황입니다. 같은 파일에서 master 브랜치와 practcie1 브랜치의 코드가 다른데 병합을 시도하였기에 다음과 같이 충돌이 발생한 것입니다.
해결방법은 간단합니다. ««« / »»» / ===== 같은 쓸데없는 것들 지운 후, 자신이 남기고자 하는 코드를 남기고 git add 파일명, git commit -m “메세지” 를 입력하여 새로운 커밋을 생성하면 됩니다. 그러면 충돌이 해결되고 브랜치도 정상적으로 잘 합쳐지게 됩니다.
머지를 하다가 conflict가 발생했을 때, 일단은 머지 작업을 취소하고 이전 상태로 돌아갈 수 있다.
git merge --abort
git merge –abort 명령은 Merge 하기 전으로 되돌린다. 완전히 뒤로 되돌리지 못하는 유일한 경우는 Merge 전에 워킹 디렉토리에서 Stash 하지 않았거나 커밋하지 않은 파일이 존재하고 있었을 때뿐이다. 그 외에는 잘 돌아간다.
Merge 후의 결과를 Merge 하기 전의 브랜치와 비교하려면, 다시 말해 무엇이 합쳐졌는지 알려면 git diff –ours 명령을 실행한다.
git diff --ours
지금까지 Merge 하는 방법을 배웠으나 Merge 할 때 실수할 수도 있다. Git에서는 실수해도 된다. 실수해도 (대부분 간단하게) 되돌릴 수 있다.
접근 방식은 원하는 결과에 따라 두 가지로 나눌 수 있다.
실수로 생긴 Merge 커밋이 로컬 저장소에만 있을 때는 브랜치를 원하는 커밋을 가리키도록 옮기는 것이 쉽고 빠르다. 잘못 Merge 하고 나서 git reset –hard HEAD~ 명령으로 브랜치를 되돌리면 된다.
reset 에 대해서는 이미 앞의 Reset 명확히 알고 가기에서 다뤘었기 때문에 이 내용이 그리 어렵진 않을 것이다. 간단하게 복습해보자. reset –hard 명령은 아래의 세 단계로 수행한다.
HEAD의 브랜치를 지정한 위치로 옮긴다. 이 경우엔 master 브랜치를 Merge 커밋(C6) 이전으로 되돌린다.
Index를 HEAD의 내용으로 바꾼다.
워킹 디렉토리를 Index의 내용으로 바꾼다.
이 방법의 단점은 히스토리를 다시 쓴다는 것이다. 다른 사람들과 공유된 저장소에서 히스토리를 덮어쓰면 문제가 생길 수 있다. 무슨 문제가 일어나는지 알고 싶다면 Rebase 의 위험성를 참고하자. 간단히 말해 다시 쓰는 커밋이 이미 다른 사람들과 공유한 커밋이라면 reset 하지 않는 게 좋다. 이 방법은 Merge 하고 나서 다른 커밋을 생성했다면 제대로 동작하지 않는다. HEAD를 이동시키면 Merge 이후에 만든 커밋을 잃어버린다.
브랜치를 옮기는 것을 할 수 없는 경우는 모든 변경사항을 취소하는 새로운 커밋을 만들 수도 있다. Git에서 이 기능을 “revert” 라고 부른다. 지금의 경우엔 아래처럼 실행한다.
$ git revert -m 1 HEAD [master b1d8379] Revert "Merge branch 'topic'"
-m 1 옵션은 부모가 보호되어야 하는 “mainline” 이라는 것을 나타낸다. HEAD 로 Merge를 했을 때(git merge topic1) Merge 커밋은 두 개의 부모 커밋을 가진다. 첫 번째 부모 커밋은 HEAD (C6)이고 두 번째 부모 커밋은 Merge 대상 브랜치(C4)이다. 두 번째 부모 커밋(C4)에서 받아온 모든 변경사항을 되돌리고 첫 번째 부모(C6)로부터 받아온 변경사항은 남겨두고자 하는 상황이다.
새로 만든 커밋 ^M 은 C6 과 내용이 완전히 똑같다. 잘못 Merge 한 커밋까지 HEAD 의 히스토리에서 볼 수 있다는 것 말고는 Merge 하지 않은 것과 같다. topic 브랜치를 master 브랜치에 다시 Merge 하면 Git은 아래와 같이 어리둥절해한다.
$ git merge topic
Already up-to-date.
이미 Merge 했던 topic 브랜치에는 더는 master 브랜치로 Merge 할 내용이 없다. 상황을 더 혼란스럽게 하는 경우는 topic 에서 뭔가 더 일을 하고 다시 Merge를 하는 경우이다. Git은 Merge 이후에 새로 만들어진 커밋만 가져온다.
이러면 가장 좋은 방법은 되돌렸던 Merge 커밋을 다시 되돌리는 것이다. 이후에 추가한 내용을 새 Merge 커밋으로 만드는 게 좋다.
$ git revert ^M [master 09f0126] Revert "Revert "Merge branch 'topic'"" $ git merge topic