thumbnail
Merge vs Rebase vs Cherry-Pick
Git
2022.08.12

Merge 🗞

먼저, 기능 구현을 할 때 순서를 정리해보자면?

(1) 기능 구현을 위해서 해당 기능을 구현하기 위한 브랜치를 생성한다.

(2) 기능 구현이 되는 경우 해당 기능을 main 브랜치에 merge 한다.

이처럼 기능을 모두 완성하게 되면 사용하고 있는 main 브랜치에 본인이 작성한 기능 commit 을 모두 합쳐줘야 한다.

두 브랜치를 합치는 과정을 git에서는 merge 라는 기능으로 도와준다.


먼저, 이 과정을 직접 눈으로 확인하면서 체크해볼 수 있는 Git GUI, 소스트리(SourceTree) 프로그램을 설치해보자.


🪄 Fast-forward

image

  • 기능 브랜치에서 기능 구현을 완료했다 생각하고, main 브랜치로 해당 작업을 합쳐보자.

$ git checkout main
$ git merge feat/md

image

  • 동작 결과를 보면 Fast-forward가 동작 되었다는 것을 볼 수 있다.

image

  • 깃 그래프 상에서도 제일 아래 commit인 ‘Update:README.md’ commit을 가리키고 있던 main 브랜치가
    기능 브랜치가 가리키고 있던 commit 쪽으로 올라왔다.

Merge 의 방법 중 하나인 Fast-forward,

단어 그대로 가리키고 있던 commit 을 merge 할 브랜치의 commit 으로 이동하는 것이다!

보통 현재 브랜치가 merge 할 브랜치로 최신 정보들을 모두 따라갈 때,

두 브랜치의 base 가 같을 때 Fast-forward를 기본적으로 제공한다.

예를 들어, A 브랜치에 B 브랜치를 merge 할 때 B 브랜치가 온전히 A 브랜치 이후의 commit들을 가리키고 있으면
A 브랜치를 B 브랜치로 이동하기만 한다.


🪄 3-way-merge

지금까지는 특별한 분기점 없이 merge 하는 과정을 나타냈다면, 조금 다른 상황을 이야기를 해보자.

기존 main 브랜치에 변경사항이 생긴 것을 기능 브랜치에 반영을 해야한다면?


image

  • main 브랜치를 분기한 develop 브랜치에서 변경 작업이 생겨서 빨간색의 다른 브랜치 뿌리가 진행되어서 올라가고 있는 모습을 볼 수 있다.

  • 작업 중인 로컬 기능 브랜치인 main2 브랜치에 ‘Feat: 다른 내용 추가’ commit 을 합쳐야 한다.


🧐 이 상황에서 Fast-forward 가 되지 않는 이유는?

Fast-forward 이 실행되는 조건은 두 브랜치의 base 가 같아야 하는데,

위 상황에서는 두 브랜치의 base 가 다르기 때문에 결과가 다를 것으로 예상된다.


일단 main2 브랜치에서 merge 를 진행해보자.

$ git fetch origin develop
$ git merge origin/develop

image

image

  • 새로운 merge commit 인 Merge remote-tracking branch 라는 commit 이 생성되면서 합쳐지는 것을 볼 수 있다.
  • 그러면서 main2 기능 브랜치가 해당 commit 을 가리키도록 변경이 된다.

Git 이 별도의 최적 공통 조상을 자동으로 찾는 auto-merge 로 commit 을 하나 생성한 후에 해당 commit 을 가리키도록 이동하는 것을 알 수 있다.
그럼 이제 변경된 develop 브랜치의 요구사항을 반영한 채 다른 기능 구현을 진행할 수 있게 된다.


🚧 Conflict

기능 브랜치에서 코드 작성을 쭉 해오다가,
main 브랜치와 동일한 파일에 내용을 추가하거나 수정을 진행하고 commit 을 했다면?

동일한 파일을 수정하려는 상황이기 때문에 똑같은 수정 파일이 있어 충돌이 발생한다.


우선, 반영 사항을 진행해야 하니까 똑같이 merge 를 진행해보자.

image

  • 메시지들을 보면 auto-merge 를 시도했지만, ‘README.md’ 파일에서 conflict 가 발생해서 실패했다고 나온다.

