본문 바로가기
개발

Git pull rebase, merge 차이

by 현명5079 2024. 12. 3.

[그림1] 기본 상황


위 그림과 같은 상황에 있다고 해보자.

- main 브랜치에서 같은 시점에 분기한 MyBranch OtherBranch가 있다.
- 나는 MyBranch에서 작업을 완료하고 Main 브랜치에 merge를 하려고 한다. 
- 근데 다른 사람이 OtherBranch에서 작업한 내용을 main에 먼저 merge를 시켰다. 


1. git pull 의 3가지 전략

바로 merge를 하면 충돌날 확률이 높아 pull을 해서 충돌을 해결하고, PR을 올려야한다. 이때 사용할 수 있는 pull 전략에는 3가지가 있다.

  • fast-forward only
  • merge (기본 옵션)
  • rebase

1) fast-forward only

명령어: ` git config pull.ff only`

 

MyBranch가 main의 모든 커밋을 가지고 있을 때 두 브랜치는 fast-forward 관계에 있다고 한다. 
fast-forward only는 말 그대로 두 브랜치가 fast-forward 관계에 있을 때만 pull을 하겠다는 말이다.

[그림1] 기본 상황

 

[그림1]은 fast-forward only로는 pull할 수 없는 상황이다. 이유는 다음과 같다.

  • 현재 Main 브랜치는 커밋1-커밋2-커밋3-커밋7을 가지고 있다. 
  • fast-forward only전략으로 pull하려면 MyBranch가 커밋1,2,3,7을 모두 가져야한다.
  • 하지만 MyBranch는 커밋7을 가지고 있지 않다.

따라서 다른 2가지 중 선택해야한다.


2) Merge (기본)

명령어: git config pull.rebase false

 



[그림2] merge 전략


아무 설정하지 않고 pull을 하면 자동으로 Merge로 선택된다.

  • 1번: 먼저 커밋5 커밋7의 충돌을 해결해서 커밋8을 만든다.
  • 2번: 충돌을 해결했으니 Main에 merge를 시켜서 커밋9를 만든다.

커밋8이 좀 불필요한 커밋이라는 느낌을 지울 수 없다. 다른 커밋은 코드가 추가되서 생기는 커밋이지만 커밋8은 기존코드와 기존코드를 섞는 과정에서 생기는 커밋이니 말이다. 그래서 이를 피하기 위한 rebase 전략이 있다.


3) Rebase

명령어: git config pull.rebase true 


rebase 를 이해하려면 먼저 base가 뭔지 알아야한다. 
base는 브랜치가 분기하기 전 가장 마지막 커밋이다. [그림1] 에서 MyBranch의 base는 커밋3이 된다.


[그림1] 기본 상황

 

rebase는 말그대로 base를 바꾸는 전략이다. Mybranch를 rebase하게 되면 [그림3]처럼 된다.

[그림3] rebase 전략


MyBranch가 Main브랜치의 커밋7 이후로 분기된 것처럼 한다는 말이다. 즉 base를 커밋3 -> 커밋7 로 변경한 것이다. rebase를 하게되면 MyBranch Main 브랜치와 fast-forward관계가 된다. Main브랜치에 Merge하기위해서는 커밋9만 추가하면 된다. 


2. rebase를 선택한 이유

현재 진행하고 있는 프로젝트에서는 rebase를 이용해서 pull을 하기로 했다. Merge 전략으로 pull을 할때보다 rebase 전략으로 pull을 할때가 불필요한 커밋이 적고 히스토리가 간단해지기 때문이다. 


3. rebase시 만나는 어려움

rebase를 사용하면 히스토리가 깔끔해지지만, 확실히 merge로 pull을 할 때보다 난이도가 있고, 눈에 띄는 2가지 불편한 점이 있다.

1) 모든 커밋에 대해 충돌해결

 

[그림2] merge 전략


Merge로 pull을 할 경우 충돌은 딱 1번 해결해주면 된다. `커밋5`와 `커밋7`의 충돌을 해결해서 `커밋8`을 만드는 과정에서 실행된다.

[그림3] rebase 전략


rebase로 pull을 해도 높은 확률로 충돌은 있다. MyBranch는 `커밋3`에서 나온 브랜치로 알고 작업했는데, 다른사람이 작업해서 만들어놓은 `커밋7`을 포함하게 됐으니 충돌이 없을리가 없다. 하지만 rebase는 충돌을 해결하는 커밋이 따로없고, 좀 더 번거롭게 해결해야한다.

rebase에서는 충돌을 다음과 같이 해결한다.
- rebase로 `커밋7`이 base가 된다
- `커밋7`과 `커밋4`가 충돌한다 -> 충돌 해결 ->충돌을 해결한 내용이 `커밋4`가 된다.
- `커밋7`과 `커밋5`가 충돌한다 -> 충돌 해결 ->충돌을 해결한 내용이 `커밋5`가 된다.

이렇게 MyBranch가 가진 모든 커밋에 대해 충돌을 해결하는 과정이 있어야한다.


2) 강제 push 

명령어: git push --force origin my-feature

[그림4] rebase 이후 로컬과 원격 차이


rebase를 하다보면 git push를 했을때 에러를 자주 마주치게 된다. 원인은 다음과 같다.

- 로컬에서 rebase를 하게 되면 로컬에 있는 MyBranch 커밋1-커밋2-커밋3-커밋7-커밋4-커밋5를 갖고 있다. 
- 하지만 원격은 rebase를 하기 전이기 때문에 아직 커밋3에서 분기된 브랜치로 인식되고있다. 즉 MyBranch는 `커밋7`을 가지고 있지 않다. 
- push를 할 때는 반드시 로컬브랜치가 원격브랜치의 모든 내용을 가져야한다.
- `커밋7` 때문에 push는 불가능하다.

이를 해결하는 방법이 많지만 강제push를 하는 방법으로 해결하는것이 가장 간단하다. 물론 강제 push는 위험하긴 하지만 공유 브랜치인 main,dev가 아니라 개인이 쓰던 MyBranch에 하기 때문에 괜찮은 방법이라고 생각한다.