[Git] Merge, Fast-forward merge, Squash and merge ... Rebase

Minji Jeong·2023년 1월 23일
2
post-thumbnail

Git merge

develop 브랜치에서 feature 브랜치를 따서 작업을 한다. 이후 feature 브랜치의 개발이 완료되고, 완료된 feature 브랜치를 develop 브랜치에 추가하고자 할 때 우리는 'feature 브랜치를 develop 브랜치에 병합(merge)'한다.

일반적으로 우리가 git merge를 할 때, 몇가지 방법들이 있는데 그 중 fast-forward, non-fast-forward, squash and merge에 대해 소개해본다.

✅ Fast-forward Merge

fast-forward merge란 브랜치간의 merge를 진행할 때 새로운 merge commit이 생기지 않고, merge commit을 진행하는 브랜치의 head commit이 병합되는 브랜치의 head commit으로 이동되는 방식이다.

1. main 브랜치에서 develop 브랜치를 딴다.
2. develop 브랜치에서 개발이 완료되면 main 브랜치에 merge한다.
3. 이 때 develop 브랜치는 main 브랜치의 커밋 히스토리를 그대로 갖고있기 때문에, fast forward merge가 발생하여 main 브랜치의 head 커밋이 develop 브랜치의 head 커밋으로 옮겨진다.

동일한 작업을 터미널에서 수행해보자.

jinnie@Jeongs-MBP git1 % git commit -m "add b.txt c.txt"
[develop 913fa58] add b.txt c.txt
 2 files changed, 2 insertions(+)
 create mode 100644 b.txt
 create mode 100644 c.txt

jinnie@Jeongs-MBP git1 % git log
commit 913fa58ee621022115762fc4adbdf28048caea75 (HEAD -> develop)

    add b.txt c.txt

develop 브랜치에서 b.txt와 c.txt를 add & commit 한 후 git log를 확인해보면 해당 커밋(913fa5)이 올라온 걸 확인할 수 있다.

jinnie@Jeongs-MBP git1 % git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

main 브랜치로 체크아웃 한 후, develop 브랜치를 main 브랜치에 merge해보자.

jinnie@Jeongs-MBP git1 % git merge develop
Updating f22d5de..913fa58
Fast-forward
 b.txt | 1 +
 c.txt | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 b.txt
 create mode 100644 c.txt

jinnie@Jeongs-MBP git1 % git log
commit 913fa58ee621022115762fc4adbdf28048caea75 (HEAD -> main, develop)

    add b.txt c.txt

commit f22d5de0f5aa57c0c64dcce3b7b6a1a922b40a3b (origin/main)

    first commit

jinnie@Jeongs-MBP git1 %

main 브랜치의 head가 가리키는 커밋이 develop 브랜치의 head가 가리키는 커밋(913fa5)과 동일함을 확인할 수 있다.

특징

  • Merge commit 이 발생하지 않기 때문에 어디서 merge가 이루어졌는지 추적하기 어렵다. 따라서 --no-ff옵션을 주어 fast-forward merge 가 발생할 때도 커밋을 남기도록 하는 것이 좋다.

✅ Non-fast-forward Merge

브랜치간의 merge가 이루어질 때, 각 브랜치간의 커밋 히스토리가 다르다면 두 브랜치의 커밋이 합쳐진 새로운 merge commit이 merge를 수행하는 브랜치에 추가된다. merge commit은 두 개의 parent 커밋을 갖고 있고, 각 parent 커밋을 통해 병합된 브랜치들의 히스토리를 추적할 수 있다.

1. main 브랜치에서 develop 브랜치를 딴다.
2. main 브랜치에서 계속 작업이 진행중이고, develop 브랜치에서도 계속 작업이 진행되고 있다.
3. develop 브랜치에서 기능 개발이 완료된 후, main 브랜치에 머지한다.
4. 이 때 main 브랜치의 커밋 히스토리와 develop 브랜치의 커밋 히스토리가 서로 다르기 때문에 두 커밋 히스토리가 병합되어 새로운 merge commit이 발생한다.

동일한 작업을 터미널에서 수행해보자.

jinnie@Jeongs-MBP git1 % git add d.txt
jinnie@Jeongs-MBP git1 % git commit -m "add d.txt"
[develop 34ab599] add d.txt
 1 file changed, 1 insertion(+)
 create mode 100644 d.txt

jinnie@Jeongs-MBP git1 % git log
commit 34ab599b94b0443cbdaa344cf28dcb2967b5e8b1 (develop)

    add d.txt

