Branch와 Merge

JaeungE·2021년 8월 2일
0

개발자의 친구들

목록 보기
2/5
post-thumbnail

Git을 이용해 버전 관리를 할 때, 이미 만들어진 버전을 토대로 새로운 기능을 추가하거나 버그를 수정해야 하는 경우가 생긴다.

이전에 작업하던 공간에서 그대로 이어서 해도 상관없지만, 그렇게 되면 기존에 잘 작동하던 코드를 손상할 우려가 있고, 또한 여러 사람이 협업으로 각각의 기능을 개발하게 되면 서로의 작업 영역을 침범하는 일도 생길 수 있다...😥

이러한 문제를 해결하기 위해서 Git에는 branchmerge 기능이 존재한다.





Branch

Git의 브랜치는 working directory를 분리하는 기능이다!😮

생성된 브랜치는 사용 목적에 따라 크게 통합 브랜치(Integration Branch)토픽 브랜치(Topic Branch)로 나뉘게 된다.

브랜치는 현재 GitHEAD가 가리키고 있는 브랜치를 기준으로 git branch [name] 명령을 이용해서 생성한다.


통합 브랜치

통합 브랜치는 실제로 배포할 버전을 가지고 있는 브랜치다.

배포해야 하는 버전이므로 통합 브랜치에 있는 버전은 프로그램의 모든 기능이 정상적으로 동작해야 한다.



토픽 브랜치

토픽 브랜치는 통합 브랜치에서 추가해야 할 기능이나 수정해야 할 버그 등 기존에 있던 버전을 수정하려 할 때 생성하는 브랜치다.

토픽 브랜치에서 작업한 내용을 merge를 통해 다시 통합 브랜치에 병합하는 식으로 작업을 진행하게 된다.





Checkout

새로운 브랜치를 생성했다면, 자신이 작업할 브랜치를 선택할 수도 있어야 하지 않을까?🤔

그럴 때 필요한 게 바로 checkout 기능이다. checkout 명령은 아래와 같이 실행한다.

git checkout [branch name | commit id]

checkout 명령을 사용하면 GitHEAD를 원하는 브랜치를 가리키게 하거나, 혹은 commit id를 통해 특정 version을 가리키게 할 수 있다.

이 외에도 git checkout -- [fileName]으로 현재 브랜치에서 modified된 파일을 수정하기 이전으로 되돌리는 것도 가능하다!

하지만 checkout2.23.0버전 이후로 switchrestore 명령으로 분리되었으니 참고하길 바란다.😉





Merge

이제 브랜치를 나눠서 작업했으면, 작업한 결과를 하나로 합쳐야 하지 않겠는가?

이럴 때 필요한 게 바로 merge 기능이다. merge 명령은 아래와 같이 실행한다.

git merge [target]

merge는 현재 GitHEAD가 가리키고 있는 브랜치를 기준으로 진행하기 때문에, checkout으로 먼저 HEAD를 변경하고 사용해야 한다.

또한 merge는 브랜치를 병합할 때 fast-forward 방식 혹은 3-way merge 방식을 이용해 병합하는데, 상황에 따라 사용하는 방식이 다르다.

아래에서 두 방식의 차이에 대해 알아보도록 하자!😆



fast-forward

예시를 위해 다음 이미지처럼 master 브랜치에 work_together.txt 파일을 생성했다고 하자.



이제 master 브랜치에서 update 브랜치를 생성하고, update 브랜치에서 새로운 기능(예를 들면 함수)을 추가한다고 해보자.

  1. git branch update
  2. git checkout update
  3. update file


이제 update 브랜치에서 작업이 끝났으면 변경 사항을 local repositorycommit한 후에, 배포용 버전을 가지고 있는 master 브랜치로 이동해서 update 브랜치를 merge로 병합하도록 하자.

  1. git commit -am "update new function"
  2. git checkout master
  3. git merge update


merge를 성공적으로 마치면 Fast-forward라는 문구가 보이고, master 브랜치와 HEADupdate 브랜치가 가리키던 버전으로 이동한 것을 볼 수 있다.

이처럼 fast-forward merge는 단순하게 merge의 기준이 되는 브랜치와 HEAD를 더 나중에 생성된 버전(merge 대상)이 있는 곳으로 옮기는 merge이다.