image

  • git status 명령어를 치면 더 자세한 이력을 볼 수 있다.
  • unmerge 되어있는 commit 이력이 있으니 이를 해결해야 하며, 해당 path는 ‘README.md’ 파일에서 발생했다고 보여준다.

image

  • 깃 그래프 내에서도 merge 되어 있지 않은 uncommited changes 라는 회색 commit 이 하나 생성되어 있음을 확인할 수 있다.

해당 코드로 넘어가서 코드가 어떻게 되어있는지 확인을 해보면,

image

conflict 가 발생한 부분에 대해서 두 브랜치에서 작성한 코드 두 개를 전부 포함하여 보여주고 있다.

  • 위에 있는 HEAD 브랜치가 현재 브랜치고, 아래에 origin/develop 브랜치가 merge 할 브랜치다.
    이 때 merge 를 계속 진행하기 위해서는 해당 부분을 직접 수정하거나 IDE에서 제공하는 자동 수정을 진행하면 된다.

image

  • 수정 후 git add 명령어를 통해서 해당 파일을 staging 하면 기다리고 있던 이 회색 merge commit 에 포함이 된다. 이후에 git commit 명령어를 진행하면 merge commit 이 정상적으로 생성된다.


💁🏼 Github 에서 제공하는 3가지의 Merge 방법

Git 에서 Fast-forward 은 항상 기본적으로 제공한다. (옵션을 걸어 제거해야만 다른 merge 로 제공된다)

다만, Github 의 Pull Request 은 조금 다르게 동작하는 세 가지의 merge 방법을 제공한다.

image

1. Create a merge commit

$ git checkout main
$ git merge --no-ff feature

image

base 가 같더라도 Fast-forward 를 진행하는 것이 아니라 하나의 merge commit 을 생성해서 진행하는 방법이다.

  • 이렇게 진행하게 될 경우, 기능 구현을 할 때 사용했던 commit 들이 하나하나 살아있어 이를 사용할 여지를
    남김과 동시에 만든 기능에 대한 merge 분기점이 생김으로써 어떤 기능을 만들어 main 브랜치에 merge 했는지 가독성이 좋아진다.

2. Squash and merge

$ git checkout main
$ git merge --squash feature
$ git commit -m "squash merge message"

image

‘Create a merge commit’와 같이 하나의 merge commit 을 생성해서 해당 commit 을 바라보게 하는데,
feature 브랜치에서 작업했던 모든 commit 을 하나의 commit 으로 통합해서 merge 하는 작업이다.

  • 이렇게 될 경우 여러 개의 기능 구현이 있을 때 알아보기 힘든 점이 있고, 특정 기능에 대한 commit 을 하나만 두어서 이전보다는 어떤 기능을 작업했는지 가독성이 더 좋아지는 장점이 있다.

image

  • 실제로 작성한 commit 과 merge 된 commit 의 내용이 다른 것을 확인할 수 있다.
    이처럼 Pull Request 각각에 대한 commit 을 하나만 남기고 어떤 기능을 완료했는지 한 눈에 알 수 있기 때문에 가독성이 높아진 모습이다.

3. Rebase and merge

$ git checkout feature
$ git rebase master

$ git checkout master
$ git merge feature

간단하게 말해서 base 를 다시 조정하는 것이다.
즉, feature 브랜치에서 작업했던 commit 들을 모두 현재 main 브랜치의 최상단에 복사 붙여넣기 하는 작업이다.

다만 이렇게 작업했을 때, commit 이 많을 때 한 줄로 모든 commit 들이 저장되어 오히려 더 난잡해 보일 수 있다.

👩🏻‍💻

각각의 merge 방법에 대한 장점과 단점을 파악해서 현재 팀에서 가장 적절하다고 생각하는 merge 방법을 선택하는 것이 좋다. 정답이 있는 것이 아니다!

🚀 Merge 요약

  1. Merge 할 때 base 가 같으면 Fast-forward 한다.
  2. Merge 할 때 base 가 다르면 Merge commit을 생성하여 Auto-merge 한다.
    단, conflict 가 발생할 경우 개발자가 직접 처리한다.
  3. Github Pull Request 에서 merge 할 수 있는 방법으로 Create a merge commit, Squash and merge,
    Rebase and merge 가 있다. 3가지 방식 각각의 장단점을 활용해서 팀에 필요한 방법을 사용하면 된다.