develop 브랜치에서 d.txt 파일을 add & commit 한 후 git log를 확인해보면 해당 커밋(34ab59)이 올라온 걸 확인할 수 있다.

jinnie@Jeongs-MBP git1 % git checkout main   
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

jinnie@Jeongs-MBP git1 % git add e.txt
jinnie@Jeongs-MBP git1 % git commit -m "add e.txt"
[main ff327ae] add e.txt
 1 file changed, 1 insertion(+)
 create mode 100644 e.txt

jinnie@Jeongs-MBP git1 % git log          
commit ff327aea2160ccc19cb59a83fa94a228f0934369 (HEAD -> main)

    add e.txt

main 브랜치로 체크아웃 한 후, e.txt를 add & commit 해보자. 로그를 확인해보면 해당 커밋(ff327a)이 올라왔음을 확인할 수 있다.

jinnie@Jeongs-MBP git1 % git merge develop
Merge made by the 'ort' strategy.
 d.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 d.txt

// merge commit 생성
jinnie@Jeongs-MBP git1 % git log
commit 899828e8953b002a3fce3fab7e3180d3c65cc6bd (HEAD -> main)
Merge: ff327ae 34ab599

    Merge branch 'develop'

commit ff327aea2160ccc19cb59a83fa94a228f0934369

    add e.txt

commit 34ab599b94b0443cbdaa344cf28dcb2967b5e8b1 (develop)

    add d.txt

main 브랜치에 develop 브랜치를 머지한 후 로그를 확인해보면 두 커밋(34ab59, ff327a)이 합쳐진 새로운 커밋, 즉 merge commit이 생성되었음을 확인할 수 있다.

특징

fast forward와 달리 merge commit을 통해 merge 가 어디서 발생했는지 추적할 수 있으나, 프로젝트 규모가 커질수록 커밋 히스토리가 지저분해진다는 단점이 있다.

✅ Squash and Merge

Squash and Merge를 수행하면 두 브랜치를 머지할 때, 각 브랜치의 커밋이 합쳐져 아예 새로운 single commit이 생성된다.

동일한 작업을 터미널에서 수행해보자.

jinnie@Jeongs-MBP git1 % git add squash1.txt squash2.txt
jinnie@Jeongs-MBP git1 % git commit -m "add texts"
[develop a8c9159] add texts
 2 files changed, 2 insertions(+)
 create mode 100644 squash1.txt
 create mode 100644 squash2.txt 

jinnie@Jeongs-MBP git1 % git log
commit a8c91591da7ddfd2b15fc3d0db1cf0a433ae4f7e (HEAD -> develop)

    add texts

develop 브랜치에서 squash1.txt squash2.txt를 add & commit 한다. 해당 작업에 대한 새로운 커밋(a8c915)이 생성되었다.

jinnie@Jeongs-MBP git1 % git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

jinnie@Jeongs-MBP git1 % git log
commit d7082b83803e544fdefcdda6ec3be9fe2c72b5b9

    add f.txt

commit c0c48a457e179b72156775e813a48d6d288b3207

    add g.txt

main 브랜치로 체크아웃 한 후 --squash 옵션을 주어 develop브랜치를 main 브랜치에 squash and merge 해보자.

jinnie@Jeongs-MBP git1 % git merge --squash develop
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

jinnie@Jeongs-MBP git1 % git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   squash1.txt
	new file:   squash2.txt

jinnie@Jeongs-MBP git1 % git commit -m "Squash merge develop into main"    
[main d940a2d] Squash merge develop into main
 2 files changed, 2 insertions(+)
 create mode 100644 squash1.txt
 create mode 100644 squash2.txt

jinnie@Jeongs-MBP git1 % git log
commit d940a2d972ccd3c4cb7e50d756a56876f798e5c4 (HEAD -> main, origin/main)

    Squash merge develop into main

commit d7082b83803e544fdefcdda6ec3be9fe2c72b5b9

    add f.txt

commit c0c48a457e179b72156775e813a48d6d288b3207

    add g.txt

머지 후 로그를 확인해보니 squash merge에 대한 새로운 single commit이 생성되었음을 확인할 수 있다.

특징

커밋 히스토리를 깔끔하게 관리할 수 있지만, 병합된 브랜치의 히스토리를 추적할 수 없다.

Git Rebase

git rebase 기능을 사용하여 특정 브랜치의 base를 재설정 할 수 있다. rebase를 하면 불필요한 merge commit을 제거하여 하나의 깔끔한 커밋 히스토리를 유지할 수 있다는 장점이 있다.

