지나공 : 지식을 나누는 공간

fatal: Need to specify how to reconcile divergent branches. 원인과 해결 / Fast-Forward, rebase, merge 이해하기 본문

우당탕탕 삽질기

fatal: Need to specify how to reconcile divergent branches. 원인과 해결 / Fast-Forward, rebase, merge 이해하기

해리리_ 2022. 8. 20. 16:09

 

git pull을 어떻게 할지 전략이 세가지가 있으니 선택해서 명시해달라는 내용이다. 

타 블로그 왈 git 2.27부터 추가된 기능이라고 한다.

 

기존 git pull의 문제점 : 세 가지 전략으로 나뉘게 된 배경


git pull --help에서 설명하는 내용

git pull --help에서는 아래처럼 설명한다.

In its default mode, git pull is shorthand for git fetch followed by git merge FETCH_HEAD.

 

번역하자면, 디폴트모드에서는 git pull이 git fetch, 즉 git merge FETCH_HEAD의 작업에 대한 간단명령어(속기)라는 말이다.

다시 말해 git pull의 내부 동작이 git merge FETCH_HEAD처럼 동작한다는 말이다.

 

따라서 이 디폴트모드에서는 git pull을 수행했을 때 git merge에 대한 불필요한 commit이 자동으로 생긴다. 

 

기존 git pull의 문제 상황 예시


실제로 기존의 default git pull을 할 때 어떤 문제가 생기는지, 불필요한 커밋은 어디서 생기는지 문제 상황이 정리된 그림이 있어서 가져왔다. Local Branch에 작업하고 있는 도중인데 누군가에 의해 remote에 또다른 commit이 생긴 아래 상황을 보자.

 

(1) 불필요한 커밋

여기서 별도의 옵션을 주지 않은 채로 default대로 git pull 또는 git pull origin master를 실행하게 되면 아래처럼 된다. local master랑 remote의 origin/master가 합쳐지면서 새로운 commit(불필요한)이 생기는 문제가 발생한다. 우리가 자주 보던 merge commit을 말하는 거임!

before -> after

(2) 또다른 문제

이 상태에서 내가 another-branch라는 브랜치에 checkout 한 뒤, 거기서 다시 git pull이나 git pull origin master를 하면 당연히 local master에 merge되는 게 아니라 내가 작업 중인 another-branch에 remote의 origin/master가 merge된다. 아래 오른쪽 그림처럼 된다.

before -> after. 예제그림이 없어서 만들어봤다..끄응..^^;;

 

git pull 전략들


  1. git config pull.rebase false #merge (the default strategy)
  2. git config pull.rebase true #rebase
  3. git config pull.ff only # fast-forward only

앞서 위의 세가지 전략이 있다며 이 중에 고르라는 hint를 봤었다. 이 중에 뭘 선택할지 정하려면 각각이 뭔지 알아야 한다.

 

일단 1번은 위에서 말한 내용처럼 기존의 default git pull이라서 우리가 알던 merge처럼 새로운 merge commit이 생기는 전략이다.

그럼 이제 #rebase랑 #fast-forward only는 무슨 차이일까? 이걸 알기 전에 fast-forward랑 rebase부터 알아보고 가자..!

 

fast-forward 관계 + Merge vs Rebase


fast-forward란? 

분기한 브랜치의 커밋 히스토리가 기존 브랜치의 커밋 히스토리를 포함하고 있는가에 따라 fast forward 관계가 정해진다.

 

[ Fast-Forward 관계 (O) ]

Fast-Forward인 관계(왼)에서 feature를 master에 merge하면 오른쪽처럼 기존 브랜치를 그대로 따라간다.

master에서 feature로 분기된 브런치의 커밋 히스토리는 A-B-X-Y이다. 글고 기존 브랜치인 master의 커밋 히스토리는 A-B이다. 따라서 분기된 브랜치인 feature의 히스토리는 기존 master 브랜치의 히스토리를 포함하고 있으니 이는 Fast-Forward 관계다. 이런 관계에서 master 브랜치에서 git merge [feature브랜치명]을 실행해서 병합하면 merge에 대한 new commit이 생기지 않고 HEAD의 위치만 변하게 된다. 브랜치를 그대로 따라가게 된다. 

 

[ Fast-Forward 관계 (X) ]

Fast-Forward가 아닌 관계(왼)에서 feature를 master에 merge하면 새로운 merge commit이 생긴다.

분기된 feature 브랜치의 커밋 히스토리는 A-B-X-Y인데 기존 브랜치인 master의 현재까지의 커밋 히스토리가 A-B-C이다. feature의 히스토리가 master의 히스토리를 모두 담고 있지는 않다. C가 새로 생겼다. 여기서 둘을 merge하면 새로운 merge 커밋이 생기는 것이 어쩌면 당연한 것 같다. feature 입장에서는 인지하지 못한 새 커밋이 생겼으니까 말이다...! 아무튼 merge commit을 생성하면서 병합된다.

 

git rebase, merge란? 

git rebase랑 merge 모두 어떤 브랜치의 변경 사항을 다른 브랜치로 통합하도록 설계됐다. 근데 이 통합하는 방식이 다르다.