Merge와 비슷한 친구지만 조금 다른 Rebase 📋

Merge 는 3-way-merge 로 merge 에 대한 commit 이 추가되고, 브랜치가 합쳐졌다는 기록이 남지만
Rebase는 이것보다 깨끗한 commit history 를 만들 수 있다.


원격 저장소에 있는 내용이 바뀌었을 때, 현재 로컬 저장소와 싱크를 맞춰본다면?


image

로컬 저장소의 commit 내역과 원격 저장소의 commit 내역이 다르기 때문에 브랜치가 갈라진 것을 확인할 수 있다.


rebase 를 통해서 브랜치를 합쳐보자!

$ git fetch origin develop
$ git rebase origin/develop

image

  • develop 브랜치에 추가된 commit 이 중간에 들어온 것을 확인할 수 있다.
    merge 와는 굉장히 다른 그래프가 그려졌는데, 어떻게 그려졌을까?

어쨌든, merge 와 rebase 의 목적은 같다 🫱🏻‍🫲🏼

한 브랜치에서 다른 브랜치를 합치는 것이다. 다만, merge 는 새로운 commit 을 만들어서 합치는 것이고

rebase 는 현재 branch의 Base 자체를 재설정하여 합치는 것이다.

main 브랜치의 base 는 ‘Update: README.md’ commit 으로, origin/developorigin/main가 나뉘어지는 분기점이라고 할 수 있다.

rebase 이후 깃 그래프가 선형적으로 잘 이어져있는 모습을 볼 수 있는데
이를 보면, 기존에 있던 commit 들은 그대로 존재하고 base 포인트만 변경된다고 생각할 수 있는데 그건 아니다!

rebase 는 base 가 바뀔 commit 들을 복사하여 연이어 붙이는 것이다!


image


이 말이 사실인지 확인하기 위해 commit id 를 확인해보면,
rebase 전의 commit id 들은 rebase 후의 commit id 와 다른 것을 볼 수 있다.


Rebase 전 image

Rebase 후 image

  • commit 메세지는 같은데 commit id 가 다른 것을 보면, 복사 후 새로운 commit 을 생성했다는 것을 알 수 있다.

🚀 Rebase 요약

  1. Rebase는 브랜치를 합치려는 목적으로 사용된다.
    commit history 가 merge 와는 다르게 선형적으로 그려진다.

  2. Rebase는 현재 브랜치의 base 를 바꾸는 것이다.
    생성된 commit 들은 새롭게 복사되어 base 가 변경된다.

  3. Merge 를 한 코드 결과와 Rebase를 한 코드 결과는 같아야 한다.


쏙쏙 골라서 Cherry-pick 🍒

만약에 develop 브랜치에서 기능1, 기능2, 기능3 을 열심히 구현 중이라고 가정해보자.


image

이 때, 기능2 commit 만 당장 퍼블리싱해야 하고 기능3 commit 은 아직 테스트가 완료되지 않아서
main 브랜치에는 올리고 싶지 않은 상황이라면?

기능2 commit 만 main 브랜치에 적용하는 방식은 없을까?


이런 상황에서 Cherry-pick 을 사용한다.

Cherry-pick 을 통해서 내가 원하는 commit 이 merge 되는 상황을 천천히 살펴보자.


✌️ Cherry-pick 2가지 방법

1. main 브랜치에서 cherry-pick을 하고 바로 origin/main 으로 푸쉬하는 방법

$ git checkout main
$ git cherry-pick 30696ac
$ git push origin main
  • 명령어를 보면, main 브랜치로 checkout을 하고, 기능2에 대한 commit id를 적고 cherry-pick 을 한다.
    그리고 바로 origin/main 으로 push 한다.

  • 기존 image

  • cherry-pick 이후 image image
    • 원하던대로 기능2 commit 만 origin/main 으로 포함된 모습을 볼 수 있다.
    • 하지만 이렇게 브랜치에 직접적으로 push 하는 방법은 주로 사용되지 않는다.
      주로 브랜치를 따로 생성해서 main 브랜치로 merge PR 을 요청한 후에 merge 되는 경우가 많다.

