Git 병합 / 충돌

    1. Git의 병합

    • Git은 브랜치 단위로 작업한다. 따라서 병합의 기준은 브랜치가 된다.
    • 특정 브랜치에서 일을 하고, 그 브랜치의 커밋 내용을 다른 브랜치에 반영하는 형태로 된다. 
    • 이 때 병합하고자 하는 브랜치는 같은 로컬 저장소에 있어야 한다.
    • 병합하는 알고리즘은 Fast-Forward / 3-way 방식 두 가지가 존재한다. 
    $ mkdir gitstudy08
    $ cd gitstudy08
    
    $ git init
    
    $ vim index.htm
    Hello Git world
    
    $ git add .
    $ git commit -m "first"
    • 먼저 테스트를 위해 다음과 같은 작업을 진행한다. 

     

     

    2. Fast-Forward 병합

    • 주로 혼자 개발할 때 많이 사용하는 방법이다. 
    • 브랜치가 생성된 커밋에 따라 순차적으로 분기되고, 코드 수정도 순차적으로 할 때가 많다. 즉, 브랜치가 분기되지만 전체 커밋 그림으로 보면 모든 변경 사항은 순차적이다.
    • 순차적 커밋에 맞추어 병합을 처리하는 방법이 Fast-Forward 방식이다. 

     

    2.1 브랜치 생성 / 수정

    # 마스터 브랜치에서 브랜치 생성
    $ git branch feature
    $ git checkout feature
    
    # index.html 파일 수정
    # 1. <header></header> 추가 + 커밋
    # 2. <ul> <li> 깃소개</li> </ul> 추가 + 커밋
    # 3. <li> 깃 설치 </li> 추가 + 커밋
    • 새로운 브랜치를 생성하고, feature 브랜치에서 3번의 커밋을 순차적으로 생성해본다. 

    • 위의 명령을 수행하면 다음과 같이 순차커밋이 발생한 것을 볼 수 있다.
    • 서로 다른 두 개의 브랜치지만, 순차적으로 커밋했기 때문에 다음과 같이 한 줄로 보이게 된다. 
    • 이 모양의 브랜치가 병합될 때는 Fast-Forward 방식으로 병합된다. 

     

    2.2 병합 위치

    $ git merge <병합할 브랜치>
    • Git은 현재 브랜치에 다른 브랜치가 병합되는 형식이 된다. 
    • 위의 명령어를 사용하면 현재 브랜치를 기준으로 "병합할 브랜치"의 변경 내용이 현재 브랜치에 반영된다. 

    • master 브랜치에서 git merge feature를 하면, feature 브랜치의 변경점이 master 브랜치에 반영된다.
    • 병합할 때, 순차적 커밋이 되었기 때문에 Fast-Forward 방식으로 Merge된 것이 명시된다. 

    • 병합 전후로 master / feature가 가리키는 HEADER 포인터가 같은 것을 볼 수 있다. 

     

    3. 3-Way 병합

    • Fast-Forward는 master 브랜치에서 feature 브랜치가 만들어지고, feature 브랜치에서만 변경점이 발생했을 때다. 
    • 그렇지만 실제로는 master / feature 브랜치에서 변경점이 동시에 발생할 때가 있다. 이 때는 브랜치가 순차적이지 않게 되는데, 이 때 3-Way로 병합한다. 

    3.1 브랜치 생성 / 수정

    # hotfix 브랜치 생성
    $ git checkout -b hotfix
    
    # hotfix 브랜치에서 수정
    #1. <footer> </footer> 등록 + 커밋 
    #2. copryright all right 2020 reserved by ojt90902 등록 + 커밋 
    
    # 마스터 브랜치 변경
    $ git checkout master
    
    # 마스터 브랜치에서 수정
    #1. <li> 커밋 </li> 등록 + 커밋 
    #2. <li> 브랜치 </li> 등록 + 커밋
    • 위 명령어를 각각 수행해서 hotfix / master 브랜치가 가리키는 HEAD가 다르도록 설정한다. 

    • 이 때, 소스트리에서 현재 브랜치 상태를 살펴보면 다음과 같다.
    • hotfix / master 브랜치는 공통 조상을 가지고 있지만 서로 갈라져 나왔다.
      • hotfix / master 브랜치는 각각의 커밋 2개를 가지고 있다.

    3.2 공통 조상

    • 브랜치 모양이 갈라지는 형태일 때는 3-way 알고리즘이 적용되어 merge 된다.
    • 이 때는 1) 공통 조상 커밋을 포함하는 브랜치  2,3) 새로운 두 브랜치를 하나로 병합한다. 따라서 3-way 병합이라고 한다.

    3.3 병합 커밋

    • 3-way 병합은 두 브랜치에서 공통 조상 커밋을 자동으로 찾아주며, 공통 조상 커밋을 기준으로 브랜치를 병합해준다.
    • 병합을 성공적으로 완료한 후에는 새로운 커밋(병합 커밋)을 추가로 하나 생성해준다.
    • 이 때, 병합 커밋은 부모 커밋이 2개가 된다. (master 브랜치 / hotfix 브랜치) 

    • 다음과 같이 merge 되는 것을 볼 수 있다. 

    • log를 찍어보면 "Merge branch hotfix"라는 것이 남아있는 것을 볼 수 있다. 

    • 소스트리에도 다음과 같이 확인된다.
    • 각 브랜치는 특정 Commit 시점을 가리키는 것이고, Merge 된 결과를 가리키기 위해서는 결국 특정 Commit 시점을 만들어야 되기 때문에 hotfix merge 커밋이 만들어진다. 

    4. 병합 후 삭제

    $ git branch -d hotfix
    • 병합된 브랜치는 일반적으로 더 이상 개발하지 않는다. 더 이상 개발하지 않는 브랜치는 삭제해버리면 편하다.

    5. 충돌

    • 여러 사람이 코드 작업을 하는 경우, 같은 부분을 수정하게 되면 충돌이 발생한다. 
    • 이 경우는 자동 병합이 되지 않고, 사람이 하나하나 일일이 수정해서 충돌을 없앤 후 Merge 해야한다.

    5.1 충돌 일부러 만들기

    # footer 브랜치로 분기
    $ git checkout -b footer
    
    # footer copyright 수정
    #1. copyright all right 2018 reserved
    #   by ojt90902
    # 위의 형태로 수정 + commit
    
    
    # master 브랜치로 체크아웃
    $ git checkout master
    
    # 동일한 위치 내용 수정
    #1. coypright all right 2020 reserved
    #   by ojt10000
    # 위의 형태로 수정 + commit
    • 위의 명령을 이용해서 일부러 충돌을 만든다. 
    $ git merge footer
    • 위 명령을 이용해서 footer 브랜치를 마스터 브랜치에 병합한다. 

    • 이 때, 동일한 부분이 수정되었기 때문에 다음과 같은 문구가 나오며 merge가 되지 않는다.
    • index.html에서 충돌이 발생했고, 충돌을 수정하고 그 결과를 커밋하라는 문구가 나온다.

    • 소스트리와 git bash에서 다음과 같은 값을 볼 수 있다.

    • 충돌이 발생하면 현재 브랜치 이름에 merging이라는 문구가 뜬 것을 볼 수 있다. 이것은 병합 충돌을 의미한다. 

    5.2 병합 취소

    # 방금 실행한 merge 취소하기
    $ git merge --abort

    위 명령을 이용하면 방금 실행한 Merge가 취소된다.

    이 상태에서 git merge --abort 명령어를 입력했다. 

    conflict가 나서 merge 되지 않았던, "커밋하지 않은 변경사항"이 없어진 것을 볼 수 있다. 

    5.3 수동으로 충돌 해결

    현재 상태

    • 병합 충돌이 발생하면 수동으로 해결해야한다. 이 때, 직접 소스 코드를 확인하고 충돌된 부분을 수정해야한다. 
    • 깃은 충돌이 발생되면 충돌된 코드 내용을 표시해준다. 
    $ vim index.html
    • 위 명령어를 이용해서 충돌이 발생한 index.html로 들어가본다. 

    • 들어가보면 현재 어떤 부분이 충돌되고 있는 지를 보여준다.
    • HEAD는 현재 병합의 기준이 되는 브랜치의 변경 사항을 보여준다. 아래에는 병합할 브랜치의 변경점을 이름과 함꼐 보여준다. 
    • 충돌한 내용을 수정할 때는 깃에서 표시한 충돌 기호도 함께 삭제해줘야한다!

    • 다음과 같이 삭제해준다. 
    $ git add index.html
    $ git commit -m "resolve conflict"
    • conflict가 발생하면, Git은 자동으로 Merge Commit을 만들어주지 않는다. 
    • 따라서 사용자가 git add / git commit을 통해 새로운 병합 커밋을 작성해야한다. 

    • 병합 커밋을 작성하면, MERGING의 문제가 없어진 것을 볼 수 있다. 

    • 또한 다음과 같이, master / footer 브랜치가 병합된 것을 볼 수 있다. 

    6. 브랜치 병합 여부 확인

    # 병합된 브런치 확인
    $ git branch --merged
    
    # 병합되지 않은 브런치 확인 
    $ git branch --no-merged
    • 위 명령어를 이용하면 병합된 브랜치와 아닌 브랜치를 확인할 수 있다. 

    • 병합을 완료한 브랜치는 *로 표시된다.

    7. 리베이스

    • 리베이스는 브랜치를 합치는 또다른 방법이다.
    • 리베이스는 커밋 순서를 재배열하면서 커밋을 병합해준다. 

    7.1 베이스

    • 모든 브랜치는 뿌리가 존재한다. 
    • 브랜치는 특정 커밋을 가리키는 포인터다.
    • 새로운 브랜치가 파생되는 커밋을 베이스 라고 한다.

    7.2 베이스 변경

    • rebase는 파생된 브랜치의 기준이 되는 베이스 커밋을 변경한다. 예를 들어 커밋 2에서 파생된 브랜치를 rebase해서 커밋6을 베이스로 바꾸는 작업을 한다.
    • 이런 작업은 커밋의 진행 모습을 단순화하기 위해서다. 좀 더 알아보기 쉽게 위함으로 이해할 수 있다. 

    7.3 리베이스 vs 병합 <그림 추가>

    • 병합은 파생된 두 브랜치를 하나로 합친다. 공통 조상 커밋을 찾고, 서로 다르게 커밋이 진행된 두 브랜치를 3-way로 병합한다. (공통 조상 / 두 브랜치). 
    • 두 브랜치는 최종 커밋을 생성한다.
    • 리베이스는 두 브랜치를 서로 비교하지 않고, 순차적으로 커밋 병합을 시도한다. 
      • 1. 공통 조상 커밋을 찾는다.
      • 2. 파생된 브랜치의 diff를 임시 공간에 잠시 보관한다. (파생된 브랜치 커밋 2개, diff를 2개 저장한다)
      • 3. master 브랜치의 커밋1→ 커밋2 → 커밋5 → 커밋6까지 순차적으로 진행한다.
      • 4. master 브랜치의 마지막 커밋에서 차례로 임시 공간에 저장한 diff를 하나씩 저장한다.  (diff 2개 적용)
    • 리베이스를 하면, 병합 커밋은 없다. 브랜치의 마지막을 가리키는 커밋 위치가 다르다. 

    7.4 리베이스 명령어

    # rebase 명령어
    $ git rebase 브랜치
    • 위 명령어를 이용해서 rebase 처리할 수 있다.
    # 새로운 브랜치 생성
    $ git checkout -b description
    
    # <h2> 깃은 소스의 변경 이력을 관리할 수 있습니다.</h2>
    # 해당 커밋을 추가한다. add description
    
    # 마스터 브랜치로 이동
    # git checkout master
    
    # <li> 병합</li> 
    # 해당 커밋을 추가 add menu5
    
    # <li> 리베이스</li>
    #  해당 커밋을 추가 add menu6
    • 위의 명령어를 이용해서 새로운 브랜치를 만들 수 있다.
    • 이제 아래에서 Rebase를 이용한 병합을 처리해본다. 

    7.5 리베이스 병합 <그림 추가>

    • 병합은 파생 브랜치가 기준 브랜치로 병합된다.
    • 리베이스는 기준 브랜치가 파생 브랜치로 병합된다. 
    • 따라서, description 브랜치로 checkout 후 리베이스를 진행해야한다. 

    • 현재 소스트리로 바라본 상태는 다음과 같다. 이 때 리베이스를 실행해본다.
    $ git rebase master
    • 위 명령어를 이용해서 description의 리베이스를 진행한다.

    • 실행하면 다음과 같이 성공적으로 이루어졌다는 것을 볼 수 있다. 

    • 소스트리에서 실행 결과를 확인하면, add menu5 → add menu6을 리베이스해서, add menu6에서 새로운 브랜치가 하나 더 만들어져 "add description"이 된 것을 볼 수 있다. 
    • 또한, master는 add menu6을 가리키지만, description은 add description 커밋을 가리키고 있는 것을 볼 수 있다.

    7.6 리베이스 되었는지 확인

    $ git add log -3
    • 위 명령어를 이용해서 로그를 확인해서, 변경된 것을 볼 수 잇다. 

    • 다음과 같이 Commit 로그가 바뀐 것을 볼 수 있다. 원래 add description → menu5 → menu6의 형태였다.
    • Rebase 이후 menu5 → menu6 → description으로 Commit이 바뀐 것을 볼 수 있다.

    7.7 리베이스 후 브랜치

    • 리베이스가 되면 master 브랜치와 description 브랜치는 서로 다른 commit 시점을 가리킨다.
    • 리베이스는 커밋 위치를 재조정할 뿐, 브랜치의 HEAD 포인터까지 옮겨주지는 않기 때문이다. 
    • 따라서 리베이스 이후에 병합 브랜치의 HEAD를 맞춰줘야한다. 
    # 마스터브랜치로 변경
    $ git checkout master
    
    # master 브랜치에 merge
    $ git merge description
    • 위 명령어를 이용하면 master 브랜치에 description 브랜치를 병합하면서, 문제는 완료된다. 

    • 다음과 같이 정상적으로 merge 되어 같은 Commit 지점을 가리키고 있는 것을 볼 수 있다. 

     

    8. 리베이스 충돌과 해결

    • 서로 다른 브랜치에서 동일한 지점을 수정하면, 리베이스에서도 충돌이 발생할 수 있다. 
    • 리베이스에서 충돌이 발생하는 경우를 살펴보고, 어떻게 해결하는지를 살펴본다.
    • 리베이스는 커밋을 하나씩 따라가면서 위치를 재조정한다. 따라서 충돌을 수정하고, git rebase --continue를 통해서 문제를 계속 해결한다.  

    8.1 리베이스 충돌 환경 만들기

    # 메뉴 브랜치 생성
    $ git checkout -b menu
    
    # 브랜치 / 리베이스 사이에 아래 코드 추가
    # # <li><ul> 병합</ul></li>
    # comment : edit menu5
    
    # 브랜치 아래에 다음 코드로 변경
    # <li>병합
    #   <ul><li> 리베이스</li></ul>
    # </li>
    # comment : edit menu6
    
    # 마스터 브랜치로 이동
    $ git checkout master
    
    $ 브랜치 / 리베이스 사이에 아래 코드 추가
    # <li>병합<ul></ul></li>
    # comment : edit submenu for menu5
    • 위 명령어를 이용해서 리베이스 충돌 환경을 만든다. 

    • 충돌 환경을 만들고, 브랜치 상태는 다음과 같이 되는 것을 볼 수 있다.

    8.2 리베이스 충돌 해결하기

    $ git checkout menu
    $ git rebase master
    • 위 명령어를 이용해서 menu 브랜치를 master 브랜치를 기준으로 rebase를 한다. 

    • 실행하자마자, index.html을 merge 할 때 충돌이 발생하는 것을 볼 수 있다.
    • 또한, 아래 창에 보면 Rebase 과정에서 2개의 충돌이 발생했다는 것을 알 수 있다. 

    • 들어가보면 현재 브랜치(menu) 헤드에 대한 변경점 / 그리고 실제 충돌이 발생한 부분이 잘 나타나진 것을 볼 수 있다. 
    • 이 때,  HEAD 부분을 삭제하고 다시 아래 명령대로 진행한다.  (충돌 제거)
    $ git add index.html
    
    # 리베이스는 이 명령어로 한줄씩 계속 변경해본다.
    $ git rebase --continue
    • 위 명령어를 이용해서 처리한다.

    • 처리하면 다음과 같이 menu|REBASE 1/2 → menu로 변경된 것을 볼 수 있다.

    • master 브랜치는 아직 이전 헤드를 가리키고 있기 때문에 menu 브랜치쪽을 가리키게 해주면 된다.

    • "git merge menu"를 이용해서 다음과 같이 바꿔줄 수 있다. 

     

    '기타 Tool > Git' 카테고리의 다른 글

    Git : Pull Request (PR)  (0) 2022.08.29
    Git 복귀  (1) 2022.08.29
    Git Branch  (1) 2022.08.27
    Git Remote Repository  (0) 2022.08.27
    Git의 Commit  (0) 2022.08.26

    댓글

    Designed by JB FACTORY