merge

main 브랜치를 feature에 merge로 합쳐보자.

git checkout feature
git merge main
// 위 두 줄을 한 줄로 압축하면 git merge feature main 이다.

before(왼)/after(오) : merge commit이 새로 생겼다.

merge commit이 새로 생겼다. merge는 비파괴적인 방식이라 어떤 방식으로든 기존 브랜치가 변경되지 않는다. 잠재적인 위험을 방지할 수 있다.

 

rebase

main 브랜치를 feature에 rebase로 합쳐보자.

git checkout feature
git rebase main

before(왼)/after(오) : merge commit을 만들지 않는다.

merge commit을 새로 만들지 않고 원본 브랜치에서 각 커밋에 대해 새로운 커밋을 만들어서 프로젝트 기록 자체를 다시 작성한다. 이로 얻을 수 있는 이점은 프로젝트 히스토리를 더 명확하게 얻을 수 있다는 것이다.

 

  1. git merge에서 생기는 불필요한 merge commit을 제거한다.
  2. 완벽한 선형 히스토리를 만들 수 있다. git log 등의 명령으로 더 쉽게 탐색할 수 있다.

근데 2번 같은 경우 더 치명적일 수도 있다. 협업 워크 플로우에서는 history가 중요한데 이걸 임의로 변경시키기 때문에 rebase를 쓸 땐 주의가 필요하다. 나중에 아래에서 전략 선택할 때 다시 언급할 예정이다. 일단은 이게 뭔지 알고만 가자..!

 

Git Merge의 종류 네 가지

  • 1) git merge (-ff)
    • 그냥 보통의 병합으로, 융통성이 있음.
    • 현 브랜치랑 병합할 브랜치가 fast-forward 관계라면 새로 merge commit을 만들지 않고 병합함.
    • 현 브랜치랑 병합할 브랜치가 fast-forward하지 않은 관계라면 새로 merge commit을 만들고 병합함.

융통성 있어서 양쪽의 경우 모두 가능함.

  • 2) git merge --no-ff
    • 현 브랜치랑 병합할 브랜치가 fast-forward이냐 아니냐에 상관 없이 무조건 merge commit을 새로 만듦.

--no-ff

  • 3) git merge --ff-only
    • 현 브랜치랑 병합할 브랜치가 fast-forward일 경우에만 병합을 진행하고, 이때 merge commit을 만들지 않음.
    • ff 관계가 가능할 때에만 하고 그렇지 않을 때는 merge를 거절한댄다!
거절할 때 이런 에러가 난다.
* branch master -> FETCH_HEAD 593a5e1..208efa3 master -> origin/master fatal: Not possible to fast-forward, abortin
  •  

--ff-only

  • 4) git merge --squash
    • 현 브랜치랑 병합할 브랜치에 대해서 서로 다른 commit이 2-3개 있다고 가정할 때 이 2-3개의 커밋 내용을 하나의 커밋으로 합쳐서 커밋함.

squash

docs에서 정리하는 말은 아래와 같다.

docs 설명이 젤 깔끔하다...

 

 

추가로 git PR에 있는 merge 버튼들을 보면 세 가지가 있는데 각각 해석하자면 아래와 같다.

 

(1) Merge Commit -> 그냥 일반 merge. merge 커밋을 때때로 만드는 그런 merge

(2) Squash and Merge -> 위 squash 그림처럼 되고

(3) Rebase and Merge -> 는 아래처럼 합치려던 브랜치의 커밋들을 master에게 복사한다. 아래 그림처럼.

rebase and merge

 

 

