이번 소재거리는 친구들과 함께 Git에 대한 면접 질문으로 뭐가 나올 수 있을까에 대해서 고민을 하다가 우연히 등장했습니다.

딱 봐도 드립치기 좋아보이네요

 

협업할 때 대부분의 케이스에서 기본 Merge만 사용해왔던 저는

 

기본 Merge로도 충분한데, 다른 건 나중에 공부하자~

 

라고 안일하게 생각했었기에 이 질문에 대해서 제대로 된 답변을 할 수 없었죠.

 

그래서 이번 기회에 자세하게 알아보았습니다!

 

목차


    0. 머지에도 전략이 있다?

    GitHub에서 Pull Request를 날릴 때 유심히 살펴봤다면, 아래와 같은 Merge 전략을 확인하실 수 있었을 것입니다.

    저는 알았음에도 바꾸지 않았지만

     

    아래 사진으로 확인할 수 있듯이, 머지 전략에는 대표적으로 일반적인 Merge commit을 남기는 방법, Squash and merge, Rebase and merge 세 가지 방법이 있습니다.

     

     

    각 전략들이 어떤 기능을 하고 어떻게 동작하는 지를 알아야 해당 기능을 잘 쓸 수 있겠죠?

     


    1. 전략이 왜 필요한건데?

    여기서 잠깐, 대표적인 세 가지 전략을 살펴보기 전에 왜 저런 다양한 전략들을 사용해야 하는지부터 알아봅시다.

     

    Merge가 뭔지 제대로 이해하기 위해서는 Commit에 대한 이해가 필요합니다.

     

    Commit이란 파일이나 폴더의 추가, 변경 사항을 저장소에 기록하는 행위입니다.

    그리고 이러한 Commit들이 쌓여서 커밋 히스토리를 만들게 되는데, 이 커밋 히스토리를 통해 저장소 내에서 도대체 무슨 일이 벌어졌는지에 대해서 알 수 있게 됩니다.

     

    최근 진행한 사이드프로젝트인 십이장기의 깃 그래프

     

    만약 개발을 하다가 휴가를 떠났습니다.

    그리고 오랜만에 돌아왔더니 내가 그동안 뭘 했는지 기억이 안날 수 있겠죠?

    이럴 때 커밋 내역을 보고 이런 일들을 했구나 쉽게 파악할 수 있게 됩니다.

     

    또한 협업할 때 다른 사람들이 남겨놓은 코드의 변경사항을 쉽게 파악하는 데에도 쓰입니다.

    시간 순서대로 나와있고, 해당 커밋에서 변경된 내용만 쉽게 파악할 수 있기 때문에 잘 이해가 안가거나 문제가 발생하더라도 해당 부분만 찾아서 확인할 수 있다는 어마어마한 효율성을 보여줄 수 있습니다.

     

     

    하지만 이렇게 유용하게 쓸 수 있는 커밋 히스토리의 커밋 메시지를 대충 쓴다면 어떻게 될까요?

    커밋 히스토리를 열심히 쌓아봐야 유지보수에도 아무 도움이 안되고 내가 무슨 내용을 했는지도 모르게 됩니다.

    이러면 커밋 히스토리를 쌓을 필요가 전혀 없어지게 되는 것이죠.

    그렇기 때문에 우리는 의미있는 단위의 커밋을 하고 의미 있는 커밋 메시지를 작성해야 합니다.

     

    이렇게 하시면 안돼요!

     

    약간 이야기가 샜는데, 이렇게 중요한 커밋메시지를 잘 작성했음에도 브랜치가 너무 중구난방이라 알아보기 어려운 경우가 있을 수 있습니다.

    아래 예시는 제가 Sooltreaming 개발을 하면서 만들어냈던 지하철 노선도(?) 입니다.

    해당 시점에 개발을 할 때는 기본 Merge 전략만 사용했기 때문에 저런 그래프가 등장하게 되었습니다.

    아래 화려한 깃 그래프가 알아보기 엄청나게 어려운건 아니지만 조금 더 깔끔하게 정리할 수도 있었을 텐데 하는 아쉬움도 약간 드네요!

     

    으악

     

    이제 이것들을 가독성 있게 만들기 위한 용도로 만들어진 머지 전략에 대해서 배워봅시다!

     


    2. 세 가지 머지 전략

    2.0. Create a merge commit

    기본 Merge라고도 불리는 Create a merge commit의 경우 각각의 브랜치에 남은 커밋을 히스토리에 그대로 남깁니다.

    그래서 어떤 브랜치에서 어떻게 만들어져서 이렇게 머지가 되었구나를 아주아주아주 상세하게 파악할 수 있죠.

     

    사용법은 아주 단순합니다.

    git merge {머지할 브랜치}

     

    출처:https://meetup.nhncloud.com/posts/122

     

    다만 해당 방식의 경우 지하철 노선도마냥 굉장히 자세하게 히스토리가 남아서 다소 보기 어려울 수 있다는 단점이 있습니다.

    별다른 HEAD 조작을 하지 않았다면 develop 혹은 main 브랜치가 맨 왼쪽에 위치해서 어찌어찌 따라갈 수 있을 지도 모르지만, 만약 HEAD가 밀려버리는 상황이 발생하게 되면 그래프의 순서가 꼬여버릴 수도 있게 됩니다.

    그러면 이제 develop 혹은 main 브랜치가 어디서부터 어디까지인지 눈알 빠지게 찾아버려야 하는 상황이 발생할 수도 있습니다...!

     

    사실 얘도 출발은 하나였지만..

     

    2.1. Squash and merge

    Squash and merge개발용 브랜치에 있던 내용들을 하나로 합쳐서 중앙 브랜치에 하나의 커밋으로 저장하는 전략입니다.

    새롭게 발생하는 머지 커밋은 개발용 브랜치에서의 변경사항들을 하나도 뭉쳐놓은 커밋이 되는 것이죠.

    기존 변경사항들이 어떻게 변했는가보다 머지가 되었다에 좀 더 집중한 전략이라고 보시면 되겠습니다.

     

    Create a merge commit에 비해서 남아있는 정보량이 비교적 적기 때문에, 개발용 브랜치에서 언제 어떤 코드를 바꿨는지에 대한 정보를 잃을 수 있다는 단점도 가지고 있습니다.

    (필요 없는 간단한 console.log 수정이나 버그 수정같은 것들을 뭉뚱그려서 합치는 데에 쓰일 수 있겠죠?)

     

    출처:https://meetup.nhncloud.com/posts/122

     

    Squash and merge를 아까 만든 커밋메시지 테스트용 브랜치에 적용해보도록 하겠습니다.

    커밋 메시지는 따라하면 안됩니다

     

     

    이 상태에서 testBranch에서의 변경사항 3개를 하나로 뭉뚱그려서 처리할 수 있습니다.

     

     

    git merge --squash {머지할 브랜치}

    명령어를 쳤더니 staging area에 변경사항이 올라갔습니다. (아직 commit이 안된 상태)

    이제 저장소에 새롭게 뭉뚱그린 커밋을 등록하기 위해 git commit -m "커밋메시지"만 하면 되겠네요!

     

     

    깔끔하게 하나로 처리되었습니다!

    Squash and merge는 이게 끝입니다. 간단하죠?!

     

     

    2.2. Rebase and merge

    Rebase and merge는 말 그대로 이미 존재하는 Rebase라는 기능을 이용해서 브랜치를 머지하는 것입니다.

    히스토리의 Base를 직접 옮겨서 처리하는 방식이기 때문에 개발용 브랜치에서 변경한 내용을 중앙 브랜치에서 변경한 것처럼 바꿔버릴 수 있습니다.

    위에서 본 Create a merge commit에서 만들어진 지하철을 한 줄로 예쁘게 만들 수 있다는 것이죠!

     

    이 방식도 단점이 있는데, 바로 기본적으로 Merge commit이 없기 때문에 어느 시점에 Merge가 되었는지 나중에 판단하기 어렵다는 것입니다.

    이걸 보완하기 위해서 어디서 Merge가 되었는지 태그를 남겨놓기도 합니다.

     

    출처:https://meetup.nhncloud.com/posts/122

     

    Rebase and merge도 실제로 테스트 해 보죠!

    다시 초기 상태로 돌아왔고, testBranch2에 커밋을 더 쌓아보았습니다.

     

     

    testBranch의 base를 testBranch2로 바꿔버렸습니다.

     

     

    그러면 아래와 같이 한줄로 쭉 이어지게 됩니다.

    (base가 testBranch2와 같기 때문에 2줄일 필요가 없어진 것이죠!)

     

     

    이제 testBranch2로 넘어가서 testBranch와 merge를 하게 되면?

    단순히 해당 Branch의 HEAD의 위치만 바꾸는 Fast-forward가 수행되어 머지가 마무리됩니다.

     

     

    한 줄로 쭉 이어졌지만, 머지했다는 커밋이 없죠?

    아주아주아주 깔끔해졌습니다!

     

     

    실제 개발 상황에서 머지한 위치를 기록으로 남겨야 하는 상황이라면, 

    git rebase -i HEAD~<n>

    명령어를 통해 rebase의 기능을 활용해서 커밋 메시지를 변경시키는 방법을 사용하셔도 좋습니다.

    (위 코드는 HEAD로부터 n번째 커밋까지 불러와서 대화형 명령(-i)을 통해 커밋을 이리저리 바꾸겠다는 말입니다) 

     


    3. 3-way merge와 Fast-forward merge

    위에서 세 가지 머지 전략에 대해서 살펴봤는데, 조금 더 깊게 생각했다면 병합을 도대체 어떻게 처리할까? 라는 고민까지 충분히 하셨을 것이라고 생각합니다.

     

    그래서 위에서 살짝 언급한 Fast-forward merge와 함께 브랜치를 합치는 로직인 3-way merge를 간단하게 알아보고 넘어가려고 합니다!

     

    3.0. Fast-forward merge

    위에서 간단하게 언급했지만, 서로 다른 상태를 병합하는 것이 아니라 단순히 HEAD만 이동시키면 Merge가 처리될 수 있는 환경에서는 Fast-forward merge가 수행됩니다.

     

    아래 상황에서는 단순히 testBranch2의 HEAD를 testBranch로 옮기기만 하면 되는 것이죠.

    위에서 사용한 사진 2개를 모아 보면 간단히 파악할 수 있습니다.

     

    Fast-forward와 관련해서 특별하게 처리할 수 있는 명령어가 두 가지 있습니다.

    병합 대상 브랜치와 현재 브랜치와의 관계가 Fast-forward라고 하더라도 머지 커밋을 남기고 싶다면 아래 명령어를 사용합니다. (Rebase and merge를 할 때 머지했다는 커밋 메시지를 남기고 싶다면 이걸 쓰면 됩니다!)

    git merge --no-ff {머지할 브랜치}

     

    반대로 Fast-forward가 아니면 머지를 하지 않고 싶다면 아래 명령어를 사용하면 됩니다.

    git merge --ff-only {머지할 브랜치}

     

    3.1. 3-way merge

    하지만 세상은 이렇게 단순하지 않습니다.

    매우 높은 확률로 두 명의 개발자는 서로 다른 내용을 바꿨을 것이고, 지금처럼 HEAD만 옮겨서는 제대로 머지가 되지 않는 상황이 발생하게 됩니다.

     

    이 때 사용하는 비교 방식이 바로 3-way merge입니다.

    만약 1번 브랜치와 2번 브랜치가 병합을 한다고 하면, 총 세 가지 커밋을 서로 비교하게 됩니다.

     

    • 1번 브랜치 커밋
    • 2번 브랜치 커밋
    • 1번 브랜치와 2번 브랜치의 공통 조상 커밋

     

    만약 단순하게 1번 브랜치와 2번 브랜치의 현재 상태만 비교(2-way merge)한다면, 어떤 요소가 어떻게 변했는 지, 혹은 두 브랜치가 충돌해서 병합할 수 없는 상태인 지 알 방법이 없습니다.

    b와 b'를 비교했을 때 b가 원본임을 증명할 수 있는 방법이 없는 것이죠.

     

     

    이걸 증명하기 위해서 공통 조상을 가져와보겠습니다.

    이제 공통 조상을 기준으로 변동사항을 파악한다면 충돌 여부를 명확하게 알 수 있게 됩니다.

     

    a의 경우 변동사항이 없었으니 그대로 a가 됩니다.

    그리고 b의 경우 원본에서 변경된 내용이 2번 브랜치한테만 있으니 2번 브랜치의 b'가 결과값이 됩니다.

    하지만 c의 경우 두 브랜치에서 모두 변경되었기 때문에 결과를 c'로 해야할 지, c''로 해야할 지 알 수 없기 때문에 충돌이 난다는 것을 확실하게 알 수 있는 것이죠.

     

     

    이렇게 보다 명확하게 두 브랜치의 충돌 여부를 파악하기 위해서 세 개의 커밋을 비교해서 머지를 진행한다는 뜻을 가진 용어가 바로 3-way merge입니다.

     


    4. 참고자료

     

    [Git] Merge(3-way merge) 이해하기 - SW Developer

    다른 형상 관리툴들과는 달리 git은 branch를 생성할 때 파일을 복사하는 것이 아니라 파일의 스냅샷만 가지고 생성하기 때문에 자원의 부담없이 branch를 만들어 사용할 수 있다. 이러한 장점 때문

    wonyong-jang.github.io

     

    커밋 히스토리를 이쁘게 단장하자

    이번 포스팅에서는 Git의 머지 전략 중 대표적인 3가지인 , , 의 차이에 대해서 한번 이야기해보려고 한다. 이 3가지 머지 전략 모두 브랜치를 머지한다는 목적은 같지만, 어떤 방식을 선택하냐에

    evan-moon.github.io

     

    GitHub의 Merge, Squash and Merge, Rebase and Merge 정확히 이해하기 : NHN Cloud Meetup

    GitHub의 Merge, Squash and Merge, Rebase and Merge 정확히 이해하기

    meetup.nhncloud.com

     

    [Git] Merge 이해하기 (Merge / Squash and Merge / Rebase and Merge)

    회사에서 Git을 사용해서 형상 관리를 하고 있다. 그 동안 내가 개인 repository branch에 commit, push등을 해본 적은 많지만 다른 사람과 협업을 하면서 branch를 생성하고 master에 merge를 해본 적은 없어서

    im-developer.tistory.com

     

    Git Merge에서 Fast Forward 관계 이해하기 - Jay's Blog

    Git Merge에서 항상 헷갈리는 Fast Forward 관계에 대해 정리해보았습니다. 🙌 들어가며 Git을 사용하다보면 브랜치를 분기하여 다시 병합하는 일이 빈번하게 일어납니다. 최근 Git 커밋 히스토리 관리

    otzslayer.github.io

    반응형
    • 네이버 블로그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기