작업 중인 브랜치에 다른 히스토리를 반영해야 할 때: Merge? Rebase?
kindof
·2022. 10. 31. 21:34
1. 상황
develop(main) 브랜치 하위에 특정 기능을 구현하기 위한 feature/A 브랜치를 생성했습니다.
feature/A 브랜치에서 몇 차례 Commit을 진행하고 Push 해둔 상태에서 아직 develop 브랜치에 반영하지는 않았습니다.
한편, 제가 feature/A에서 작업을 하는동안 다른 팀원이 develop 브랜치 하위에 feature/B 브랜치를 생성해서 수정사항을 반영한 뒤, develop 브랜치에서 Merge를 했습니다.
이 때, 저는 다른 팀원이 작업한 develop 변경 사항을 포함하는 동시에 제가 변경한 부분도 반영해서 앞으로의 작업을 이어나가고 싶습니다.
* 아래 그림을 두고 설명하자면 feature/a 브랜치의 10~40 부분을 그대로 유지하면서 main의 변경사항 중 50~80 부분은 가져오고 싶은 상황입니다.
2. 테스트 환경 만들기
먼저 위와 같은 문제 상황을 인위적으로 구축하기 위한 명령어들을 수행하겠습니다. 실습을 위한 원격 저장소는 임의로 미리 생성해두시면 됩니다.
[1] main
main > vi A
main > git add .
main > git commit -m "create: file A"
main > git push
=============================================
# main 내 파일 A 내용
main > cat A
1
2
3
4
[2-1] feature/a 브랜치
다음으로는 feature/a 브랜치를 생성하고, 거기서 두 번의 Commit 기록을 생성하겠습니다.
main > git checkout -b feature/a
feature/a > vi A
feature/a > git add .
feature/a > git commit -m "add: some line"
feature/a > git push --set-upstream origin feature/a
feature/a > vi A
feature/a > git commit -am "update: file A"
feature/a > git push
=============================================
# feature/a 브랜치 내 파일 A 내용
feature/a > cat A
10
20
30
40
[2-2] feature/b 브랜치 작업과 main Merge
이러한 작업을 하고 있는 도중에 다른 팀원은 feature/b 라는 브랜치를 만들어서 작업을 한 뒤, main에 Merge 했습니다.
main > git checkout -b feature/b
feature/b > vi A
feature/b > git add .
feature/b > git commit -m "update: file A"
feature/b > git push --set-upstream origin feature/b
main > git merge feature/b
main > git push
======================================================
# Merge 이후 main과 feature/b 내 file A 내용
1
2
3
4
50
60
70
80
실제로 main에 merge하는 과정은 feature/b 브랜치에서 push 이후 Pull Request를 올리고 다른 팀원들의 동의 하에 이루어지겠지만, 여기서는 편의를 위해 그냥 merge를 수행했다고 가정하겠습니다.
이제 서두에 말했던 것처럼, 1~4번째 라인의 내용들(feature/a: 10,20,30,40 VS main: 1,2,3,4)이 서로 충돌되어서 이 부분은 feature/a 내용으로 가져가고 / 5~8번째 라인은 main의 내용을 그대로 가져오고 싶습니다.
3. rebase를 이용한다?
이전에 작성한 포스팅에서 rebase에 대해 다뤘습니다. rebase는 아래 설명처럼 topic 브랜치의 기점(base)를 새로운 지점(master)로 이동시키는데요.
그렇다면 위에서 제기한 문제를 해결하기 위해서 merge 이후의 main에 대해 rebase를 수행하고, 거기서 충돌이 발생하는 지점(Line1~4)만 해결해주면 될 것 같습니다.
그래서 아래와 rebase 작업을 진행합니다.
중간에 충돌을 해결하기 위해 파일 내용은 아래와 같이 수정합니다.
이제 feature/a 브랜치에서 파일의 내용을 보면 우리가 원했던 결과가 보입니다.
하지만, 위와 같이 rebase 하는 방식은 좋은 선택지가 아닙니다.
우리에겐 이전에 feature/a 브랜치에서 remote 저장소에 push 했던 기록이 있습니다. 그러면 현재 remote인 origin/feature/a는 아래 그림에 머물과 같은 상태에 머물러 있게 되고, 로컬에 있는 feature/a 브랜치만 rebase 됩니다.
따라서 아래와 같이 로컬에서 rebase한 작업물을 remote에 push하려고 하면 아래와 같은 에러가 발생하게 되고, 이를 해결하기 위해 git push --force 명령어를 사용하는 등의 불상사에 이르게 됩니다. 물론 --force 명령어를 사용하면 문제 상황을 모면할 수'는' 있습니다.
그런데 왜 git push --force를 사용하는 게 문제일까요?
[1] 첫번째 이유는 rebase가 개념적으로 커밋들을 복제해서 새로운 노드를 만들고 이를 새로운 base에 붙이는 과정이라는 데 있습니다.
즉, 복제된 커밋은 내용은 같지만 새로운 노드이므로 기존 노드와 해시값이 다르게 되고, 그 결과 저장소에는 해시 값만 다를 뿐 변경점과 커밋 메시지까지 똑같은 커밋 노드들 두 줄이 공존하게 됩니다.
애초에 rebase를 사용하는 큰 이유인 히스토리 관리가 Merge보다 더 복잡해지는 것이죠.
[2] 둘째로, --force 명령어는 현재 local 브랜치 내용으로 remote를 강제 갱신해버립니다.
해당 브랜치를 가지고 작업을 하는 사람이 저밖에 없다면 문제가 없겠지만, 다른 사람이 작업을 해서 push를 한 시점 이후에 내가 --force로 덮어버린다면 협업에서 굉장히 곤란한 상황이 생기게 되죠.
물론 --force-with-lease 옵션을 주게 되면 해당 브랜치에 대해 다른 사람의 Commit 이력을 추적해서 Warning을 날려 주지만, 위의 [1] 문제를 해결하지 못할 뿐더러 또 다시 다른 사람의 commit 내용을 보고 수정해야 하는 문제로 돌아오게 됩니다.
여기까지 이야기를 종합한 결론은 아래와 같습니다.
"이미 remote에 push 이력이 있는 브랜치에 대해서는 rebase를 사용하면 안 된다."
4. Merge 이용하기
merge 명령어는 여러 히스토리를 Join 하기 위해 사용하는 개념입니다.
따라서 위에서 우리가 원하는 상황을 만들기 위해서는 main → feature/a 방향으로 merge 하는 것이 방법이 될 수 있습니다.
아래와 같이 merge-test 라는 브랜치를 만들고, 위의 feature/a와 같은 작업을 똑같이 진행했습니다. 그리고 merge 명령어를 실행하고, 충돌을 해결한 뒤 커밋을 합니다.
merge는 애초에 새로운 커밋이 하나 추가되기 때문에 rebase보다 히스토리 관리 면에서 단점이라고 알고 있지만, 위에서 설명한 것처럼 이미 remote에 올라간 브랜치에 대해서는 오히려 Merge 방식이 rebase보다 단순한 방법이 됩니다.
5. 정리
이번 포스팅에서는 제가 실제로 맞닥뜨린 상황을 해결하기 위해 git rebase, git merge 방법을 비교해서 사용해보았고, 각 방법의 장점이나 단점 그리고 특징에 대해 정리해볼 수 있었습니다.
감사합니다.
5. Reference
https://eocoding.tistory.com/m/108
'Git' 카테고리의 다른 글
Github PR(Pull Request) 생성 전 생각해보면 좋을 것들 (1) | 2024.01.14 |
---|---|
[Git] Rebase는 왜 쓰나 (0) | 2022.04.13 |