-
Need to specify how to reconcile divergent branches를 아라보자Git 2024. 6. 28. 21:20
기존 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된다. 아래 오른쪽 그림처럼 된다.
git pull 전략들
- git config pull.rebase false #merge (the default strategy)
- git config pull.rebase true #rebase
- 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 mainbefore(위)/after(아래) : merge commit을 만들지 않는다.
merge commit을 새로 만들지 않고 원본 브랜치에서 각 커밋에 대해 새로운 커밋을 만들어서 프로젝트 기록 자체를 다시 작성한다. 이로 얻을 수 있는 이점은 프로젝트 히스토리를 더 명확하게 얻을 수 있다는 것이다.
- git merge에서 생기는 불필요한 merge commit을 제거한다.
- 완벽한 선형 히스토리를 만들 수 있다. 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 그림을 다시 가져와봤다.
728x90'Git' 카테고리의 다른 글
git 다른 브랜치 커밋 가져오기 (0) 2024.06.14 checkout시 손실 (0) 2023.07.26 fork가 날아가는 에러 (0) 2023.02.20 Organization push불가 (0) 2023.02.20 git Push 취소 (0) 2023.01.20