Rebase
한 브랜치에 다른 브랜치의 내용을 적용시키려면 흔히들 쓰는, 그리고 내가 지금까지 쓰던 명령어는 Merge 이다.
Master 브랜치에 내 작업내용 PR 을 적용시키려면, Merge 명령어를 썼다.
그리고 Master 의 변경내용을 내 작업브랜치에 적용시킬때는 pull ( 원격 저장소의 내용을 가져오는 Fetch + Merge) 를 썼다.
오늘은 코드를 깔끔하게 병합할 때 쓰는 Rebase 라는 명령을 알아보도록 한다.
내가 깃을 이용한 협업과 gitflow라는 개념을 처음 알게 되었던 글은 우아한 형제들의 블로그였는데,
https://techblog.woowahan.com/2553/
우린 Git-flow를 사용하고 있어요 | 우아한형제들 기술블로그
{{item.name}} 안녕하세요. 우아한형제들 배민프론트개발팀에서 안드로이드 앱 개발을 하고 있는 나동호입니다. 오늘은 저희 안드로이드 파트에서 사용하고 있는 Git 브랜치 전략을 소개하려고 합
techblog.woowahan.com
당시에는 그 글에서의 Rebase 가 어려워서 넘어갔던 기억이 든다.
Rebase, Squash 등을 이용해서 깃의 로그를 깔끔하게 하는 방법을 알기 위해 Rebase 라는 것에 먼저 공부를 해본다.
Rebase
git 에는 HEAD 라는 포인터가 있다.
이 포인터는 현재 작업중인 커밋을 가리킨다.
다르게 말하면, 내가 작업할 커밋에 HEAD 를 놓게 되는 것이다.
우리는 어떠한 작업을 할때, 항상 HEAD 가 가르키는 커밋에서 작업을 이어간다.
rebase 명령은 이 HEAD 를 re - base 재 배치하는 명령이다.
이를 기억하면서 Rebase 로 병합하는 과정을 거쳐보자.
그리고 Rebase 를 하지 않고, 그냥 Merge 도 해보면서 차이를 보자.
1. 일반적인 Merge
Master 브랜치에서 C1이 커밋된 시점에 내가 Branch1 이라는 이름으로 브랜치를 따서 C3 이라는 작업을 커밋했다.
그리고 내가 작업하는 사이 C2 의 커밋이 마스터에 적용되었다. (Conflict 는 없다 치자.)
이후 내 브랜치도 Master 에 적용시키고 싶을때 아래의 명령어를 사용한다.
// 현재 내 작업 브랜치에서 작업하고 있을테니 checkout
$ git checkout master
$ git merge Branch1
브랜치에서 작업한 C3 의 내용을 마스터가 이제 보고 있게 되었고, 변경 사항이 잘 적용 되었다.
하지만
- 지금은 브랜치가 한개지만, 브랜치가 여러개라면?
- 지금은 브랜치에서 작업한 내용이 한개지만, 수십 커밋한 브랜치를 병합하면?
길~고 복잡한 깃 로그를 가지게 될 것이다.
이에 도움을 줄 수 있는 방법이 Rebase 이다.
2. Rebase
다시 아까의 처음 상태로 돌아가보자.
이번에는 작업 브랜치가 master 로 옮기지 말고 branch1 인 상태에서 아래 명령어를 실행한다.
$ git rebase master
결과를 그림으로 먼저 보자면 아래와 같다.
이 명령어를 친걸 직접적으로 해석해보면 다음과 같을 것이다.
"master 브랜치를 branch1 브랜치에 rebase 해라!"
내부적으로는 아래와 같은 일이 일어난다.
- 두 브랜치가 나뉘기 전 공통 브랜치 C1 로 이동.
- 공통 브랜치 C1 에서 현재 브랜치의 C3 까지의 Diff 를 임시 저장.
- Branch1를 master 가 있는 곳을 가리키게 이동.
- 아까 임시 저장한 C1-C3 의 Diff 커밋들을 적용.
곰곰히 생각해보면 위의 그림이 결과로 나온다.
이후 master 브랜치로 이동해서 master 를 merge 한다.
$ git checkout master
$ git merge branch1
Branch1 을 Master 브랜치로 병합할때, Branch1 의 이력은 rebase 로 인해 Master 의 이력을 그대로 가지고 있다.
따라서, Master 브랜치로 병합할때, Master 가 가리키는 방향만 그대로 옮기면 된다.
이런 병합을 Fast-Forward 병합 이라고 한다.
작업내용을 병합한 결과가 위의 Merge 만 사용한 것과는 다르게 한줄로 간단하다.
또 merge 커밋 하나도 줄었다.
이런 식으로 간단한 깃 로그를 만들 수 있다.
3. Rebase 시 충돌 - continue
이 상황에서 C2, C3 가 같은 부분을 수정하여 Conflict 가 발생할 수 있다.
그때는, merge 시 충돌처럼 충돌부분을 적당히 수정하고, 수정부분을 스테이징하는 것 까진 똑같지만
commit 으로 적용하는 것이 아니다.
$ git add .
$ git rebase --continue
4. Rebase Checkout 하지 않고 간단히 쓰기.
우리는 이 상황에서 아래와 같이 rebase 했다.
- branch1 로 Checkout
- master rebase
- master 로 Checkout
- branch1 merge
$ git checkout branch1
$ git rebase master
$ git checkout master
$ git merge branch1
굳이 checkout 을 하지 않고 한줄로도 가능하다.
$ git rebase <basebranch> <topicbranch>
토픽(server) 브랜치를 Checkout 하고 베이스(master) 브랜치에 Rebase 한다.
쉽게 말해서, 로그상으로 생각해봤을때, 적혀진 순서대로
<basebranch>-<topicbranch> 가 된다.
따라서, master 브랜치에서
$ git rebase branch1 master
를 하게 되면,
가 된다. 대신, 시간 순서대로 커밋이 정렬되지 못한다.
반대로, branch1 브랜치에서
$ git branch master branch1
을 하게 되면,
가 된다. master 브랜치로 checkout 후 fast-forward merge 를 해야 할 것이다.
5. Rebase 시 주의점.
이미 공개 저장소에 Push 한 커밋을 Rebase 하지 마라
Rebase 명령은 사실, 변경사항은 같지만 다른 커밋을 만든다.
변경사항을 복사해서 새로운 커밋을 만든다라고도 할 수 있겠다.
따라서, 공개 저장소에 이미 올라간 커밋을 가져와서 Rebase 하면, 새로운 커밋들로 이루어진 로그를 만들게 된다.
이를 다시 공개저장소에 push 하려면, force 명령어를 써서 하게 될 텐데, 이렇게 올려버리면 문제가 생기는 것이다.
왜냐면, 이미 공개 저장소에 올라간 커밋들을 팀원들은 쓰고 있을텐데, 새로운 커밋으로 구성된 브랜치를 내가 강제로 올려버리면 다른 팀원들은 merge 할때 문제가 생기기 때문이다.