git merge 명령어 정리


  • Git의 merge에 대해서 공부하고 정리한 글입니다

Merge

  • 병합(merge)는 간단히 말해서 두 버전의 합집합을 구하는 것이다
  1. Merge commit(병합 커밋) : +A U A* = +A*

  2. Fast-forward(빨리 감기) : A U B = B

    • 합친 결과물이 B와 동일하기 때문에 새로 상태를 만들어 줄 필요 없이 B로 상태를 바꿔주면 된다
  3. Conflict(충돌) : A+ U A* = A+? A*?

    • 무엇을 기준으로 합쳐야할지 모르기 때문에 충돌이 난 부분만 확인하고 무엇을 남길지 수동으로 선택해 주면 된다

git merge 브랜치이름
# 지정한 브랜치의 커밋들을 현재 브랜치 및 워킹트리에 반영한다



  • 커밋4는 커밋2를 단순하게 수정한 최신본이기 때문에 두 상태를 합치면 바뀌는 상태 없이 커밋 4가 되는 Fast-forward가 된다

  • 따라서 merge 후에 marster branchfeature/detail-page 브랜치는 커밋 4를 가리키게 된다

  • 커밋5는 커밋 2를 중심으로 상태가 바뀌었기 때문에 커밋4에 있는 marster branch와 커밋5에 있는 feature/cart 를 합치게 되면 merge commit이 된다

  • 이 때 상황에 따라, 새로운 merge commitmarster branch에 둘지, feature/cart에 둘 지 선택한다.

  • 즉, A와 B브랜치가 있을 때 두 개를 합쳤을 때 만들어진 AB브랜치를 A 브랜치에 올릴 건지, B 브랜치에 올릴 것인지 정하는 것이다.

  • 만약 A 브랜치에 올린다면 AB브랜치가 A 브랜치에만 반영되고 B 브랜치에는 반영이 안된다

  • 따라서 [master] 브랜치를 기준으로 병합한다는 것은 합친 결과물을 [master]브랜치에 반영한다는 것이다


  • 두 커밋이 같은 코드를 수정했다면 병합 커밋을 만들다가 충돌이 날 가능성이 있다

  • 그래서 동료들과 같이 쓰는 [master] 브랜치에 바로 병합하지 않고 나만 쓰는 [feature/cart]에서 먼저 병합해보고 문제가 없는지 확인한다

  • 1단계 : master 브랜치를 땡겨와서 feature/cart 브랜치에서 병합한다

  • 2단계: feature/cart 브랜치에서 병합한 커밋을 master 브랜치에 반영한다


  • Fast-forward 는 히스토리에 merge 되었다는 사실이 남지 않지 않는다 하지만 히스토리에 남기고 싶은 경우라면 Fast-foward가 가능해도 새로운 commit을 만들어서 master branch에 commit할 수 있다

  • 아래처럼 master 브랜치에서 새로운 브랜치를 만들고 새로운 커밋을 만든 후에 master 브랜치를 feature/c브랱치와 merge 하면 Fast-forward가 된다

$ git checkout -b feature/c

$ echo c > c.txt

$ git commit -m "Add c branch"

  • 하지만 merge 흔적을 히스토리에 남기고 싶은 경우에는 다음처럼 --no-ff라는 옵션을 사용한다

  • 아래처럼 --no-ff을 사용하면 새로운 커밋을 입력받기 위해 텍스트 에디터가 실행되고, 커밋 메시지를 작성한 후에 git log로 확인해 보면 merge 커밋이 생성되어있고 master 브랜치는 생성된 merge 커밋을 가리키고 있는 것을 확인할 수 있다

$ git merge --no-ff

$ git branch -d feature/c

$ git log
# 새로운 커밋 생성되어 있고 master 브랜치는 새로운 커밋 가리킨다
# feauture/c 브랜치는 삭제되었지만 feature/c와 합쳐졌다는 것을 알 수 있따



