- Today
- Total
개발하는 고라니
[Git] 4가지 케이스에 대해 Rebase 사용 본문
대부분 개인적으로 프로젝트를 하거나, 공부를 하는 차원에서는 Git 혹은 Github에 대해 저장하고, 불러오는 용도로만 써도 무방했다. 하지만 필드에서 협업을 하게되면 어쩔 수 없이 '더 공부해야겠구나' 느끼게 된다. 그 이유는 회사마다 코드를 관리하는 정책이 다르기 때문이다. 예를 들어, A 회사는 pull(fetch + merge)로 하고, B 회사는 squash merge로 하고, C 회사는 rebase를 쓴다. 혹은 rebase와 merge를 섞어서 쓰기도 한다.
마찬가지 이유로 구체적인 정책이 정해지진 않아왔지만, 최근 들어 모든 팀이 동일한 정책을 사용하기로 맞추어 졌고, Rebase를 사용하기로 했다.
하지만 나는 rebase가 뭔지 모른다. 그래서 실습하며 배워보고자 한다.
Rebase vs Merge
Rebase와 Merge의 가장 큰 차이점은 깃 로그의 모양이 아닐까 싶다.
[Rebase]
- feature 브랜치의 분기 지점을 main 브랜치의 끝으로 이동시킵니다.
- 파괴 작업이며 feature 브랜치의 모든 커밋을 새롭게 작성합니다.
- 불필요한 병합 커밋이 없으며 완벽한 선형 프로젝트 기록을 유지할 수 있습니다.
[Merge]
- 두 브랜치의 기록을 묶기 위한 "병합 커밋" 을 생성합니다.
- 비파괴 작업이며 기존 분기, 커밋은 변경되지 않습니다.
- 업스트림( main 브랜치)의 변경 사항을 통합할때마다 병합 커밋이 생성되며, 히스토리를 읽기 어렵게 만듭니다.
Rebase는 말 그대로 base를 다시 구축하는 것이고, Merge는 두 브랜치를 하나로 합치는 것이다.
Rebase는 작업 브랜치의 커밋들을 떨어져 나온 main의 2번째 커밋을 '베이스로' 재구축 한다. 그래서 feature 브랜치에서의 커밋과 다른 커밋이 만들어진다.
Merge는 feature브랜치의 작업 결과를 main에 병합할 때 새 merge 커밋이 만들어지는데, 이 때 충돌이 난다면 1회만 해결해주면 된다. 반면에 Rebase는 충돌이 났다면, 각 커밋마다 충돌을 해결해주어야 한다.
그래서 Rebase와 Merge 둘 중에 뭐가 좋느냐 따지는 것은 아닌 것 같다. 둘 다 장단점이 있기 때문이다. 나같은 초보자에게는 아무래도 Merge가 더 쉽다. 다만 트리가 지저분해지는 것이 단점일 수 있겠다.
Rebase 에는 많은 옵션들이 있는데, 사실 그것을 다 쓸 일은 거의 없을 듯 하며 지금 봐도 모른다. 그래서 내가 생각하는 4가지 케이스에 대해서 사용 방법에만 집중하자.
feature -> main (충돌 X)
[Rebase]
main으로 부터 분기된 feature/rebase-1 브랜치에서 커밋 3개가 발생하였다. 각 커밋 로그와 Revision Number는 다음과 같다.
(rebase-1 (3)) : 40034ec4a817ca370d3b6fa3664f3490c198bafd
(rebase-1 (2)) : b4807f6c6c1b2cd2f6cfee763b1a9a275127b1ad
(rebase-1 (1)) : 64a8b357fcee67a97f036e7e4f82448e1752f7c9
이제 PR을 올리고 Rebase로 병합해보자.
원격 리퍼지토리의 main에 'rebase'로 병합이 되었다. 이를 내 로컬 main에 그대로 가져오도록 해보자
$ git checkout main
$ git pull --rebase origin main
이때 커밋의 Revision Number는 다음과 같다.
(rebase-1 (3)) : 8af83a7bc42d241dfd9ab8a078f7baf0acab7d11
(rebase-1 (2)) : 3a83acea2289bcd7f2422f2c4cd607f832405048
(rebase-1 (1)) : c4c6f15d11c6ea0ef2c61bdfb5b7a31f3f91d04f
즉, feature 브랜치에서의 commit과 rebase 후 main 브랜치에 합쳐진 commit이 달라진 것을 알 수 있다.
main -> feature (충돌 X)
[시나리오]
동일한 main 커밋을 기점으로
A 개발자는 feature/rebase-2,
B 개발자는 feature/rebase-3
브랜치를 분기해서 작업을 했다.
이때 A는 3개의 커밋을 작성했고, 이를 rebase로 main에 병합했다. 이때 B도 작업을 다 마쳤기 때문에 main에 병합해야하나, 이미 main이 변경되었기 때문에 feature/rebase-3 브랜치와 main 브랜치의 동기화를 해주어야 한다. 이때 충돌은 없다.
이때, feature/rebase-3 커밋의 Revision Number는 다음과 같다.
(rebase-3 (2)) : 0423493317e2842ceb92e1c6b07978d7e36f2a23
(rebase-3 (1)) : 8680f831741978a09eeecce6b627830a841979db
이제 3번째 탭의 그림처럼 만들어보자.
$ git checkout feature/rebase-3
$ git pull --rebase origin main
이때, feature/rebase-3 커밋의 Revision Number는 다음과 같다.
(rebase-3 (2)) : b22dc41f65e64d1156c86bf2fc67fa954ebee6e1
(rebase-3 (1)) : e5cfef8a0f7107682e6abf0ecc2238d6d891b5ed
마찬가지로 기존 커밋과 달라진 것을 알 수 있다.
main -> feature (충돌 O)
[시나리오]
동일한 main 커밋을 기점으로
A 개발자는 feature/rebase-2,
B 개발자는 feature/rebase-3
브랜치를 분기해서 작업을 했다.
이때 A는 3개의 커밋을 작성했고, 이를 rebase로 main에 병합했다. 이때 B도 작업을 다 마쳤기 때문에 main에 병합해야하나, 이미 main이 변경되었기 때문에 feature/rebase-3 브랜치와 main 브랜치의 동기화를 해주어야 한다. 이때 충돌이 발생하였다.
충돌이 발생하면 충돌이 발생한 각 커밋마다 해결을 해주어야 하는데, 이는 개인적으로 몹시 불편하다...
우선 feature/rebase-3 브랜치로 가서 main 브랜치를 기준으로 동기화를 시도해보자.
$ git checkout feature/rebase-3
$ git pull --rebase origin main
충돌이 난다면 다음과 같이 출력되며, 코드에 충돌난 곳을 해결해달라고 뜬다.
From https://github.com/rhacnddl/rebase-merge-diff
* branch main -> FETCH_HEAD
Auto-merging src/org/gorany/Feature.java
CONFLICT (content): Merge conflict in src/org/gorany/Feature.java
error: could not apply 977df22...[Rebase] rebase-3 (1)
Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 977df22... [Rebase] rebase-3 (1)
해석해보면 "모든 충돌을 수동으로 해결해주고, git rebase --continue를 입력해줘, 이 커밋을 건너뛰려면 git rebase --skip을 입력해. 중단하고 이전 상태로 돌아가려면 git rebase --abort를 입력해."
라고 한다.
충돌을 해결하다가 답이 안나오면 git rebase --abort로 걍 원복 시키는 방법이 있다.
일단 충돌난 곳을 해결하고 다음과 같이 입력하자.
/* 충돌 해결 */
$ git add .
$ git rebase --continue
/* 충돌 해결 */
$ git add .
$ git rebase --continue
...
/* 원상복구 시키고 싶을 때 */
$ git rebase --abort
/* 건너뛰기 */
$ git rebase --skip
이 과정을 반복하다보면 각 커밋마다 충돌을 해결하게 되고 마침내 동기화가 완료된다. 이를 원격 리퍼지토리의 feature/rebase-3 에도 올려주자.
$ git push origin +feature/rebase-3
또는
$ git push -f origin feature/rebase-3
/* -f는 --force랑 같음. 강제로 push */
/* +feature/rebase-3 에서 '+'는 이해하기 조금 어려워서 일단 씁시다.. */
feature -> main (충돌 O)
이는 바로 위의 프로세스를 진행하면 충돌 없이 합칠 수 있다.
이렇게 rebase를 이용해 간단하게 사용법을 케이스 별로 알아보았다. 더 많고, 심화 내용이 있지만 아직 그거까진 불필요하여 다음에 필요성을 느끼면 그때 다시 파보도록 한다.
'Programming > Git' 카테고리의 다른 글
[Git] Github에 올린 파일 제거 (0) | 2021.04.03 |
---|---|
[Git] Push Error - (0) | 2020.12.03 |