일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Basic
- html
- Express
- Merge
- Reset
- Remote
- rbenv
- relative
- Advanced
- git branch
- sourcetree
- commit
- Box Model
- workflow
- Develop
- Process
- Nodejs
- route
- React
- git checkout
- git
- crud
- css
- Teamwork
- fixed
- root
- 소스트리
- linux
- WEB
- Rebase
- Today
- Total
Codesigner
[Git] Git Advanced - Rebase overview 본문
Git에서 브랜치를 병합하는 방법은 두 가지가 있다. 첫 번째는 Merge이고 다른 하나는 Rebase이다. 이번 포스팅에서는 Rebase가 무엇인지, 어떻게 사용하는지, 좋은 점은 무엇인지, 어떤 상황에서 사용하고 어떤 상황에서 사용하지 말아야 하는지 알아보도록 하자
Rebase 기초
다음과 같은 브랜치의 모습을 가정하자
위 두 브랜치를 합치는 가장 쉬운 방법은 이전에 배웠던 merge 명령을 사용하는 것이다. 두 브랜치의 마지막 커밋(C3, C4)과 공통 조상(C2)을 사용하는 3-way merge로 다음과 같은 새로운 커밋을 만들어낸다
비슷한 결과를 만드는 다른 방식으로, C3에서 변경된 사항을 패치(Patch)로 만들고 이를 다시 C4에 적용시키는 방법이 있다. Git 에서는 이러한 방식을 Rebase라고 한다. Rebase는 한 브랜치에서의 변경 사항을 다른 브랜치에 적용시키는 merge 와는 또 다른 방법이다. 위의 예제는 다음과 같은 명령으로 Rebase 한다
C:\Users\glafu\Desktop\rebase_test>git checkout experiment
Switched to branch 'experiment'
C:\Users\glafu\Desktop\rebase_test>git rebase master
First, rewinding head to replay your work on top of it...
Applying: Add fourth line on experiment
여기서 실제로 일어나는 일을 설명하자면 일단 두 브랜치가 나뉘기 전인 공통 커밋(C2)으로 이동하고 나서 그 커밋에서부터 현재 위치하는 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 임시로 저장해 놓는다. Rebase 할 브랜치(여기서는 experiment
)가 합칠 브랜치(여기서는 master
)가 가리키는 커밋을 가리키게 하고, 아까 저장해 놓았던 변경사항들을 차례대로 적용시킨다. 아래 그림은 이러한 과정을 나타내고 있다
그리고 나서 master
브랜치를 Fast-forward 시킨다
C3'로 표시된 커밋에서의 내용은 <그림 2>의 C5 커밋에서의 내용과 같다. Merge이든 rebase든 둘 다 합치는 관점에서는 서로 다를 게 없다. 하지만 rebase 가 좀 더 깨끗한(선형적인) 커밋 히스토리를 만든다. 즉, rebase 하고 나면 모든 작업이 차례대로 수행된 것처럼 보인다
rebase는 보통 리모트 브랜치에서 커밋을 깔끔하게 적용하고 싶을 때 사용한다. 즉 로컬 브랜치에서 작업한 내용을 origin/master
로 rebase 하여 리모트의 master
브랜치를 Fast-forward 시키면 된다. 아마 이렇게 rebase 하는 모든 브랜치는 직접 작업하는 것이 아니라 그냥 참여하는 브랜치일 것이다
rebase를 하든지, merge를 하든지 결과물은 동일하고 커밋 히스토리만 다르다는 것이 중요하다. Rebase의 경우 브랜치의 변경사항을 순서대로 다른 브랜치에 적용하면서 합치고 merge의 경우는 두 브랜치의 최종 결과만을 가지고 합친다
Rebase 활용
Rebase는 단순히 브랜치를 합치는 것만 아니라 다른 용도로도 사용할 수 있다. 다음과 같이 다른 브랜치에서 또 갈라져 나온 브랜치 히스토리가 있다고 가정하자
여기서 server
브랜치는 테스트가 덜 되어서 그대로 두고 client
브랜치만 master
에 병합하려는 상황을 생각해보자. server
와는 아무 상관이 없는 client
커밋은 C8, C9이다. 이 두 커밋을 master
브랜치에 적용하기 위해서 --onto
옵션을 사용하여 아래와 같은 명령을 실행한다
git rebase --onto master server client
위 명령은 client
브랜치로 checkout 하고 server
와 client
의 공통조상 이후의 커밋의 패치를 만들어 master
에 적용한다
이제 master
브랜치로 checkout 하여 Fast-forward 시키면 다음과 같다
git checkout master
git merge client
자, 이제 server
브랜치의 작업이 완료되었다고 하자. 우리는 다시 server
브랜치로 checkout 할 필요 없이 다음 명령으로 basebranch 에 topicbranch를 rebase 할 수 있다
git rebase [basebranch] [topicbranch]
즉, 여기서는 basebranch가 master
이고 topicbranch가 server
브랜치이다. 다음 명령은 topicbranch(server)로 checkout 하고 basebranch(master)로 rebase 한다
git rebase master server
위 명령의 결과는 다음과 같다
그리고 나서 master
브랜치를 다시 Fast-forward 시킨다
git checkout master
git merge server
자, 이제 master
브랜치의 모든 변경 사항이 적용되었다. 임무 수행을 마친 브랜치는 삭제해도 된다. 브랜치를 삭제하고 난 후 커밋 히스토리는 다음과 같다
git branch -d client
git branch -d server
Rebase의 위험성
Rebase는 브랜치를 병합할 때 merge와는 달리 커밋 이력이 존재하여 눈으로 변경 내용을 좀 더 명확하게 확인할 수 있고, 선형적인 히스토리를 만들어 준다는 장점이 있다. 하지만, 단점이 존재하지 않는 것은 아니라 주의해야 한다. 그 주의사항은 아래 한 문장으로 표현할 수 있다.
이미 공개 저장소에 push 한 커밋을 rebase 하지 마라
이 것만 지키면 rebase를 하는 데 문제 될 게 없다. 하지만, 지키지 않는다면 공개 저장소를 공유하는 협업자들에게 무진장 욕을 얻어먹을 것이다...
이미 공개 저장소에 push한 커밋을 rebase 하면 어떤 결과를 초래하는지 예제를 통해 알아보도록 하자. 리모트 레포지토리를 clone 하고 일부 수정을 하면 커밋 히스토리는 다음과 같다
이제 팀원 중 누군가 리모트를 클론하여 브랜치를 파서 작업한 다음, merge 하고 서버에 push 하였다고 하자. 이를 다시 나의 로컬 레포지토리에 pull 하면 커밋 히스토리는 다음과 같게 된다
그런데 push 했던 팀원이 merge 했던 커밋을 되돌리고 다시 rebase 하려고 한다. 서버의 히스토리를 덮어 씌우려고 git push --force
명령을 사용하여 리모트에 push 하였다고 가정하면 커밋 히스토리는 다음과 같게 된다(참고로 지금 팀원의 행동이 앞에서 말한 주의사항을 지키지 않은 것이다!)
현재 C7이 참조하고 있는 커밋(C6)이 사라졌기 때문에 이미 처리한 일이라고 해도 또다시 merge 해야 한다. Rebase는 커밋의 SHA 해쉬값을 바꾸기 때문에 Git은 새로운 커밋으로 C4'를 받아들인다. 사실 C4는 이미 히스토리에 적용되어 있지만, Git은 이를 알지 못한다
git log 명령으로 히스토리를 확인해 보면 저자, 커밋 날짜, 메시지가 동일하지만 SHA 해쉬 값이 다른 커밋이 두 개가 있다(C4, C4'). 이렇게 되면 혼란스럽다. 게다가 이 히스토리를 서버에 push 하면 같은 커밋이 두 개가 존재하기 때문에 협업하는 사람들도 혼란스러워한다. C4와 C6는 포함되지 말아야 할 커밋이지만, rebase 하기 전에 이미 다른 사람들이 해당 커밋을 참조하기 때문에 이러한 사고가 발생한 것이다.
이러한 사고가 발생한 경우, 해결책이 없는것은 아니다. 여기에서 그 방법을 찾을 수 있다. 가장 좋은 방법은, 위에서 설명하였다시피 이미 공개하여 사람들이 사용하는 커밋을 rebase 하지 않는 것이다.
Rebase vs Merge
자, 지금까지 rebase가 무엇인지, merge와는 어떻게 다른지 기나긴 여정을 지나 살펴보았다. 지금쯤 한 가지 의문이 들 수 있을 것이다. 둘 중 무엇을 쓰는 게 좋은 것인가? 이 질문에 대한 답을 찾기 전에 히스토리의 의미에 대해서 잠깐 다시 생각해보자
히스토리를 보는 관점 중에 하나는 작업한 내용의 기록으로 보는 것이 있다. 작업 내용을 기록한 문서이고, 각 기록은 각각의 의미를 지니며, 변경할 수 없다. 이런 관점에서 커밋 히스토리를 변경한다는 것은 역사를 부정하는 꼴이다. 언제 무슨 작업을 했었는지 기록에 대해 거짓말을 하게 되는 것이다. 역사는 보존되어야 할 필요가 있다(부정적 관점)
히스토리를 프로젝트가 어떻게 진행되었나에 대한 이야기로도 볼 수 있다. 소프트웨어를 주의 깊게 편집 하는 방법에 매뉴얼이나 세세한 작업내용을 초벌부터 공개하고 싶지 않을 수 있다. 나중에 다른 사람에게 들려주기 좋도록 rebase나 filter-branch 같은 도구로 프로젝트의 진행 이야기를 다듬으면 더 잘 전달할 수 있을 것이다(긍정적 관점)
merge나 rebase 중 무엇이 좋냐는 질문은 다시 생각해봐도 섣불리 대답하기 어렵다. Git은 매우 강력하고 유용해서 히스토리를 잘 쌓을 수 있지만, 모든 팀과 모든 이가 처한 상황은 다르기 때문에, 이 둘을 어떻게 사용할 것인지는 각자의 상황과 각자의 판단에 달려있다
해답을 일반화 시키자면 로컬 브랜치에서 작업할 때는 히스토리를 정리하기 위해 rebase 할 수도 있지만, 리모트 등 어딘가에 push 한 커밋에 대해서는 절대 rebase 하지 말아야 한다
'Git > git' 카테고리의 다른 글
[Git] Git Advanced - Rebase -i 활용법 (0) | 2019.04.16 |
---|---|
[Git] Git Advanced - 태그(tag)의 생성, 조회, 서명 그리고 검증 및 공유 (0) | 2019.04.15 |
[Git] Git Teamwork - Workflow & Generalizations (0) | 2019.04.09 |
[Git] Git Teamwork - Remote Repository (0) | 2019.04.09 |
[Git] Git Branching - Merge conflict & Review (0) | 2019.04.09 |