1. main 브랜치에서 develop 브랜치를 딴다.
2. main 브랜치에서 계속 작업이 진행중이고, develop 브랜치에서도 계속 작업이 진행되고 있다.
3. develop 브랜치에서 git rebase main을 사용하여 main 브랜치 기준으로 커밋을 재정렬한다.

4. main 브랜치로 체크아웃한 후 develop 브랜치를 main 브랜치에 merge한다.

동일한 작업을 터미널에서 수행해보자.

jinnie@Jeongs-MBP git1 % git add rebase1 rebase2   
jinnie@Jeongs-MBP git1 % git commit -m "rebase1 & rebase2"
[develop c8c665e] rebase1 & rebase2
 2 files changed, 2 insertions(+)
 create mode 100644 rebase1
 create mode 100644 rebase2

jinnie@Jeongs-MBP git1 % git log
commit c8c665e059ec70fc4870138fec1ec1fe7359f33b (HEAD -> develop)

    rebase1 & rebase2

develop 브랜치에서 rebase1, rebase2를 add & commit 한다. 해당 작업에 대한 새로운 커밋(c8c665)이 생성되었다.

jinnie@Jeongs-MBP git1 % git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

jinnie@Jeongs-MBP git1 % git add rebase_on_main
jinnie@Jeongs-MBP git1 % git commit -m "add rebase_on_main"
[main 12f437d] add rebase_on_main
 1 file changed, 1 insertion(+)
 create mode 100644 rebase_on_main

jinnie@Jeongs-MBP git1 % git log
commit 12f437ddf7c879b73005b4a45f9e74050e568bfd (HEAD -> main)

    add rebase_on_main

main 브랜치에서 rebase_on_main을 add & commit 한다. 해당 작업에 대한 새로운 커밋(12f437)이 생성되었다. 다시 develop 브랜치로 체크아웃 한 뒤 main 브랜치로 rebase해보자.

jinnie@Jeongs-MBP git1 % git checkout develop
Switched to branch 'develop'

jinnie@Jeongs-MBP git1 % git rebase main
hint: use --reapply-cherry-picks to include skipped commits
hint: Disable this message with "git config advice.skippedCherryPicks false"
Successfully rebased and updated refs/heads/develop.

jinnie@Jeongs-MBP git1 % git log
commit a310df188f4e027827540c04b17ffb7f8957c122 (HEAD -> develop)

    rebase1 & rebase2

commit 12f437ddf7c879b73005b4a45f9e74050e568bfd (main)

    add rebase_on_main

develop 브랜치의 커밋 히스토리가 재정렬되면서 새로운 커밋(a310df)이 발생했다. 이제 main 브랜치로 돌아가서 develop 브랜치를 merge하면 하나의 정렬된 커밋 히스토리를 유지할 수 있다.

jinnie@Jeongs-MBP git1 % git checkout main 
Switched to branch 'main'
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

jinnie@Jeongs-MBP git1 % git merge develop
Updating 12f437d..a310df1
Fast-forward
 rebase1 | 1 +
 rebase2 | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 rebase1
 create mode 100644 rebase2

jinnie@Jeongs-MBP git1 % git log
commit a310df188f4e027827540c04b17ffb7f8957c122 (HEAD -> main, develop)

    rebase1 & rebase2

commit 12f437ddf7c879b73005b4a45f9e74050e568bfd

    add rebase_on_main

특징

git rebase는 일반적인 merge 방식과 다르게 불필요한 merge commit을 제거하여 하나의 깔끔한 커밋 히스토리를 유지할 수 있다는 장점이 있으나, 파생된 브랜치를 여러 사람이 사용하고 활발하게 커밋이 일어나는 브랜치라면 rebase가 정답은 아니다. 파생된 브랜치에서 이미 새로운 커밋이 발생하고 있는데 다른 브랜치로 base를 변경해버리면 파생브랜치의 커밋 히스토리가 변경되고, 따라서 개발자들은 자신의 커밋을 다시 반영하거나 재작업을 해야 한다. 따라서 혼자 작업하는 브랜치나 작업하는 사람이 적어 문제상황이 발생할 확률이 적은 경우에만 주의깊게 사용하는 것이 좋다.

References

Git - git-merge Documentation
Git - git-rebase Documentation
https://velog.io/@devp1023/GIT-%EB%B3%91%ED%95%A9-%EC%B6%A9%EB%8F%8C-%ED%95%B4%EA%B2%B0-3-way-merge-fast-forward
https://im-developer.tistory.com/182
https://cross-the-line.tistory.com/m/20

profile
Mobile Software Engineer

0개의 댓글