fast-forward merge보다 3-way merge를 이해하는 것이 중요하다. 어떤 방식인지 보도록 하자!🙂



3-way merge

어떠한 브랜치를 기준으로 여러 개의 토픽 브랜치를 만들어 작업을 진행한 뒤, 해당 브랜치들을 병합하려 할 때 Git은 기본적으로 3-way merge방식으로 병합을 진행하게 된다.

예제로 master 브랜치를 기준으로 한 명은 update 브랜치에서 기존 파일의 업데이트, 한 명은 newfile 브랜치에서 새로운 파일을 추가하는 작업을 한다고 해보자.

위의 fast-forward merge에서 썼던 예제를 이어서 사용해보겠다!

  1. git branch update
  2. git branch newfile
  3. git checkout update
  4. update file


작업이 끝났으면 commit을 진행하고, 새로운 파일의 추가보다 기존 파일을 업데이트하는 작업이 먼저 끝나서 update 브랜치를 우선 merge한다고 하자.

  1. git commit -am "update new function2"
  2. git checkout master
  3. git merge update


두 브랜치의 병합이 Fast-forward merge로 정상적으로 완료되었고, git log는 위와 같다.

이제 새로운 파일을 추가하는 작업을 진행해보자!

  1. git checkout newfile
  2. add new file


위처럼 newfile.txt 파일을 추가했다면, 새로 생성된 파일을 git add newfile.txtstaging area에 올려주고, commit을 진행한 뒤에 git log로 로그를 확인해보자.


  1. git add newfile.txt
  2. git commit -m "add new file"
  3. git log --oneline --graph --all


보시다시피 c62c28c 버전(과거 master 브랜치의 버전)에서 두 갈래로 나뉜 것을 확인할 수 있다.
이처럼 3-way merge에서는 각 브랜치가 갈라지기 전의 원본 버전인 c62c28cbase라고 부른다.

이제 다시 master 브랜치로 HEAD를 옮기고, newfile 브랜치를 병합해보도록 하자!


  1. git checkout master
  2. git merge newfile


master 브랜치의 내용을 보면 정상적으로 merge가 완료돼서 각 브랜치의 작업내용은 모두 병합되었으나, git log를 확인해보면 fast-forward merge와는 다르게 merge에 대한 commit message가 자동으로 작성된 것을 볼 수 있다.



그리고 merge 완료를 알려줄 때 Fast-forward가 아니라, "Merge made by the 'recursive' strategy." 라는 문구가 출력되는 것을 볼 수 있다.

이렇게 보면 3-way merge의 특징이 크게 드러나지 않는데, 아래의 conflict를 설명할 때 더 자세히 쓰도록 하겠다!





Conflict

conflict는 브랜치 병합 도중에 각 브랜치에서 수정한 영역이 겹치는 경우에 일어나는 충돌이다. 같은 파일을 여러 사람이 분담해서 작업할 때 발생한다.

하지만 conflict가 발생한 부분만 적절히 수정해주면 다시 정상적으로 브랜치 병합이 가능해지므로 겁먹을 필요는 없다!😉

예를 들어 master 브랜치를 baseworkAworkB 브랜치 두 개를 생성하고, 각 브랜치에서 모두 하나의 파일을 수정하는 작업을 한다고 해보자.



base가 될 master 브랜치의 내용은 위와 같다.

이제 토픽 브랜치들을 생성하고, 각자 작업을 진행할 내용은 아래와 같이 정했다고 하자!

  • Commit Ver은 1.0으로 통일
  • workA 브랜치에서 작업하는 사람은 3번 라인의 기능을 수정할 것.
  • workB 브랜치에서 작업하는 사람은 2번 라인의 내용을 "merge"로 수정할 것.
  • 모든 토픽 브랜치에서 4번 라인에 새로운 기능을 추가할 것.

그럼 이제 새로운 브랜치들을 생성하고 작업 내용에 맞게 conflict.txt 파일을 수정해보자.

workA

workB


commit 까지 완료하고 git log를 확인해 보면 이 전 예제에서 보았던 구조로 되어있다.