3-way 병합하기

  • 3-way merge는 fast-forward merge에서 히스토리에 남는 것을 선호해서 새로운 커밋을 만들고 싶거나 또는 fast-forward가 불가능한 경우에 사용한다

  • master 브랜치에서 새로운 feature/a 라는 브랜치를 만든 다음 여러개의 커밋을 추가하였고 master 브랜치에도 커밋이 추가되어 있는 경우엔은 fast-forward를 사용할 수 없다

  • 그 이유는 master 브랜치에서 feature/a 브랜치를 만든 다음 추가한 커밋에 대한 정보를 잃어버리게 되기 때문이다

  • 따라서 아래 처럼 원래의 브랜치에서 새로운 커밋이 추가된 경우 3-way merge를 이용해야 한다

        ----- C3 <-- C4(feature/a)
        ↑
C1 <-- C2 <-- C5 <-- C6 <-- C7 (master)
  • 즉, 베이스 branch인 master 브랜치와 파생된 feature/a 브랜치의 변동사항을 모두 합해서 merge 커밋을 만든 다음에 master 브랜치에 커밋해야 한다

  • git merge를 사용할 때 아무런 옵션 없이 사용하는 경우에 fast-forward가 가능한다면 merge commit을 만들지 않고 fast-forward merge를 진행하지만 fast-forward가 불가능한 상황이라면 커밋 메시지를 입력하도록 진행된다

$ git merge feature/a
# 새로운 커밋 메시지 입력 받는다
  • 3-way merge 후 다음과 같은 상태가 된다
        ----- C3 <-- C4(feature/a)
        ↑
C1 <-- C2 <-- C5 <-- C6 <-- C7 <-- C8 (master)
  • 3-way merge를 실무에서는 다음과 같은 상황에서 사용할 수 있다

  • 버그를 발견한 상황에서 버그 수정은 다음과 같은 단계로 이루어 진다

    1. (옵션) 오류가 없는 버전(주로 Tag가 있는 커밋)으로 롤백

    2. [master] 브랜치로부터 [hotfix]브랜치 생성

    3. 빠르게 소스 코드 수정 / 테스트 완료

    4. [master] 브랜치로 병합 (Fast-forward) 및 배포

    5. 개발 중인 브랜치에도 병합 (충돌 발생 가능성이 높음)

  • 버그가 발생한 상황에서는 원래 작업 중이던 브랜치도 [master] 브랜치로 부터 시작했기 때문에 같은 버그를 가지고 있을 것이다

  • 때문에 [hotfix] 브랜치의 내용은 [master] 브랜치와 개발 브랜치 모두에 병합 되어야 한다

  • 보통 [master] 브랜치의 병합은 빨리 감기이기 때문에 쉽게 되는 반면 개발 중인 브랜치의 병합은 병합 커밋이 생성되고 충돌이 일어날 가능성이 높다

$ git checkout master

$ git checkout -b feature1 # 브랜치 생성 및 체크아웃

$ echo "기능 1 추가" >> file1.txt

$ git add file1.txt

$ git commit

$ git log --oneline --all --graph -n2

  • 위 상황에서 커밋한 이후 장애가 발견되었을 때

  • [master] 브랜치에서 [hotfix] 브랜치를 만들고 버그를 고친 후에 커밋을 한다

  • 그리고 [hotfix]브랜치를 [master]브랜치에 병합한다

  • [master]브랜치의 최신 커밋을 기반으로 [hotfix] 브랜치 작업을 했기 때문에 빨리 감기 병합이 가능하다

  • 따라서, 아래처럼, [hotfix] 브랜치를 만들고 버그를 해결한 다음 [master] 브랜치와 Merge한다


$ git checkout -b hotfix master # master로 부터 hotfix브랜치 생성, 체크아웃

$ git log --oneine --all -ne # 2개의 커밋 로그만 보기

$ echo "some hot fix" >> file1.txt

$ git add file1.txt

