[Git] Fast-Forward Merge, 3-Way Merge 알아보기

Lee Youjin·2024년 12월 1일

이번에 교내 동아리에서 Git 관련 교육 자료를 작성하면서 Fast-Forward merge, 3-Way merge에 대한 내용을 다뤘다.
Git을 사용하는 사람들이 알아두면 좋을 내용인 것 같아서 벨로그에도 공유해보기로 했다. 도움이 되기를!

🍛 김치볶음밥으로 이해하는 Branch의 개념

Fast-Forward merge, 3-Way merge는 Merge의 방식으로, 너무 당연하게도 두 방식을 알기 위해서는 머지가 무엇인지부터 알고 있어야 한다.

그런데 사실 머지를 알기 위해서는 브랜치(Branch)의 개념을 먼저 알고 있는 게 중요하다. 이 브랜치를 한 문장으로 설명하자면 내가 작업하는 독립적인 영역 정도로 설명할 수 있는데, 이렇게 개념적인 말로 설명하면 이해하기 힘드니까 ... 김치볶음밥으로 예시를 한 번 들어보겠다.

모두가 다같이 협력해서 김치볶음밥을 만드는 상황에서 한명은 계란프라이를 만들고, 한명은 파를 썰고, 한명은 밥을 지어야 한다고 가정해봅시다.

이 세명은 서로에게 전혀 영향을 주지 않는 작업을 동시에 진행하게 됩니다. 그렇지만 각자의 작업이 끝났을 때는 파도 썰어져 있고, 계란프라이도 만들어져 있고, 밥도 지어져 있겠죠?

이렇게 각자의 작업이 끝나면 파, 밥, 계란프라이를 모두 합쳐 볶음밥을 만드는 작업을 진행하게 될겁니다.

교육 자료에 있던 걸 그대로 들고 와서 ㅎㅎ 말투가 약간 낯설지만 여기서는 김치볶음밥, 프라이, 파, 밥에 주목해보면 된다.
최종적으로 만들어야 하는 김치볶음밥은 프로덕트, 프라이와 파, 밥은 각자 브랜치에서 생성한 코드로 이해해본다면 다음과 같이 예시를 치환해 작성할 수 있다.

모두가 다같이 협력해서 프로덕트를 만드는 상황에서 한명은 A 기능을 작업하고, 한명은 B 기능을 작업하고, 한명은 C 기능을 작업한다고 가정해봅시다.

이 세명은 서로에게 전혀 영향을 주지 않는 작업을 동시에 진행하게 됩니다. 그렇지만 각자의 작업이 끝났을 때는 A 기능도, B 기능도, C 기능도 완성되어 있겠죠?

이렇게 각자의 작업이 끝나면 A, B, C 코드를 모두 합쳐 프로덕트를 만드는 작업을 진행하게 될겁니다.

이렇게 한 시점에서 서로에게 영향을 주지 않는 작업을 진행하기 위해 브랜치를 생성한다. 즉 브랜치는 개발자들이 동시에 작업하지만 서로에게 영향을 끼치지 않도록 처리해주는 것!
위와 같은 이유 때문에 보통 브랜치는 기능 단위로 많이 분리하곤 한다.

기본적으로 레포를 생성했을 때는 main 브랜치가 기본 브랜치로 설정되어 있어 따로 브랜치를 만들지 않았다면 내 현재 브랜치는 main이구나~ 하고 생각하면 된다.

🔗 브랜치 간 내용을 병합하는 Merge

사실 merge의 개념 자체는 제목과 같아서 더 설명할 여지가 없다 ... 현재 브랜치에서 다른 브랜치의 변경 사항을 가져오고 싶을 때 사용하는 명령어가 바로 git merge다.
그런데 사실 협업 시에는 해당 명령어를 잘 사용하진 않고 모두가 잘 아는 pull request를 작성하곤 한다. 충돌의 위험이 있기도 하고 변경사항을 메인 브랜치에 반영하기 위해서는 다른 작업자들의 승인이 필요한 경우도 있기 때문!

