티스토리 뷰
[Git] merge와 rebase의 차이, 깔끔한 Git history를 위한 Rebase 사용법 주의사항
blueprint-12 2022. 12. 1. 20:07git을 소수 인원으로 사용할 때에는 push, pull, merge만 사용해도 큰 문제가 없습니다. 개인 프로젝트할 때에는 그렇게 했구요. git history도 깔끔했습니다. 하지만 소수인원이 아닌 회사에서 협업하게 된다면 git history가 엉망이될 겁니다. 이럴 때 우리가 사용할 수 있는 기능이 rebase입니다.
개인적으로 sourcetree라는 git GUI툴을 사용하지만 사용하면 사용할수록 시각적인 매력 외에 불편함을 느끼고 있었습니다. CLI가 익숙해지면 계속 그것만 사용하게 될거라는 분도 계셨구요. CLI로 진행하게 된다면 git history가 깔끔하게 정렬되어 있는 게 좋을 거 같았습니다.
Merge와 Rebase의 차이?
Git에서 한 브랜치에서 다른 브랜치로 합치는 방법으로는 두 가지가 있습니다. 하나는 merge , 나머지 하나는 rebase입니다.
두 브랜치를 합치는 가장 쉬운 방법은 merge명령어를 사용하는 것입니다. 두 브랜치의 마지막 커밋 두 개(C3, C4)와 공통 조상 (C2)을 사용하는 3-way Merge로 새로운 커밋을 만들어 냅니다.
비슷한 결과를 만드는 다른 방식으로, C3에서 변경도니 사항을 Patch로 만들고 이를 다시 C4에 적용시키는 방법이 있습니다. Git에서는 이런 방식을 Rebase라고 합니다. rebase 명령으로 한 브랜치에서 변경된 사항을 다른 브랜치에 적용할 수 있습니다.
rebase 실행 명령어
- master -> main
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
-> 두 브랜치가 나뉘기 전인 공통 커밋으로 이동하고 나서 그 커밋부터 지금까지 checkout한 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 어딘가에 임시로 저장해놓고 Rebase할 브랜치(experiment)가 합칠 브랜치(master)가 가리키는 커밋을 가리키게 하고 아까 저장해놓았던 변경사항을 차례대로 적용합니다.
그리고 나서 master 브랜치를 Fast-forward 시킵니다.
$ git checkout master
$ git merge experimen
->C4로 표기된 커밋에서의 내용은 merge 예시에서 살펴본 C5커밋에서의 내용과 같을 것입니다. Merge이든 rebase든 둘 다 합치는 관점에서는 서로 다를 게 없습니다. 하지만, Rebase가 좀 더 깨끗한 히스토리를 만듭니다.
Rebase한 브랜치의 log를 살펴보면 히스토리가 선형(linear)입니다. 일을 병렬로 동시에 진행해도 Rebase하고 나면 모든 작업이 차례대로 수행된 것처럼 보입니다. (깔끔한 git history를 가지게 됐습니다.)
Rebase는 보통 리모트 브랜치에 커밋을 깔끔하게 적용하고 싶을 때 사용합니다. 아마 이렇게 Rebase하는 리모트 브랜치는 직접 관리하는 것이 아니라 그냥 참여하는 브랜치일 것입니다 메인 프로젝트에서 Patch를 보낼 준비가 되면 하는 것이 Rebase니까 브랜치에서 하던 일을 완전히 마치고 orgin/master 로 Rebase합니다. 이렇게 rebase를 하고나면 프로젝트 관리자는 어떠한 통합작업도 필요 없게 됩니다. master 브랜치를 fast-forward 시키면 됩니다.
Rebase 활용 관련 사례는 링크를 걸어두겠습니다. 참고해주세요.
Rebase의 위험성
rebase는 장점이 많은 기능이지만 단점이 없는 것은 아닙니다.
이미 공개 저장소에 Push한 커밋을 rebase하지마라
- rebase는 기본의 커밋을 그래도 사용하는 것이 아니라 내용은 같지만 다른 커밋을 새로 만드는 것입니다. 새 커밋을 서버에 push하고 동료 중 누군가가 그 커밋을 pull해서 작업을 한다고 칩니다. 그런데 그 커밋을 gir rebase로 바꿔서 push해버리면 동료가 다시 push했을 때 동료는 다시 merge해야 합니다. 그리고 동료가 다시 merge한 내용을 pull하게 되면 나의 코드는 엉망이 됩니다. 관련된 자세한 예시는 링크를 참고합시다.
Rebase 한 것을 다시 Rebase하기
위의 상황(한 팀원이 다른 팀원의 의존하는 커밋을 없애고 Rebase한 커밋을 다시 push함)에 빠지게 되면 유용한 Git 기능이 있습니다. 어떤 팀원이 강제로 내가 한 일을 덮어썼다고 하면 내가 했던 일은 무엇이고 덮어쓴 내용이 무엇인지 알아내야 합니다.
커밋 SHA 체크섬 외에도 Git은 커밋에 Patch할 내용으로 SHA-1 체크섬을 한번 더 구합니다. 이 값은 "patch-id"라고 합니다. 덮어쓴 커밋을 받아서 그 커밋을 기준으로 Rebase할 때 Git은 원래 누가 작성한 코드인지 잘 찾아낼 수 있다.
그래서 Patch가 원래대로 잘 적용된다.
위의 상황으로 돌아가 다시 merge하는 대신 git rebase gamza/master (gamza는 그냥 제 이름이라 칩시다)명령을 실행하면 Git은 아래와 같은 작업을 합니다.
- 현재 브랜치에만 포함된 커밋을 결정한다. (C2, C3, C4, C6, C7)
- Merge 커밋이 아닌 것을 결정한다. (C2, C3, C4)
- 이 중 merge할 브랜치에 덮어쓰이지 않은 커밋을 결정한다. (C2, C3. C4는 C4’와 동일한 Patch다)
- 결정한 커밋을 gamza/master 브랜치에 적용한다.
결과를 확인해보면 같은 merge를 다시 한다 같은 결과 대신 제대로 정리된 강제로 덮어쓴 브랜치에 Rebase하기 같은 결과를 얻을 수 있습니다.
동료가 생성했던 C4와 C4` 커밋 내용이 완전히 같을 때만 위와 같이 동작됩니다. 커밋 내용이 아예 다르거나 비슷하다면 커밋이 두 개 생깁니다. (같은 내용이 두 번 커밋될 수 있기 때문에 깔끔X)
git pull 명령을 실행할 때 옵션을 붙여서 git pull --rebase 로 Rebase 할 수 있습니다. 물론 git fetch 와 git rebase gamza/master 이 명령을 직접 순서대로 실행해도 됩니다.
✅git pull 명령을 실행할 때 기본적으로 --rebase 옵션이 적용되도록 pull .rebase 설정을 추가할 수 있습니다.
git config --global pull .rebase true 명령으로 추가합니다.
Push 하기 전에 정리하려고 Rebase하는 것은 괜찮습니다. 또 절대 공개하지 않고 혼자 Rebase 하는 경우도 괜찮습니다. 하지만, 이미 공개하여 사람들이 사용하는 커밋을 Rebase하면 틀림없이 문제가 생깁니다.
차후에, 후회하지말고 git pull --rebase로 문제를 미리 방지할 수 있다는 점을 작업하는 동료와 모두 함께 공유하길 권고합니다.
그래서 Rebase vs Merge?
히스토리를 보는 관점 중 하나는 작업한 내용의 기록으로 보는 것(혹은 프로젝트가 어떻게 진행되었나에 대한 이야기)입니다. 작업 내용을 기록한 문서이고, 각 기록은 각각 의미를 가지며, 변경할 수 없습니다. 이런 관점에서 커밋 히스토리를 변경한다는 것은 역사를 부정하는 꼴입니다. 이렇게 했을 때 지저분하게 수많은 Merge 커밋이 히스토리에 남게되면 문제가 과연없을 지 생각해봐야 합니다.
첨언을 하자면 로컬 브랜치에서 작업할 때는 히스토리를 정리하기 위해서 Rebase할 수도 있지만, 리모트 등 어딘가에 Push로 내보낸 커밋에 대해서는 절대 Rebase하지 말아야 합니다.
위의 설명에서 봤듯이 merge는 branch를 통합하는 것이며, Rebase는 branch의 base를 옮긴다는 개념의 차이가 있습니다. 그래서 둘 중에 하나만 쓰는 것이 아니라 두 가지가 존재합니다.
- merge만 사용한다 (only merge)
- Rebase와 merge를 사용한다 (merge + rebase)
Rebase는 무엇일까요?
사전적 의미는 base를 재설정한다는 의미입니다.
여기서 말하는 base는 branch의 base를 의미합니다.
branch는 base 지점을 가지고 있어 base에서부터 코드를 수정합니다.
git history를 살펴보면 branch의 base가 어디 있는 지 확인할 수 있습니다.
- B 지점을 base로 가진 branch가 D, E 커밋을 진행한다.
- C 지점으로 base를 이동하기 위해 branch에서 C 지점으로 rebase를 한다.
- C 지점으로 rebase하게 되면 기존 D, E 커밋은 새롭게 정렬되어 C 지점 이후로 변경된다.
-> C 지점으로 base를 옮기고 기존에 있던 Commit을 재정렬하게 된다는 의미입니다.
리베이스의 효과는 바로 Git History가 상당히 깔끔해진다는 것입니다.
merge만 했을 때와 Rebase를 같이 진행했을 때 git history는 어떤 지 확인해봅시다.
merge dev from branch1 -> merge dev from branch2
merge만 사용하여 dev 브랜치에 각자의 브랜치를 머지하게 되면 서로 엮이지 않는 branch가 각각 dev에 진행되는 모습으로 서로 엉키게 됩니다. 브랜치가 서로 엉키는 모습은 개발자의 수가 늘어날수록 심해지겠죠
이런 branch들이 엉키는 git histroy를 깔끔하게 해주는 방법이 rebase입니다.
merge와 rebase를 사용하여 dev에 반영했을 때는
merge dev from branch 1 -> rebase dev into branch2
branch1이 merge된 dev branch를 branch2에 rebase했습니다.
rebase를 하게 되면 git history의 commit이 재정렬됩니다.
마치 branch1이 dev에 merge된 후 branch를 새로 딴 것처럼 commit 내용들이 정렬됩니다.
그 다음, branch2를 dev에 merge 합니다. (merge dev from branch2)
branch2를 dev에 merge 하면 위와 같이 깔끔한 git history를 가질 수 있습니다.
개발자가 여러명이라도 순서대로 commit 한 것과 같은 git history를 만들 수 있습니다.
참고 자료:
https://backlog.com/git-tutorial/kr/stepup/stepup2_8.html
https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-Rebase-%ED%95%98%EA%B8%B0
https://firework-ham.tistory.com/12
'프로그래밍 > Git, Github' 카테고리의 다른 글
[Github | CLI] Github 공식 CLI: gh (0) | 2023.02.03 |
---|---|
[Github] commit message & Issue 활용 (0) | 2023.01.11 |
[Github] 강제 push하기, git 브랜치 내용 덮어쓰기 (0) | 2022.08.10 |
[Git] .gitignore가 적용되지 않을 때 with sourcetree (0) | 2022.05.22 |
[Git] sourcetree 충돌 해결하기(merge conflict) (0) | 2022.05.09 |
- Total
- Today
- Yesterday
- reactAPI
- 원티드 3월 프론트엔드 챌린지
- 틸드와 캐럿
- ~ ^
- 타입스크립트 DT
- && 셸 명령어
- nvm경로 오류
- is()
- 원티드 FE 프리온보딩 챌린지
- Prittier
- 원티드 프리온보딩 FE 챌린지
- aspect-ratio
- grid flex
- tilde caret
- 형제 요소 선택자
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- 타입스크립트 장점
- 항해99추천비추천
- nvm 설치순서
- text input pattern
- fs모듈 넥스트
- 프리온보딩 프론트엔드 챌린지 3월
- float 레이아웃
- D 플래그
- getServerSideProps
- 항해99프론트
- 부트캠프항해
- 항해99프론트후기
- 프리렌더링확인법
- getStaticPaths
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |