Pro git - 3.6
Updated:
Pro git - Rebase 하기
1. Rebase 의 기초
- Git에서 한 브랜치에서 다른 브랜치로 합치는 방법으로는 두 가지가 있다.
- 하나는 Merge 이고 다른 하나는 Rebase 다.
-
이 두 브랜치를 합치는 가장 쉬운 방법은 앞에서 살펴본 대로
merge명령을 사용하는 것이다. -
두 브랜치의 마지막 커밋 두 개(
C3,C4)와 공통 조상(C2)을 사용하는 3-way Merge로 새로운 커밋을 만들어 낸다.
- 비슷한 결과를 만드는 다른 방식으로,
C3에서 변경된 사항을 Patch로 만들고 이를 다시C4에 적용시키는 방법이 있다. - Git에서는 이런 방식을 Rebase 라고 한다.
rebase명령으로 한 브랜치에서 변경된 사항을 다른 브랜치에 적용할 수 있다.
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
- 두 브랜치가 나뉘기 전인 공통 커밋으로 이동하고 나서 그 커밋부터 지금 Checkout 한 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 어딘가에 임시로 저장해 놓는다.
- 그리고 나서
master브랜치를 Fast-forward 시킨다.
$ git checkout master
$ git merge experiment
C4'로 표시된 커밋에서의 내용은 Merge 예제에서 살펴본C5커밋에서의 내용과 같을 것이다.-
Merge 이든 Rebase 든 둘 다 합치는 관점에서는 서로 다를 게 없다.
- 하지만, Rebase가 좀 더 깨끗한 히스토리를 만든다. Rebase 한 브랜치의 Log를 살펴보면 히스토리가 선형이다.
- 일을 병렬로 동시에 진행해도 Rebase 하고 나면 모든 작업이 차례대로 수행된 것처럼 보인다.
- Rebase는 보통 리모트 브랜치에 커밋을 깔끔하게 적용하고 싶을 때 사용한다.
- 아마 이렇게 Rebase 하는 리모트 브랜치는 직접 관리하는 것이 아니라 그냥 참여하는 브랜치일 것이다.
- 메인 프로젝트에 Patch를 보낼 준비가 되면 하는 것이 Rebase 니까 브랜치에서 하던 일을 완전히 마치고
origin/master로 Rebase 한다. - 이렇게 Rebase 하고 나면 프로젝트 관리자는 어떠한 통합작업도 필요 없다.
- 그냥 master 브랜치를 Fast-forward 시키면 된다.
- Rebase를 하든지, Merge를 하든지 최종 결과물은 같고 커밋 히스토리만 다르다는 것이 중요하다.
- Rebase 의 경우는 브랜치의 변경사항을 순서대로 다른 브랜치에 적용하면서 합치고 Merge 의 경우는 두 브랜치의 최종결과만을 가지고 합친다.
2. Rebase 활용
- Rebase는 단순히 브랜치를 합치는 것만 아니라 다른 용도로도 사용할 수 있다.
- 다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치 같은 히스토리가 있다고 하자.
server브랜치를 만들어서 서버 기능을 추가하고 그 브랜치에서 다시client브랜치를 만들어 클라이언트 기능을 추가한다.- 마지막으로
server브랜치로 돌아가서 몇 가지 기능을 더 추가한다.
- 이때 테스트가 덜 된
server브랜치는 그대로 두고client브랜치만master로 합치려는 상황을 생각해보자. server와는 아무 관련이 없는client커밋은C8,C9이다.- 이 두 커밋을
master브랜치에 적용하기 위해서--onto옵션을 사용하여 아래와 같은 명령을 실행한다:
$ git rebase --onto master server client
- 이 명령은
master브랜치부터server브랜치와client브랜치의 공통 조상까지의 커밋을client브랜치에서 없애고 싶을 때 사용한다. client브랜치에서만 변경된 패치를 만들어master브랜치에서client브랜치를 기반으로 새로 만들어 적용한다.
- 이제 master 브랜치로 돌아가서 Fast-forward 시킬 수 있다.
$ git checkout master
$ git merge client
server브랜치의 일이 다 끝나면git rebase <basebranch> <topicbranch>라는 명령으로 Checkout 하지 않고 바로server브랜치를master브랜치로 Rebase 할 수 있다.- 이 명령은 토픽(server) 브랜치를 Checkout 하고 베이스(master) 브랜치에 Rebase 한다.
$ git rebase master server
- server 브랜치의 수정사항을 master 브랜치에 적용했다.
$ git checkout master
$ git merge server
- 모든 것이
master브랜치에 통합됐기 때문에 더 필요하지 않다면client나server브랜치는 삭제해도 된다. - 브랜치를 삭제해도 커밋 히스토리는 최종 커밋 히스토리에 같이 여전히 남아 있다.
$ git branch -d client
$ git branch -d server
3. Rebase의 위험성
- 이미 공개 저장소에 Push 한 커밋을 Rebase 하지 마라
- Rebase는 기존의 커밋을 그대로 사용하는 것이 아니라 내용은 같지만 다른 커밋을 새로 만든다.
- 새 커밋을 서버에 Push 하고 동료 중 누군가가 그 커밋을 Pull 해서 작업을 한다고 하자.
- 그런데 그 커밋을
git rebase로 바꿔서 Push 해버리면 동료가 다시 Push 했을 때 동료는 다시 Merge 해야 한다. - 그리고 동료가 다시 Merge 한 내용을 Pull 하면 내 코드는 정말 엉망이 된다.
3.1 예제
- 중앙 저장소에서 Clone 하고 일부 수정을 하면 커밋 히스토리는 아래와 같아 진다.
- 이제 팀원 중 누군가 커밋, Merge 하고 나서 서버에 Push 한다. 이 리모트 브랜치를 Fetch, Merge 하면 히스토리는 아래와 같이 된다.
- 그런데 Push 했던 팀원은 Merge 한 일을 되돌리고 다시 Rebase 한다.
- 서버의 히스토리를 새로 덮어씌우려면
git push --force명령을 사용해야 한다. 이후에 저장소에서 Fetch 하고 나면 아래 그림과 같은 상태가 된다.
git pull로 서버의 내용을 가져와서 Merge 하면 같은 내용의 수정사항을 포함한 Merge 커밋이 아래와 같이 만들어진다.
git log로 히스토리를 확인해보면 저자, 커밋 날짜, 메시지가 같은 커밋이 두 개 있다(C4, C4’).- 이 히스토리를 서버에 Push 하면 같은 커밋이 두 개 있기 때문에 다른 사람들도 혼란스러워한다.
C4와C6는 포함되지 말았어야 할 커밋이다. 애초에 서버로 데이터를 보내기 전에 Rebase로 커밋을 정리했어야 했다.
4. Rebase한 것을 다시 Rebase 하기
- 만약 이런 상황에 빠질 때 유용한 Git 기능이 하나 있다. 어떤 팀원이 강제로 내가 한일을 덮어썼다고 하자. 그러면 내가 했던 일이 무엇이고 덮어쓴 내용이 무엇인지 알아내야 한다.
- 커밋 SHA 체크섬 외에도 Git은 커밋에 Patch 할 내용으로 SHA-1 체크섬을 한번 더 구한다. 이 값은 “patch-id” 라고 한다.
- 덮어쓴 커밋을 받아서 그 커밋을 기준으로 Rebase 할 때 Git은 원래 누가 작성한 코드인지 잘 찾아 낸다.
- 그래서 Patch가 원래대로 잘 적용된다.
4.1 예제
- 한 팀원이 다른 팀원이 의존하는 커밋을 없애고 Rebase한 커밋을 다시 Push한 상황이라고 가정.
- 이때, Merge 하는 대신
git rebase teamone/master명령을 실행하면 Git은 아래와 같은 작업을 한다.
- 현재 브랜치에만 포함된 커밋을 찾는다. (C2, C3, C4, C6, C7)
- Merge 커밋을 가려낸다. (C2, C3, C4)
- 이 중 덮어쓰지 않은 커밋들만 골라낸다. (C2, C3. C4는 C4’와 동일한 Patch다)
- 남은 커밋들만 다시
teamone/master바탕으로 커밋을 쌓는다.
- 그러면 같은 Merger를 다시 한다. 같은 결과 대신 제대로 정리된 강제로 덮어쓴 브랜치에 Rebase하기 같은 결과를 얻을 수 있다.
- 동료가 생성했던 C4와 C4’ 커밋 내용이 완전히 같을 때만 이렇게 동작된다.
- 커밋 내용이 아예 다르거나 비슷하다면 커밋이 두 개 생긴다. (같은 내용이 두 번 커밋될 수 있기 때문에 깔끔하지 않다.)
-
git pull명령을 실행할 때 옵션을 붙여서git pull --rebase로 Rebase 할 수도 있다. - 물론
git fetch와git rebase teamone/master이 두 명령을 직접 순서대로 실행해도 된다. git pull명령을 실행할 때 기본적으로--rebase옵션이 적용되도록pull.rebase설정을 추가할 수 있다.git config --global pull.rebase true명령으로 추가한다.- Push 하기 전에 정리하려고 Rebase 하는 것은 괜찮다.
- 또 절대 공개하지 않고 혼자 Rebase 하는 경우도 괜찮다.
- 하지만, 이미 공개하여 사람들이 사용하는 커밋을 Rebase 하면 틀림없이 문제가 생긴다.
5. Rebase vs. Merge
- 히스토리를 보는 관점 중에 하나는 작업한 내용의 기록으로 보는 것이 있다.
- 작업 내용을 기록한 문서이고, 각 기록은 각각 의미를 가지며, 변경할 수 없다.
- 이런 관점에서 커밋 히스토리를 변경한다는 것은 역사를 부정하는 꼴이 된다.
- 역사는 후세를 위해 기록하고 보존해야 한다.
- 히스토리를 프로젝트가 어떻게 진행되었나에 대한 이야기로도 볼 수 있다.
- 소프트웨어를 주의 깊게 편집하는 방법에 메뉴얼이나 세세한 작업내용을 초벌부터 공개하고 싶지 않을 수 있다.
- 나중에 다른 사람에게 들려주기 좋도록 Rebase 나 filter-branch 같은 도구로 프로젝트의 진행 이야기를 다듬으면 좋다.
- 일반적인 해답을 굳이 드리자면 로컬 브랜치에서 작업할 때는 히스토리를 정리하기 위해서 Rebase 할 수도 있지만, 리모트 등 어딘가에 Push로 내보낸 커밋에 대해서는 절대 Rebase 하지 말아야 한다.