TIL

git branch 잘 이용하기 | 2. 깔끔한 커밋 관리를 위한 rebase

차가운에스프레소 2023. 6. 14. 19:06

1. 개요

-  git 작업을 하면서, rebase를 안써본 것은 아니다.

- "PR시 merge와는 다르게 브랜치 내역이 남지 않아, 커밋 내역을 깔끔하게 관리할 수 있다."라는 장점이 있는 것만으로 "느낌"만 가지고 있었다.

- 그러다 보니, 협업하면서 소위 "어버버" 대는 경우가 생겨, 이번 기회에 merge, rebase를 좀 비교해보았다.

 

2. branch를 병합시키는 기본, merge

- merge는 말 그대로 branch를 병합시키는 것이다.

- merge는 두 가지 종류가 있는데, 다음 예시 상황을 기준으로 설명해보겠다.

> 기본: main 브랜치, main의 커밋1에서 checkout한 dev 브랜치가 있다. dev -> main으로 병합시킨다.

> 상황1: main 브랜치에서 별다른 commit을 추가로 하지 않았고, dev 브랜치에서만 커밋2을 추가해 커밋을 한 상태다.

> 상황2: main 브랜치에서 커밋3을 추가해 커밋을 했고, dev에서는 커밋2를 추가해 커밋을 했다.

1. fast-forward merge
- 상황1에서 발생하는 merge이다.
- base branch(main)의 기점이 topic branch(dev)가 checkout한 기점과 같을 때 발생하는 merge이다.
- 별다른 merge 커밋이 발생하지 않는다.  
2. 3 way merge
- 상황2에서 발생하는 merge다.
- base branch의 기점이 topic branch가 checkout 한 기점과 다를 때 발생하는 merge다.
- merge 커밋이 생성된다.
- 경우에 따라, 충돌이 발생한다.

- merge는 가장 기본적인 브랜치 병합 방법이지만, 브랜치 내역이 계속 남게된다는 단점이 있다.

- 이것이 왜 단점이냐면, 프로젝트를 계속 진행하다보면 다양한 브랜치들을 만들게 되는데, 그 브랜치들을 merge로 관리한다면, 브랜치 내역이 마치 "거미줄" 처럼 복잡하게 얽혀 보이기 때문이다.

 

3. 커밋 내역을 깔끔하게 관리할 수 있는 rebase

- rebase는 그런 merge의 단점을 극복할 수 있는 기능이다.

- rebase는 말 그대로 브랜치의 base를 다시(re) 세팅하는 것이라 이해하는 것이 직관적인 것 같다.

- 위 merge에서 상황2번 예시를 그대로 사용해 설명해보겠다.

> merge의 경우, merge 커밋이 남고 브랜치 내역이 남는 것과 달리 rebase는 다음과 같은 결과를 도출한다.

> 작업 순서는 다음과 같다.

git checkout dev
git rebase main
위 명령어의 의미를 풀이하자면,
- dev 브랜치의 베이스를 main으로 옮기겠다는 의미이다.
- 구체적으로, dev 브랜치는 main에서 체크아웃한 기점부터 새로운 줄기를 가지게 된다.
- rebase 명령어로 dev 브랜치의 줄기를 main 브랜치로 옮기겠다는 것이 된다.
- 이 때, rebase는 다음과 같이 동작한다.
> 상황2에서 dev가 checkout한 기점은 main 브랜치의 커밋1이었다.
> 이후 dev에서 커밋2를 올렸고, main에서는 커밋3을 올렸다.
> rebase를 하게되면, 먼저 main의 커밋 1,3을 저장한다.
> dev에서는 기존에 커밋했던 커밋2를 새로 커밋하도록 유도된다.

- 줄기로 표현해보자면 이렇게 되는것이다.
(아래가 과거다.)
기존
main 커밋3
                    dev 커밋2
main 커밋1

rebase
dev 커밋2
main 커밋3
main 커밋1

- 이와 같이 rebase를 하면, 기존에 branch에 있던 커밋을 다시 생성하게 된다.

- 그리고 rebase가 완료되면 git rebase --continue 명령어를 작성해줘야한다.

- rebase를 이용하는 명령어 순서를 다시 정리해보자면 다음과 같은 것이다.

git checkout dev
git rebase main

git commit 커밋2
git rebase --continue

- 이와 같은 작업을 하면, dev 브랜치인 상황에서 dev 브랜치의 줄기를 main 브랜치로 옮기게 된다.

- 이렇게 되면, dev 브랜치는 main 브랜치와 같은 선상에 놓이게 되므로, fast-forward merge를 할 수 있게 된다. 즉, 다음 명령어를 썼을 때, 새로운 merge 커밋이 남지 않게 되는 것이다.

git checkout main
git merge dev

 

- github에서 PR 생성 시, merge 방법으로 나오는 것 중 하나인 rebase and merge가 위 작업을 실행하는 명령어인 것이다.

- 정리하자면, merge 전에 rebase를 이용하면 다음과 같은 효과를 누릴 수 있다.

1. merge 커밋이 발생하지 않는다.
2. 브랜치 내역이 남지 않게 되어, 브랜치 줄기가 복잡해질 일이 없다.

3. pull에도 이용되는 rebase

- 한편, pull을 이용할 때도 rebase가 이용된다.

- 참고로, pull은 git fetch + merge를 수행하는 명령어라고 한다.

git pull origin main --rebase

- 위와 같은 명령어로 pull을 할 때 rebase를 이용할 수 있다.

- 위 명령어의 의미는

> remote의 main branch의 커밋내역을 로컬 저장소로 가져오는데, 로컬 저장소의 브랜치 베이스를 remote의 main branch로 변경해서 가져오는 것이다.

- 상황을 통해 git pull 과 git pull origin --rebase의 차이를 설명해보겠다.

상황
1. 로컬의 dev 브랜치에서 무언가 작업을 해서 remote에 push 했다.
2. 다른 개발자가 이미 dev에 PR을 승인 받은 게 있어서, remote와 local 브랜치 간의 형상이 달라서 push가 되지 않았다.

git pull
- remote의 내역을 받아온다.
- 충돌이 있다면 충돌을 해결한다.
- 하지만 pull도 merge 작업이므로 merge 커밋이 생성된다.
- 커밋 내역은 다음과 같이 된다.
merge 커밋
remote 커밋1 
local 커밋1

git pull --rebase
- remote의 내역을 받아온다.
- 충돌이 있다면 충돌을 해결한다.
- rebase를 먼저 하므로 merge 커밋이 생성되지 않는다.
- 커밋 내역은 다음과 같이 된다.
local 커밋1
remote 커밋1