그럼 이제 master 브랜치로 HEAD를 옮긴 후, workA 브랜치부터 병합해보도록 하자!


fast-forward merge를 이용해 정상적으로 병합된 것을 볼 수 있다.

그렇다면 남아있는 workB 브랜치도 마저 병합해보자!


workB 브랜치를 자동으로 병합하던 도중에 conflict.txt에서 conflict가 발생했다고 알려준다.

무슨 일이 일어났는지 conflict.txt 파일을 확인해 보도록 하자.


======는 해당 부분에서 conflict가 발생했다는 의미이다.

====== 를 기준으로 위쪽은 현재 HEAD가 가리키고 있는 버전의 내용이고, 아래쪽은 병합하려는 브랜치에 있는 내용이라고 보면 된다.

이 상태로는 자동으로 병합이 불가능해서 conflict가 발생한 부분을 적절하게 수정해줘야 한다.

아래와 같이 수정하고 commit을 해보자!



발생했던 conflict를 해결하고, 정상적으로 모든 브랜치가 병합된 것을 확인할 수 있다.

사실 위의 예제는 3-way merge가 아니라 마치 2-way merge로 병합한 것처럼 conflict가 발생했는데, 이는 버그가 아니라 Git이 수정한 라인을 기준으로 앞뒤 라인에 수정이 생기면 conflict를 발생시키는 것 같다...😅

아마도 실제 코드를 작성할 때, line feed 없이 함수들을 붙여서 작업하는 경우는 없어서 위와 같은 일이 벌어지지는 않을 것으로 생각된다.

그렇다면 실제로 3-way merge2-way merge가 어떻게 다른지, conflict를 발생시키는 기준을 테이블을 통해 알아보도록 하자!😆


baseworkAworkB
Commit Ver 1.0Commit Ver 1.0Commit Ver 1.0
basebasemerge
base functionbase function updated by workAbase function
new function made by workAnew function made by workB

위 테이블은 각 브랜치에서 수정한 내용들이다. rowline이라고 생각하고 보도록 하자.



2-way merge

2-way mergebase를 고려하지 않고, 두 브랜치의 내용만을 비교해서 conflict를 발생시킨다.


workAworkBmerge
Commit Ver 1.0Commit Ver 1.0Commit Ver 1.0
basemergeconflict
base function updated by workAbase functionconflict
new function made by workAnew function made by workBconflict

두 브랜치에서 동일한 부분만 merge의 결과가 되고, 나머지 부분에서는 모두 conflict가 발생한다.

겨우 네 줄짜리 내용인데도 불구하고 내용 중 1/4이 conflict가 발생해서 일일이 수정해줘야 하는데, 만약 변경한 라인이 1,000개가 넘어간다고 생각하면 끔찍한 일이 아닐 수 없다.😱

그래서 Git은 더욱더 많은 부분을 알아서 처리할 수 있도록 3-way merge 방식을 채택했다.



3-way merge

3-way merge는 두 브랜치를 병합하려고 할 때, base의 내용도 고려해서 병합을 진행한다.


baseworkAworkBmerge
Commit Ver 1.0Commit Ver 1.0Commit Ver 1.0Commit Ver 1.0
basebasemergemerge
base functionbase function updated by workAbase functionbase function updated by workA
new function made by workAnew function made by workBconflict

결과를 보면 workAworkB 브랜치에서 동시에 수정한 부분을 제외하고, 나머지는 Git이 적절한 내용을 자동으로 선택해서 병합한 것을 알 수 있다.

3-way merge는 갈라져 나온 브랜치의 조상이 되는 base와 각각의 브랜치의 내용을 토대로, 두 브랜치에서 모두 수정된 내용만 conflict를 발생시킨다.

그 외에 어떠한 내용을 자동으로 병합할지 선택하는 기준은, 3개의 브랜치 중 하나의 브랜치에서만 수정된 내용을 선택해서 merge에 반영한다.

이처럼 3-way merge2-way merge에 비해 많은 부분을 알아서 병합해서 굉장히 편리한 병합 방식이다!🤗

2개의 댓글

comment-user-thumbnail
2022년 3월 4일

구글링하다가 우연히 보게 되었는데 너무 쉽게 설명해주셔서 감사합니다!

1개의 답글