이런 Merge의 방식에는 여러 가지가 있지만 이번에는 가장 대표적인 Fast-Forward3-Way 방식을 알아보려고 한다.

🧠 HEAD의 위치를 바꾸는 Fast-forward merge

모든 브랜치에는 HEAD라는 값이 존재한다. HEAD는 현재 브랜치의 최신 커밋을 참조하는 값으로, 현재 내가 위치하고 있는 브랜치의 최신 커밋을 포인터로 가리키고 있다고 생각하면 된다!

그렇다면 HEAD의 위치를 바꾼다는 건 ... 변경사항을 합치는 게 아니라 최신 커밋을 참조하는 값만 바꾼다는 것! 포인터만 바꿔준다고 이해하면 쉽다.

아주 간단한 예시를 통해 Fast-forward merge를 이해해보자.

first_file.txt라는 파일 하나를 만들고 커밋을 진행했다. 이 때 브랜치는 main 브랜치!

이후 이렇게 간단하게 내용을 작성해서 커밋을 한 번 더 진행했다.

그 다음 main 브랜치에서 분기하는 A, B 브랜치를 각각 생성해주는데 이렇게 되면 A, B 브랜치에는 내용을 작성한 이후 커밋까지 반영되어 있을 것이다.

이후 B 브랜치로 Switch한 후에 내용을 추가적으로 작성하고 커밋을 진행했다.

플로우를 간단하게 정리하면

  1. main 브랜치에서 first_file.txt 파일 생성 후 커밋
  2. first_file.txt 파일에서 내용 작성 후 커밋
  3. A, B 브랜치 각각 생성
  4. B 브랜치로 이동 후 내용 추가 작성 및 커밋

정도다.

약간 복잡해보일 수도 있어서 위의 이미지를 통해 시각적으로 브랜치 분리와 커밋 이력을 확인해보겠다.

이제 위와 같은 상태에서 A 브랜치와 B 브랜치가 합쳐져야 한다면 어떻게 될까? A 브랜치에는 사실 B 브랜치에서 내용이 추가된 커밋만 있으면 된다.
아까 언급한 HEAD에 대해 한번 더 짚고 넘어가자면 HEAD는 현재 내가 바라보고 있는 브랜치의 가장 최신 commit을 가리킨다.
그렇다는 건 A 브랜치가 B 브랜치와 합쳐진다고 생각했을 때 현재 A 브랜치가 가리키는 커밋을 B 브랜치에서 내용이 추가된 커밋으로 설정하면 되지 않을까?
이렇게 진행할 경우 A 브랜치에서 내용 추가라는 변경사항을 새로 생성하지 않고도 참조 값을 변경해 두 브랜치의 변경 사항을 하나로 합칠 수 있다.
이렇게 변경사항을 합치기보다는 HEAD를 옮기는 방법을 fast-forward merge라고 한다.

🧙: 엥 그러면 fast-forward merge를 어떻게 하는 건데요?

라고 물어볼 수 있다. 답을 하자면 Git에서는 기준이 되는 브랜치에 신규 커밋이 존재하지 않고 다른 브랜치에만 새로운 커밋이 존재할 때 자동으로 fast-forward merge를 실행한다!

이렇게 fast-forward가 가능한 환경에서 merge 명령어를 실행하면 자동으로 Git에서 Fast-Forward 라는 메시지를 띄우고 merge를 수행하게 되는 것이다.

아주 간단하고 수월하게 merge를 할 수 있는 경우다.
그렇다는 건 복잡한 경우도 있다는 건데 ... 그런 경우에는 3-way merge를 이용한다.

3️⃣ 조금 더 복잡한 상황에서 세 커밋을 비교해 병합하는 3-way merge

아까 전에는 합쳐지는 브랜치 두 개 중 하나에서만 변경사항이 생겼다. 그렇다면 두 브랜치 모두 다른 변경사항이 생기는 경우에는 어떻게 될까? 이번에도 예시를 들어보겠다.
fast-forward merge 에서와 동일하게 main 브랜치에서 file.txt 파일을 생성하고 내용을 작성한 뒤 각각 커밋해보겠다.

