본문 바로가기

Git

Git 속이기 - rebase

$ git log로 이제껏 남겨둔 commit을 볼 수 있다. branch들이 각기 origin/main에서 언제 분화되어 나오고 거기서 얼마나 멀리, 몇 commit이나 떨어졌는지도 알 수 있다. 이렇게 쌓아둔 commit 중에는 무언가 중요한 변화가 담겨있는 것도 있고, branch를 옮겨다니느라 대충 남긴 것도 있다. 이걸 그대로 merge하면 이 기록이 전부 남는다. 여러 branch를 받아들이는 main의 이력이 특히 엄청 지저분해진다.

 

rebase를 쓰면 이 이력을 원하는 대로 조작할 수 있다. 여러번의 commit을 마치 한 번의 commit에 해낸 것처럼 합쳐 넣을 수 있고, 한참 전에 파생된 branch지만 최신 main의 상태를 따와 만든 것처럼 히스토리를 바꿀 수 있다.

기능이 센 만큼 조심해서 사용해야 된다. 잘못하면 로컬에서 작업해둔 내역을 잃어버리는 아찔한 경험을 할 수도 있다.

 

commit 몇 개 없는데 이력 조작까지 해야되나 싶다. 그럴 때 git flow에 대한 이해를 더하면 누구든 조금씩은 갖고 있는 MBTI J가 툭 튀어나온다. 아 정리하고 싶다! (이런 마음이 안 생기면 어떨 수 없고...)

 

main을 기준으로 branch A와 B 두개를 만든 다음 A, B 각기 commit을 쌓은 상황을 생각해보자. A에서는 2월 10일, 20일에 commit을 만들고 3월 1일에 main에 merge를 했다. B는 2월 11일, 13일, 21일에 commit을 만들고 3월 2일에 merge를 했다. 그리고 main은 2월 3일, 19일, 28일에 commit을 쌓았다.

이랬을 때 main의 이력은

main(2/3) - A(2/10) - B(2/11) - B(2/13) - main(2/19) - A(2/20) - B(2/21) - main(2/28) - A(3/1) - B(3/2)

이런 꼴이다. A의 수정사항과 B의 수정사항이 뒤섞여 있을 뿐 아니라 A(3/1)과 B(3/2)는 merge commit이기 때문에 사실상 기록 외의 의미가 없다.

같은 상황에서 rebase를 쓰면 main의 이력을 더 가독성 있게 만들 수 있다.

3월 1일에 A를 main에 merge하기 전, A branch에 있는 상태에서 $ git rebase -i main을 한다. 그러면 원래는 main(2/3)의 상태를 가지고 시작했던 A가 마치 main(2/28)의 상태를 갖고 시작했던 것처럼 이력이 조작된다. 원래 가지고 있던 2월 10일, 20일의 커밋은 3월 1일에 만든 한 개의 커밋으로 합쳐진다. 이 과정에서 conflict도 commit마다 단계별로 해결해야 된다. 그 후에 main에 merge하면 단 한 개의 commit으로 들어간다. 이렇게 B도 rebase하면 main의 A(3/1)의 상태를 갖고 시작한 것처럼 조작할 수 있어 main의 이력이 아래처럼 아주 깔끔해진다.

main(2/3) - main(2/19) - main(2/28) - A(3/1) - B(3/2)

 

이렇게 rebase를 사용하여 더욱 가독성 있고 나중에 이전 상태로 돌아갈 때도 관리가 더 쉬운 이력을 만들 수 있다.

 

1. rebase는 merge 기능도 갖추고 있다.

merge도 하고 rebase도 하는 건 아니다. branch에서 rebase를 한 뒤 main에 합치고 싶다면 push request를 올리고 나서Github에서 merge하면 된다.

2. commit 2~3개마다 rebase하는 걸 권장한다

rebase는 main에 branch를 합치기 전 딱 한 번만 사용하는 게 아니다. branch에서 commit이 약간 쌓였을 때마다 하는 게 좋다. main에서 변경 사항이 생겨 branch에서 갖고 있는 main의 상태와 달라졌다면, rebase시 branch에 있는 commit마다 conflict를 고쳐줘야 된다. commit이 많을 수록 실수할 위험이 크다. branch 여러개를 넘나드는 작업을 한다면 특히 주의하자.

3. Github 잔디가 사라진다

commit 여러개를 하나로 합치는 작업이니 그 전에 심어둔 잔디와는 안녕이다.

4. pick은 하나만 남기기

rebase 커맨드 중 -i는 에디터를 띄우고자 함이다. 여기서 처음 뜨는 에디터에는 commit 해쉬 앞에 pick이 붙어있다. commit이 3개라면 3개의 앞에 각각 붙어있다. 이 중 맨 위 pick만 남기고 나머지 pick은 s로 바꿔 하나로 합치게 만들자.

5. commit 메세지는 상세하게 쓰기

3번 적업을 한 뒤 :wq로 저장하며 나가면 합친 commit의 메세지를 작성하는 에디터가 뜬다. 최대히 상세히 써서 이 안에 들어있는 수정 사항을 알 수 있게 하자.

6. branch를 팔 때는 꼭 main에서 파자

main이 안정된 코드만 갖고 있고 항상 같은 기준으로 branch를 만들기 위해서도 있지만, 다른 branch에서 파생 branch를 만들어 작업하다 원본 branch를 rebase하면 파생 branch의 작업 내역이 날아갈 수도 있다.

7. conflict를 해결하다 좀 싸하다 싶으면 중단하자

중단하고 rebase 전 시점으로 돌아갈 수 있다. stash -u 로 작업 내역을 임시 저장한 뒤 branch를 지우고 main에서 새 branch를 만들어 거기다가 stash apply하는 게 위험이 적다.

8. 두번째 push할 때는 강제로 넣어야 된다

rebase는 이전 commit 내역을 바꾸는 커맨드이기 때문에 만일 이미 push된 commit 이후에 rebase를 했다면 Github에서 알고 있는 commit 이력이 바뀐 셈이다. 그래서 이렇게 한 뒤 push한다면 git에서 막는다.

$ git push origin <branch> -f

이렇게 강제로 push할 수 있다. commit 이력 바뀌는 거 아니까 그냥 넣어! 이건 rebase를 했을 때만 사용하자.