드디어 Git의 꽃, Merge 섹션에 왔다.
Git의 꽃(이라고 읽고 엿 같다고 쓴다...)은 "충돌(Conflict)"겠지만 충돌이 Merge 과정에서 일어나기 때문에 병합에 대해 잘 알아두어야지만 Conflict를 이해할 수 있고, 잘 대처할 수 있다.
병합은 두 버전(스냅샷, 커밋)의 합집합을 구하는 과정이다.
이전에 말했듯 Branch를 생성하는 것은 브랜치를 생성한 커밋으로부터 가지를 만들어 새로운 커밋을 이어붙이는 작업이다.
그렇다면, Branch 생성 후 작업이 모두 끝났다면 Branch의 작업물을 가지가 시작된 커밋에 추가(적용) 해 줘야 할 필요가 있다.
그래야지만 협업하고 있는 다른 팀원들도 내가 작업한 작업물에 대해 인지할 수 있고, 동시에 공유 받을 수 있을 것이다.
그리고 개발이 완료된 작업물을 병합시켜야지만 "현실 서비스 = 개발 코드" 상태를 유지하여 Git의 주요 목적인 버전 관리를 수행할 수 있을 것이다.
그리고, 바로 이 과정이 "병합(Merge)"인 것이다.
출처 : https://www.inflearn.com/course/%ED%8C%80%EA%B0%9C%EB%B0%9C-%EA%B9%83-%EA%B9%83%ED%97%88%EB%B8%8C
병합 커밋이란 이전 커밋과 현재 커밋 중 다른 점이 존재하는데 다른 점들이 충돌되지 않을 경우 발생한다.
위 이미지에서 두 문어는 "꽃을 들고 있다", "모자를 쓰고 있다"라는 점에서 차이가 존재하지만 차이점을 모두 수용하여 새로운 문어 상태를 만들 수 있으므로 "병합 커밋" 된 것이다.
출처 : https://www.inflearn.com/course/%ED%8C%80%EA%B0%9C%EB%B0%9C-%EA%B9%83-%EA%B9%83%ED%97%88%EB%B8%8C
빨리 감기 병합이란 현재 커밋이 이전 커밋에서 추가되거나 삭제된 부분만 있을 경우의 병합을 의미한다.
위 이미지에서 이전 문어 상태와 비교했을 때 모자를 쓰기만 했으므로 빨리 감기 병합에 속하며, 반대로 문어가 이전에 장갑을 끼고 있었는데 현재는 벗고 있었더라도 빨리 감기 병합에 속한다.
빨리 감기 병합은 위와 같이 설명할 수도 있지만 가장 쉽게 표현하는 방법이 있는데, 바로 "현재 커밋과 병합 이후 커밋이 동일한 병합"이다.
위에서 설명한 병합 커밋은 현재 버전과 이전 버전이 다르기 때문에 이전 버전과도 현재 버전과도 다른 새로운 버전이 생성된다.
하지만 빨리 감기 병합은 최종적으로 현재 버전과 병합 이후의 상태와 동일하다.
출처 : https://www.inflearn.com/course/%ED%8C%80%EA%B0%9C%EB%B0%9C-%EA%B9%83-%EA%B9%83%ED%97%88%EB%B8%8C
충돌은 병합을 하려 하는데 이전 버전과 현재 버전에서 겹치는 부분에 다른 점이 존재하여 어떤 부분을 유지시켜야 할지 모르는 상태이다.
위 사진에서 보면 왼쪽 문어는 고깔모자를 쓰고 있지만 중간 문어는 캡 모자를 쓰고 있다.
이 둘을 합치면 과연 최종 문어는 어떤 모자를 써야 할까? 도저히 알 수가 없다.
이런 상태는 "협업"을 기반으로 하는 Git에서는 생각보다 많이 발생하는데 개발자 A는 고깔모자로 개발을 수행한 뒤 원격 저장소에 등록해 놨는데 개발자 B가 캡 모자로 개발을 수행하는 경우가 생각보다 빈번하다.(모든 개발 과정 및 원리를 설명할 순 없으니...)
이 충돌의 원리 및 해결법에 대해선 다음 섹션에서 더욱 자세히 배울 테니 이 정도만 설명하겠다.
최초 브랜치 상황이다.
개발자 A는 feautre/developA
브랜치에서 작업을 하고 있고 개발자 B는 feature/developB
브랜치에서 작업을 하고 있다.
이 상황에서 개발자 B는 작업을 완료하였고 배포를 위하여 main 브랜치에 병합을 하기로 했다.
feature/developB
브랜치의 경우 커밋2(메인 브랜치)와 비교했을 때 몇 개의 기능이 추가(혹은 삭제) 되었을 뿐이다.
즉, 커밋2와 커밋4를 병합하면 최종적으로 커밋 4의 형태로 병합이 완료될 것이다.
그리고 우리는 이를 "빨리 감기 병합"이라고 하기로 했다.
개발자 B는 작업이 완료되었으므로 feature/developB
브랜치는 이제 삭제하자.
개발자 B는 작업을 완료하였으므로 브랜치를 삭제하였고, 현재 main 브랜치는 커밋4를 가리키고 있다.
이 상황에서 개발자 A도 작업을 완료하였고 배포를 위해 main 브랜치에 병합을 할 필요성이 있다.
문제는 main 브랜치가 커밋5를 생성한 커밋2를 가리키고 있지 않고 개발자 B의 최종 작업물인 커밋4를 가리키고 있다는 점이다.
즉, 개발자 A는 커밋4와 커밋5를 병합하는 과정이 필요한 것이다.
이 상황에서 커밋4에는 커밋5가 가지지 못한 기능을 가지고 있을 것이고, 반대로 커밋5는 커밋4가 가지지 않는 기능을 가지고 있을 것이다.
(충돌이 없다는 가정 하에) 이 상황에서 이뤄지는 병합이 바로 "병합 커밋"인 것이다.
병합 커밋은 총 2가지 상황이 존재할 것이다.
먼저, 커밋4를 기준으로 커밋5에 추가된 기능을 반영할 수 있을 것이다.
두 번째로, 커밋5를 기준으로 커밋4에 추가된 기능을 반영할 수 있을 것이다.
어떤 방법을 써도 상관은 없지만, 대부분 main 브랜치를 기준으로 두고 feature 브랜치에 추가(혹은 삭제) 된 기능을 반영하는 편이다.
병합 커밋이 완료된 후 feature/developA
브랜치도 필요 없으므로 삭제시켰다.
확인을 위해 커밋 후 main 브랜치로 이동해 보면 변경사항이 적용되지 않았음을 확인할 수 있다.
추가로 Branch 2개에 변경사항을 적용할 때 같은 파일을 변경한 후 커밋 하면 충돌할 가능성이 존재한다.
따라서 2개 브랜치를 생성한 뒤 브랜치마다 다른 파일에 변경사항을 생성한 뒤 커밋하도록 하자.
(예를 들어 A 브랜치는 README 파일을 수정했다면 B 브랜치에서는 AdminController.java 파일을 수정하자)
IntelliJ에선 "현재 브랜치" 기준으로 병합(Merge)를 원하는 브랜치를 합치는 방식으로 동작한다.
우리는 Main 브랜치를 기준으로 새로 생성한 2개 브랜치의 작업물을 반영시키고 싶은 것이므로 일단 메인 브랜치로 이동하자.
파란색 네모를 클릭하여 병합시킬 브랜치를 정할 수 있다.
일단 빨리 감기 병합을 테스트하기 위하여 fast_forward_branch
로 이름 지은 브랜치를 main 브랜치
에 병합시키겠다.
Merge
버튼 클릭!
참고로
Modifiy options
를 클릭하여 Merge Option을 선택할 수도 있다.
아래 CLI를 통한 Merge에서 명령어 Option을 설명할 텐데 이를 공부하고 한 번씩 사용해 보자
성공적으로 Main 브랜치에 반영되었음을 확인할 수 있다.
옵션을 사용하지 않았다면 아마 Merge 이후에도 병합시킨 브랜치가 살아 있을 것이다.
더 이상 해당 브랜치(실습에선fast_forward_branch
)는 사용되지 않으므로 삭제하자.
이제 새로 생성한 브랜치 중 남은 브랜치까지 병합시켜보자.
fast_forward_branch
의 작업 내용은 이번에 병합시키고 싶은 merge_commit_branch
에 존재하지 않으므로 빨리 감기 병합이 아닌 병합 커밋일 것이다.
병합 후 2개의 변경 사항이 정상적으로 main 브랜치에 적용되었음을 확인해 보자.
git merge [Option] <branch명>
IntelliJ에서도 보았지만 git merge
명령어는 "현재 HEAD가 가리키고 있는 브랜치(커밋)"를 베이스로 명령어에 입력한 브랜치의 변경점을 반영하는 동작을 수행한다.
따라서 CLI로 병합을 시키기 전 꼭 "병합(Merge) 시 베이스로 삼고 싶은 브랜치"로 이동(checkout
) 한 다음 위 명령어를 입력하도록 하자.
git merge --no-ff <branch명>
: 현재 브랜치와 병합 대상의 관계와 상관 없이 무조건 병합 커밋(Merge Commit)으로 Merge 됨
git merge --ff-only <branch명>
: 현재 브랜치와 병합 대상의 관계가 Fast-forward(빨리 감기 병합)인 경우에만 병합 진행
git merge --squash <branch명>
: 현재 브랜치에 병합 대상과 차이나는 commit들을 1개로 뭉쳐 병합
git merge -m [메시지] <branch명>
: 병합 커밋(Merge Commit) 시 메시지를 담아 병합
--no-commit
, --no-verify
는 많이 사용되지 않는 옵션인 것 같아 따로 설명하진 않겠다.
git merge
를 통해 병합한 뒤 사용하지 않을 브랜치는 git branch -d <branch명>
명령어를 통해 삭제해 주는 것이 좋다.
그런데 브랜치 삭제 과정을 추가로 수행하는 것이 조금은 귀찮을 수 있다.
CLI Option이 이 귀찮음을 해결해 주지 않았을까 생각했지만, 아쉽게 그런 옵션은 없었다.(내가 찾지 못했던가...)
개인적인 뇌피셜로는 branch 삭제와 merge(병합)가 완전히 다른 로직이라 합치지 못했거나 안전상의 이유로 일부러 만들지 않았거나 둘 중 하나인 것 같긴 한데, 뇌피셜은 읽은 뒤 시원하게 머릿속에 지우고 그냥 "Merge와 브랜치 삭제를 명령어 1개로 동시에 진행할 수 없다"라는 것만 기억해두자.