현재 main 브랜치의 file.txt 파일 내용은 오른쪽과 같고, 이제 오른쪽 파일과 동일한 상태에서 각각 A, B 브랜치를 생성하고 다른 내용을 작성해보도록 하겠다.


각 브랜치에서 위와 같이 다른 내용을 작성한 후 각각 커밋했다. 각 변경사항을 커밋 그래프를 통해 살펴보면 다음과 같다.

현재 A와 B 브랜치 각각에서 새로운 커밋이 생성된 것을 확인할 수 있다. 두 브랜치를 합치고 싶은데 … 각각 다른 변경사항이 생겼기 때문에 fast-forward 방식으로는 merge 가 불가능하다!
이런 경우, 즉 두 브랜치에 모두 변경사항이 있어 여러 변경사항들을 비교해야 할 때 3-way merge를 활용한다.

3-way merge에 대해 알아보기 전에 만약 실제 사람이라면 두 변경사항을 어떻게 합칠지 생각해 보자.
아마 기본 베이스가 되는 부분인 main 브랜치에서 내용을 작성했어요! 내용이 두 브랜치에서 남아있는지 차례대로 비교한 후, 삭제/변경되지 않은 것을 확인하고 내용을 존속시킬 것이다.
이후 A, B 브랜치의 A 브랜치에서 내용을 추가했어요!B 브랜치에서 내용을 추가했어요!는 각각 새로 추가된 부분이므로 내용을 추가해주면 된다.

Git을 이용한 merge의 경우도 비슷한 플로우로 진행된다.
실제로는 두 브랜치가 갈라지고 변경사항이 생기기 전인 현재 main/HEAD 위치의 커밋을 기준으로 변경사항을 비교하게 되는데, main/HEAD 위치의 커밋과 각 브랜치의 변경사항을 순차적으로 비교한 후 Git이 충돌되는 부분이 없다고 판단하면 merge가 이루어지게 되는 것이다.

결론적으로는 아래와 같이 내용이 합쳐지게 된다!

커밋 그래프를 통해 내용을 살펴보면 다음과 같다.

커밋 그래프에서 확인할 수 있듯 fast-forward merge와 약간 다르게 병합이 진행되면서 새로운 커밋이 하나 생긴 것을 확인할 수 있다.
이렇게 서로 다른 두 변경사항이 합쳐지는 경우에는 반드시 두 변경사항을 병합하는 새로운 변경점을 생성하게 된다. 즉 3-way merge는 무조건적으로 새로운 커밋을 하나 생성하게 된다는 것!

🧙: 엥 그러면 왜 이름이 3-way merge인가요?

아까 현재 main/HEAD 위치의 커밋을 기준으로 변경사항을 비교하게 된다고 언급했었다. main/HEAD 위치의 커밋을 A/HEAD, B/HEAD의 커밋과 비교해 변경사항을 파악하고 합친다고 했었는데 그렇다면 이 merge에서는 총 3개의 변경점이 활용되게 된다!
이렇게 세 개의 커밋이 merge에 사용되기 때문에 3-way merge라고 부르는 것이다.

또한 A/HEAD, B/HEAD의 커밋을 비교할 때 사용된 main/HEAD 위치의 커밋을 base라고 부르기도 한다. 여튼 가장 중요한 건 3개의 커밋이 병합에 활용된다는 것!

이렇게 복잡한 경우의 merge 방식도 함께 알아보았다.


사실 나도 두 방식에 대해 정확하게 설명해보라고 하면 못했을 것 같은데 이번에 자료를 제작하면서 스스로도 개념을 다시 되새길 수 있었던 시간이었다 🥹
다음에는 교육 자료에 작성한 다른 부분들도 한번 정리해서 들고와보도록 하겠다. 안녕 ~

profile
꾸준히 발전하고 있습니다.

0개의 댓글