-
Squash Merge 후 Rebase시 발생하는 문제 해결 과정😂카테고리 없음 2022. 8. 22. 23:52
0. intro
이번에 속닥속닥은
feature
-dev
간 pr 병합 시squash merge
방식을 채택했습니다.이를 통해 dev 커밋기록을 깔끔하게 관리할 수 있었습니다!👍
1. 문제상황
1) main에 squash merge
다음은 저희가
main
에squash merge
를 했던 과정입니다. 기존 main 커밋들이 날아간 관계로 간단히 도식화해 보여드리겠습니다.- main에 hotfix 커밋을 추가 dev에서 기능(d1, d2) 구현
- dev 커밋들(d1, d2)을 main으로 squash merge
- dev에서 이후 기능들(d3, d4) 구현
- dev 커밋들(d3, d4)을 main으로 squash merge 불가!
문제는 4번,
main
-dev
간 병합 시 발생했습니다.- (2번) 기존의 dev 변경사항(1-2)을
main
으로squash merge
한 상황에서 - (4번) 이후 변경사항(3-4)을
main
에 추가 merge 시 문제가 발생합니다.(정확히는, dev를 main으로 rebase 할 수 없었습니다.)
자동으로 main의
squash merge
된 커밋(1-2) 이후에 d3, d4 변경사항을 새 커밋으로 붙여주리라 예상했는데 그러지 않았습니다.깃은 “3-4 변경사항”이 “1-2 squash 커밋” 이후 발생했음을 인지하지 못했던 것이죠.
관련 설명 : https://stackoverflow.com/questions/9920182/rebasing-after-squash-merge
소스트리로 보니 다음과 같은 상태였습니다.
보시다시피, Dev(#3)은 dev pr1(d1), dev pr2(d2)가 squash된 상태이지만 깃에서는 분기가 나눠져 있습니다…👀2) dev에 hotfix 미반영
뭔가 이상한 점이 하나 더 있지 않나요? 위 충돌에는
hotfix
커밋이 더 얽혀있습니다.즉, dev에 main의 hotfix가 반영되지 않았던 상태였습니다.🙀
깃에서 dev와 main의 공통조상이 d2가 아닌 Initial commit인 이유는
첫번째로 초반 hotfix 병합 때문이고 (당연히 hotfix 병합으로 중간에 새 커밋이 생겼다면, 공통조상이
dev pr2
가 될 수 없습니다.)두번째로 squash merge 때문일 것입니다. (squash merge된 커밋은 최종적으로 새로운 커밋입니다. 공통조상이 main에 없는
dev pr2
가 될 수는 없습니다.)두 문제로 인해 main 브랜치와 dev 브랜치는 만나지 못할 강을 건넜습니다…
2. 충돌 해결방법
저희는 안전한 방식으로 충돌을 해결하는 방법들을 연구했고, 다음 3가지 방법을 생각해냈습니다.
방법 1 : 연쇄
revert
가장 안전한 방법(충돌이 가장 적음)
revert는 커밋의 내용을 되돌리는 커밋을 새로 만듭니다.
- 기존
main
의hotfix
/squash merged
커밋들을 역순으로revert
- 최종적으로
dev
초기 상태가 되도록 세팅 - main 브랜치에 dev 커밋들을 가져온다.
- 첫 커밋에 대해 개행 관련 conflict가 발생할 수 있다. 당황하지말고 들어오는(dev) 커밋 상태로 통일해 세팅한다.
- rebase를 통해 dev 변경된 모든 커밋들을 가져오기
- 마지막으로 hotfix 수정사항을 main에 다시 rebase/cherry pick으로 반영한다
장점
- main 브랜치에서 일어난 일들을 커밋 기록을 통해 한눈에 파악할 수 있다.
- hotfix 커밋을 제외하면, 가장 적은 conflict를 해결하게 된다.
- 개행 관련 conflict 이슈가 아니라면 conflict 없이 해결 가능하다.
- force push 없이 문제 해결이 가능하다.(기존 시맨틱 버저닝을 유지할 수 있다.)
단점
- main에 revert 기록이 남는다.
- main에 반영했던 hotfix 커밋들을 다시 반영해야 한다.
방법 2 :
cherry pick
main 브랜치를 비교적 안전하고 깔끔하게 관리하는 방법
- 기존 main에서
squash merge
커밋의 마지막 반영사항을 확인한다. - 해당 반영사항 후 dev 첫 커밋 이후 모두
cherry pick
/rebase -i
으로 가져온다- 연쇄 conflict가 발생할 수 있다.
- 커밋 별 conflict를 모두 해결해야 한다!
장점
- main 브랜치에 revert 커밋이 안 남는다.
- conflict만 해결하면 main 브랜치를 우리가 원했던 형태(hotfix-release1-release2)로 관리할 수 있다.
- force push 없이 문제 해결이 가능하다.(기존 시맨틱 버저닝을 유지할 수 있다.)
단점
- 연쇄 conflict를 해결해야 하며 중간에 실수가 생길 수 있다.
- dev의 수정된 커밋 양이 많을 시, cherry pick을 쓸 때 시간이 오래 걸린다.
- 중간에 dev 브랜치가 rebase가 아닌 merge로 관리되었을 시 관리가 더 까다로워진다.
방법 3 :
force-push
main 브랜치를 간단하고 가장 깔끔하게 관리할 수 있지만 다소 위험한 방법
- dev에 기존 main의 hotfix 관련 commit을 cherry pick으로 반영한다
- 관련 conflict를 모두 해결한다.
- 해당 dev를 main에
force push
한다.
장점
- 가장 간편하다. main 커밋 기록도 가장 깔끔해진다
단점
- force push를 해야한다… 위험하다.
- 기존 main 커밋 기록들이 날아가게 된다.
3. 결론
1)
force push
를 통해 해결했다저희는 위 3가지 방법 중 3번 방법을 변형해 진행했습니다. 여러 우역곡절이 있었기 때문에 커밋 기록은 깔끔하지 않지만, 손실된 코드 없이 배포를 완료했습니다.
1번 방법은 최종적으로 생각해내는 데에 시간이 걸렸고, 개행 관련 conflict 처리에 시간이 걸렸습니다.
2번 방법을 시도했지만 main의 1.1 release 이후 dev에 커밋된 일부 feature의 경우 squash merge가 아닌 일반 merge로 통합되었기 때문에 안전한 체리픽이 어려웠습니다.(체리픽 할 커밋 수가 70여개에 달했습니다.)
따라서 배포를 앞둔 상황에서 force push를 하게 되었습니다.
앞으로의 충돌 상황에선 force push를 피할 수 있는 1, 2번 방법들을 고려해볼 수 있을 것 같습니다.
2) 앞으로 지켜야 할 팀 컨벤션
dev
→main
pr 시squash merge
를 하지 말자!
(이미feature
→dev
에서squash merge
로 커밋들이 관리되고 있다.)main에 hotfix를 반영했다면, 기존 dev에도 hotfix를 반영해야 한다.
(즉, main과 dev의 공통조상이 hotfix가 되도록 설정해야 한다.)
아니면 main을 dev로 force-push 하거나 dev를 삭제 후 재생성해야 한다.
(이때 기존 pr들의 merge 타겟을 dev로 재조정 해야 한다.)되도록 main에 force push를 하지 말자!
4. 참고자료
squash merge
관련Rebasing after squash merge?
[Git] Squash and Merge 후 Rebase를 할 때 발생하는 문제force push
관련Why is it dangerous to do a force-push against a remote repository?
revert
관련git revert 사용법: 이미 커밋한 내용을 되돌리는 방법
Learn Git Branchingcreated by hunch