#rebase와 #fast-forward only의 차이


  • git config pull.rebase true(#rebase)
  • git config pull.ff only(#fast-forward-only)

이제 위의 글들을 다 봤으면 이 둘의 차이는 금방 이해가 되는 것 같다.

 

rebase의 결과는

  • 가져올 대상 브랜치의 commit들을 병합의 목적지인 브랜치로 복사했으니까 새로운 merge commit이 생기지 않는다. 어찌보면 병합이라고 하기도 좀 그런 것 같다.
  • 아래 그림에서 rebase하기 전 모습을 보면 master 브랜치에 이미 feature가 모르는 새로운 커밋들이 있으므로 fast-forwad하지 않은 상태다. 그러모르 ff only를 주면 merge가 되지 않을 것이다.

rebase and merge

 

ff only의 결과는

  • 일단 fast-forward하지 않은 바로 위 같은 상황에서는 병합이 안된다.
  • fast-forward한 상태에서 그때서야 병합이 가능하다.
  • 위에 첨부했던 fast-forward 상태에서의 merge 그림을 다시 가져와봤다.

 

선택 & 기본 환경 세팅


그래서 셋 중에 뭘 선택해야 할지 다시한번 정리해보자.

  1. git config pull.rebase false #merge (the default strategy)
  2. git config pull.rebase true #rebase
  3. git config pull.ff only # fast-forward only

 

1은 ff가 아니면 그냥 merge commit 만들면서 병합하고, ff이면 merge commit 없이 병합한다.

2는 merge가 아니라 커밋 자체를 복사해온다.

3은 ff가 아니면 에러 내면서 병합 자체를 refuse한다. ff일 때만 merge commit 없이 병합한다.

 


내가 본 외국 블로그에서는 여전히 --ff-only를 더 추천한다고 한다. rebase해버리면 현재 브랜치가 있었다는 기록을 영구적으로 알 수 없어서이다. 근데 --ff-only는 ff관계가 아닐 때마다 refuse 나면서 날 혼란스럽게 할 것 같다.

 

내 생각으로는, 협업 프로세스에서는 '이 브랜치가 있었다' 라는 기록 정도는 남아있는 게 좋을 것 같다. 그래서 나라면 ff관계가 아닐 때에도 merge는 수행하면서 새로운 merge commit을 만들어 두는 1번(git config pull.rebase false)를 선택할 것 같다. 아니면 그냥 rebase를 하거나..!

 

뭐가 됐든 --ff-only는 좀 꺼두는 게 좋을 듯 하다. 이 경우는 fast-forward만 하겠다는 말이라서 위에서 말했듯이 안되는 경우마다 다 refuse 나니까.. 얘를 꺼두려고 한다. 아래 명령어로 끄면 된다고 한다. (난 놑북 다시 세팅한 뒤로 git 버전이 낮아서 이런 에러가 안나타나서... 적용 안했다ㅎㅎ)

git config --unset pull.ff

 

 

 

 

 

 

[참고]

https://blog.sffc.xyz/post/185195398930/why-you-should-use-git-pull-ff-only

 

Why You Should Use git pull –ff-only

Git is a simple, elegant version control system. Like many great things, if you are new to Git, it takes trial and error before you can come to appreciate its power and simplicity. Having introduced Git to many dozens of people, first as a teaching assista

blog.sffc.xyz

https://stackoverflow.com/questions/28140434/is-there-a-difference-between-git-rebase-and-git-merge-ff-only

 

Is there a difference between git rebase and git merge --ff-only

From what I read, both of them help us get a linear history. From what I experimented, rebase works all the time. But merge --ff-only works only in scenarios where it can be fast forwarded. I also

stackoverflow.com

https://otzslayer.github.io/git/2021/12/05/git-merge-fast-forward.html

 

 

Git Merge에서 Fast Forward 관계 이해하기 - Jay's Blog

Git Merge에서 항상 헷갈리는 Fast Forward 관계에 대해 정리해보았습니다. 🙌 들어가며 Git을 사용하다보면 브랜치를 분기하여 다시 병합하는 일이 빈번하게 일어납니다. 최근 Git 커밋 히스토리 관리

otzslayer.github.io

https://www.atlassian.com/ko/git/tutorials/merging-vs-rebasing

 

병합과 기준 재지정(rebase) 비교 | Atlassian Git Tutorial

관련된 git merge 명령으로 git rebase를 비교하고 잠재적인 모든 기회를 식별하여 일반 Git 워크플로우로 기준 재지정을 통합합니다.

www.atlassian.com

https://stackoverflow.com/questions/28140434/is-there-a-difference-between-git-rebase-and-git-merge-ff-only

 

Is there a difference between git rebase and git merge --ff-only

From what I read, both of them help us get a linear history. From what I experimented, rebase works all the time. But merge --ff-only works only in scenarios where it can be fast forwarded. I also

stackoverflow.com

https://stackoverflow.com/questions/62653114/how-can-i-deal-with-this-git-warning-pulling-without-specifying-how-to-reconci

 

How can I deal with this Git warning? "Pulling without specifying how to reconcile divergent branches is discouraged"

After a git pull origin master, I get the following message: warning: Pulling without specifying how to reconcile divergent branches is discouraged. You can squelch this message by running one of the

stackoverflow.com

https://minemanemo.tistory.com/46

 

[GIT] 병합(merge) 종류 별 완벽 설명

조금 더 케이스를 상세하게 다시 포스팅 해보았습니다 https://minemanemo.tistory.com/157 [Git] merge와 rebase - (1) 주요 개념 및 예시 목차 목차 🤓 개요 🥶 중요 개념: Fast-Forward 관계란? 🥸 merge와 reb..

minemanemo.tistory.com

https://velog.io/@injoon2019/Git-Merge-%EC%A2%85%EB%A5%98

 

[Git] Merge 종류

출처: https://im-developer.tistory.com/182GitHub은 아래 3가지의 Merge Button이 가능해요.Merge CommitSquash MergingRebase Merging저장소에 맞게 허용 가능한 병합 방법을 Settings

velog.io

https://velog.io/@emrhssla/%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-merge-no-ff-squash-rebase-%EA%B7%B8%EB%A6%AC%EA%B3%A0-pull-requestPR

 

그림으로 이해하는 merge --no-ff, squash, rebase 그리고 pull request(PR)

pull request(PR)을 할 때 merge 옵션이 있다여기서 옵션들은 merge의 옵션들과 같다Create a merge commit -> git merge <branch> --no-ffSquash and merge -> git merge <branc

velog.io

 

728x90
Comments