2. cherry 브랜치를 따로 생성한 후 merge PR 요청하는 방법

$ git checkout main
$ git checkout -b cherry
$ git cherry-pick 30696ac
  • 명령어를 보면, main 브랜치로 checkout을 해서 cherry라는 브랜치를 새로 만들어준다.
    cherry 브랜치에서 기능2 commit 에 대한 cherry-pick 을 진행하고 origin/cherry 로 push 한다.

  • 기존 image

  • cherry-pick 이후

    image


  • 깃허브 PR 요청 image
    • origin/cherry 브랜치를 origin/main 브랜치로 PR을 보낸다.
      그렇게 누군가가 main 브랜치로 merge 를 시켜주면 다음과 같은 그래프가 그려진다. image
    • origin/main 브랜치와 origin/cherry 브랜치가 ‘none forward merge’로 commit 이 하나 생성이 되고
      기능2에 대한 commit 이 origin/main 에 포함된 것을 확인할 수 있다.

이렇게 cherry-pick 을 사용하면 원하는 commit 만 쏙쏙 골라서 다른 브랜치에 반영할 수 있다.

📌 Cherry-pick 사용법

  • 한 개의 커밋을 가져오고 싶을 때
$ git cherry-pick 30696ac

git cherry-pick 명령어 다음에 가지고 오고 싶은 커밋 id를 적어주면 된다.


  • 여러 개의 커밋을 가져오고 싶을 때
$ git cherry-pick 30696ac 45298fd 65639a1

여러 개의 커밋을 가져오고 싶을 때는 commit id를 공백으로 구분해서 적어주면 된다.


  • 연속된 커밋을 가져오고 싶을 때
$ git cherry-pick 30696ac..65639a1

특정 구간에 있는 연속되는 commit을 불러올 수 있다. 가지고 오고싶은 연속된 커밋들 중 시작 commit의 아이디와
마지막 commit의 아이디를 적고 그 사이에 .. 표시로 이어주면 된다.

🚧 Cherry-pick Conflict

가져오려는 commit 과 내 코드 상황이 다르면 당연히 충돌이 발생한다.

  1. conflict을 해결하기 위해 코드를 수정한다.
  2. git add 명령을 통해 수정된 코드를 add 한다. (staged 상태로 변경)
  3. git cherry-pick --continue 명령을 수행해서 모든 충돌을 해결하고 정상적으로 commit 을 불러온다.

🚀 Cherry-pick 요약

  1. Cherry-pick 으로 다른 브랜치 commit 을 내 브랜치로 가져올 수 있다.
    한 개, 여러 개 또는 연속된 구간의 commit 들을 가져올 수 있다.

  2. Cherry-pick이 된 commit 들은 복사가 된다.

  3. Cherry-pick 진행 시 충돌이 발생할 수 있으며, git add & continue 명령어로 해결한다.

  • Cherry-pick 을 사용할 때 유의점이 있다.
    특정 커밋을 가져오게 되면, 같은 내용을 가진 commit 들이 여러 개가 생기기 때문에 나중에는
    누가 누구를 cherry-pick 했는지 모르는 상황이 생길 수 있다. 😮
    따라서 이러한 유의점을 잘 파악하고 사용하는 것을 추천한다.

✨ 정리

프로젝트를 관리하면서 Merge 와 Rebase 를 항상 사용하면서도 정확하게 어떤 차이점이 있는지,

어떤 상황에서 적용해야 하는지 별 생각없이 사용을 해왔었다. (차이점만 알고 있는 정도로?)

이번 포스팅을 정리하면서 merge 와 rebase 의 차이점과 종류들에 대해서 알게 되었다.

무엇보다 새로운 git 명령어인 cherry-pick 에 대해서 알게 된 점이 좋았고,

Fast-forward, none forward merge 등 새로운 개념을 확실히 알게 되어 뿌듯하다!

Git 을 더 자유자재로 사용할 수 있는 그 날까지 … 😎 화이탱

참고 내용

우아한테크톡 youtube

Thank You for Visiting My Blog, Have a Good Day 😊
©2021 Developer minkyung choi, Powered By Gatsby.