$ git commit

$ git log --oneline -n1

$ git checkout master

$ git merge hotfix

$ git push

  • hotfix 커밋은 버그 수정이었기 때문에 이 내용을 현재 개발 중인 [feature1] 브랜치에도 반영해야 한다

  • [feature1] 브랜치와 [master] 브랜치은 서로 다른 분기로 진행되고 있다.

  • 이 경우에는 빨리 감기 병합이 불가능 하므로 3-way 병합을 해야한다

  • 따라서 병합 커밋이 생성된다

  • 모든 3-way 병합이 충돌을 일으키는 것은 아니지만 두 브랜치 모두 file1.txt를 수정했기 때문에 충돌이 발생한다

$ git checkout feature1 # 개발 중인 브랜치로 checkout

$ git log --oneline --all

$ git merge master # master 브랜치와 병합
# 충돌 발생

$ git status # 실패 원인 파악

# visual studio (editor)를 열어서 충돌이 일어난 부분을 수정한 후
# 변경 내용을 저장하고 다시 스테이지에 추가 및 커밋을 하면 수동으로 3-way 병합이 완료된다

$ cat file1.txt # 변경 내용확인

$ git add file1.txt

$ git status

$ git commit # git commi 명령으로 충돌난 3 way 병합을 마무리 짓는다

$ git log --oneline --all --graph -n4


Conflict 해결 방법

  • 다음은 C2에서 새로운 feature/a 라는 브랜치를 생성하고 난 이후 feature/a라는 브랜치와 master 브랜치 모두 동일한 파일을 수정한 경우이다
        ----- C3 (feature)
        ↑
C1 <-- C2 <-- C4  (master)
  • 이러한 경우 merge를 하면 에러가 발생한다
$ git merge feature
# 에러 발생

$ git status
# 내용 확인
  • 이러한 에러가 발생했을 때 수동으로 해결할 수 있는 방법은 다음과 같다
$ open main.txt
# 에러 발생한 파일 열어준다

# 파일 열면 아래처럼 파일이 충돌한 부분 확인할 수 있다
<<<<<<< HEAD
Oh.. Here!! From master branch!
=======
Oh.. Here!! From feature branch!
>>>>>>> feature


# master 브랜치에 있는 내용 쓰고 싶으면 feature 브랜치 내용 삭제해주고
# feature 브랜치에 있는 내용 쓰고 싶으면 master 브랜치 내용 삭제해준다
# 혹은 둘다 넣고 싶은 경우 confict와 관련된 <<<HEAD, >>>feature 지워준다

  • 다만 이처럼 confict 해결할 때 다른 작업은 하지 않고 충돌한 부분만 해결한다
$ git merge --abort
# merge 취소하고 싶은 경우

$ git status
# both modified 확인할 수 있다
# (use "git add <file>..." to mark resolution) 부분 통해 add 해야하는 것을 알 수 있다

$ git add main.txt

$ git merge --continue
# fast forward가 아니기 때문에 merge commit 입력 받는다

$ git log
# 새로운 커밋 만들어진 것 확인할 수 있다
  • vscode로 confict 해결하는 방법
$ git config --global -e

# 아래 명령어를 추가해준다
[merge]
  tool = vscode
[mergetool "vscode"]
  cmd = code --wait $MERGED


# 충돌 발생하면 vscode가 열린다
$ git mergtool

$ git status
# main.txt.orig 라는 이전에 merge confict가 발생했을 때의 내용을 포함한 파일도 함께 있다

$ git config --global mergetool.keepBackup false
# 해당 파일이 생기는 것을 막기 위한 옵션

  • vscode 말고 또 다른 방법으로 merge를 할 수 있는 툴로는 p4merge 가 있다

$ git config --global -e

[mergetool "p4merge"]
  path = "설치경로"

$ git mergetool
# p4merge가 실행된다

Reference









© 2020. by dkmqflx

Powered by dkmqflx