본문 바로가기

Git

[Git] confilct 와 결부되는 cherry-pick, rebase, revert 알아보기

반응형

해당 포스팅은 얼마 전 업로드된 이고잉님의 오픈튜토리얼스 Git 강의를 보고 개인적으로 정리하는 목적 하에 작성되는 포스팅입니다. 아래 포스팅 내용의 모든 출처는 원본 영상임을 밝힙니다.

 

Github


이번 포스팅에서는 Git을 어느정도 알고 나면 더 알고싶어 지는(?) 기능들인 cherry-pick, rebase, revert 기능에 대해 알아보고 실습도 해보려고 한다. 우선 해당 기능들은 이전 포스팅에서 배운 Git 에서의 conflict를 마주하고 해결하는 방법에 대해 알고 있어야 학습이 수월하다는 점 참고해두자. 물론 여기서 배울 3가지 기능들이 무조건적으로 conflict 와 결부되는 것은 아니지만, 해당 기능들을 사용하다 보면 conflict가 자주 발생할 수 있기 때문에 conflict가 무엇인지, 또 어떻게 해결하는 것인지 모른다면 꼭 이전 포스팅을 이해하고 오자.

1. 부분적으로만 merge 하고 싶어, cherry-pick!

cherry-pick 이라는 기능은 merge하는 방법이지만, 전체가 아닌 일부분만 merge하는 기능이다. 우선 아래처럼 master 브랜치를 base로 간주하고, 여기에서 파생하는 main, exp 브랜치 2개를 생성하고 각기 수정사항은 add, commit 시켜보도록 하자. common.txt 파일에서 각기 브랜치에서 수정한 내용은 아래와 같다. 수정한 내용은 커밋 메세지에 있는 것인데, 예를 들어서 2M 이라는 커밋 메세지는 2라는 내용을 2M으로 대체했음을 의미하는 걸로 이해하면 된다.

 

1
-
2
-
3
-
4

 

위 common.txt 원본을 base(master 브랜치)로 하여 main, exp 브랜치에서 각기 아래와 같이 내용을 수정했고, 총 3가지 브랜치의 현재 그래프 상태는 아래와 같다.

 

현재 각기 브랜치의 상태

 

현재 우리(HEAD)는 main 브랜치를 가리키고 있다. 그런데 우리는 exp 브랜치가 수정한 내용을 merge 하고 싶긴한데, exp 브랜치의 모든 내용을 merge 시키고 싶은 것이 아닌 커밋 메세지가 4E인 즉, [커밋아이디가 'ddb1df2c'인 상태 ➜ 커밋아이디가 '793acb1b'인 상태] 로 변경되면서 반영된 수정사항만 merge 하고 싶은 것이다. 결국, '전체가 아닌 일부만 merge' 하고 싶다는 것! 이럴 때 우리는 cherry-pick을 사용할 수 있다.

 

cherry-pick을 사용할 때는 3-way merge 방법을 사용하는데, 3-way merge 방법을 사용해서 cherry-pick이 부분병합을 할 수 있는 방법은 이고잉님의 원본 영상을 보면 이해할 수 있다. 여기서 우리는 cherry-pick을 사용하면 어떤 식으로 main 브랜치의 common.txt 내용이 어떻게 바뀔지 살펴보도록 하자. cherry-pick을 사용하는 명령어는 아래와 같다.

 

git cherry-pick [commit id]

 

cherry-pick을 실행한 후의 모습

 

명령어로 cherry-pick을 수행했는데, 위와 같이 confict가 발생했다. 이유가 뭘까? 방금 우리가 cherry-pick으로 부분 병합하려는 부분은 [커밋아이디가 'ddb1df2c'인 상태 ➜ 커밋아이디가 '793acb1b'인 상태] 로 갈 때 변경된 사항이다. 그 변경된 사항이라 하면 4라는 부분이 4E로 바뀐 부분이다. 즉, [4 ➜ 4E] 로 바뀐 부분을 main 브랜치 common.txt로 병합하려하는데, 이미 main 브랜치의 common.txt에는 4M으로 이미 바뀌어져 있다. 다시 말해, 동일한 부분을 서로 다르게 수정하고 있기 때문에 conflict가 발생한 것이다. 

 

그래서 친절한 Git은 위 vs code 화면의 왼쪽을 보면 conflict가 발생한 부분을 표시해주고 프로그래머에게 어떤 수정사항을 선택할지 권한을 위임시켜준다. 여기서는 4E를 선택하는 것으로 하고 4E만 남기고 나머지 내용을 다 지워준 후, 아래 명령어를 수행하도록 하자.

 

git add common.txt
git cherry-pick --continue

 

conflict를 해결하고 난 후의 브랜치 그래프 모습

 

conflict를 해결해주고 위 명령어를  수행하면 cherry-pick 명령어가 잘 수행되고 부분 병합이 완성된 것을 알 수 있다. 이것이 바로 conflict에 잘 대처하면서 cherry-pick을 사용하는 방법이다.

2. 거짓말을 하는 merge, rebase!

다음은 rebase 기능에 대해 알아보도록 하자. rebase는 쉽게 말해서 merge와 동일한 기능을 하는 것이다. 하지만 merge 와는 다른 차이점이 있는데, 바로 merge할 때, 조상(base)을 인위적으로 조작(re)하여 마치 하나의 조상(base) 코드에서 파생된 코드인 것처럼 위장시켜주는 것이다. 이게 무슨 말인지는 아래 그림을 통해 살펴보자. 우선 1번 목차에서 cherry-pick 까지 수행한 상태인데, reset 기능을 통해서 cherry-pick 수행 이전의 상태로 되돌아간 후의 브랜치 그래프 형태는 아래와 같다.

 

reset 명령어를 통해서 cherry-pick 이전의 상태로 돌아가자

 

그림의 오른쪽을 보면 현재 master라는 조상(base)에서 main 브랜치, exp 브랜치가 파생되어 있는 것을 볼 수 있다. rebase를 하게 되면 merge를 하되 하나의 조상에서 출발한 코드였던 것처럼 위장시켜서 그래프 형태를 일직선 형태로 만들어준다. 그러면 rebase 명령어를 아래처럼 수행해서 exp 브랜치를 main 브랜치로 rebase 병합 시켜보자.

 

git rebase exp

 

exp 브랜치를 main 브랜치로 rebase 시켜보자

 

그런데 여기서도 또 conflcit 문제가 발생했다. 이유는 cherry-pick 때와 동일하다. 현재 exp 브랜치는 4E를 나타내고 있는데, main 브랜치는 동일한 부분을 4M으로 나타내고 있다. 그래서 동일한 부분을 두 브랜치가 서로 다르게 수정하고 있기 때문에 conflict가 발생한 것이다. 여기서도 4E를 선택하기로 결정해서 conflict를 해결한 후, 아래의 명령어를 쳐서 rebase를 완료해보자.

 

git add common.txt
git rebase --continue

 

confilct를 해결한 후 rebase를 완료시켜보자

 

vs code 화면의 오른쪽 브랜치 그래프를 보면 main과 exp 브랜치가 merge 된 것을 볼 수 있다. 그런데 특이하게, 이전에 exp 브랜치는 빨간색으로 표시되었는데, 현재 exp 브랜치도 base(master 브랜치)와 동일하게 파란색깔로 변해 일직선 형태로 그래프가 완성된 것을 볼 수 있다. 즉, exp 브랜치도 마치 처음부터 base 브랜치를 조상으로 했던 것처럼 일종의 '위장' 병합을 시켜준 것이다. 이것이 바로 conflict에 잘 대처하면서 rebase 기능의 사용법이다.

3. cherry-pick의 반대 방향으로, revert!

다음은 revert 기능이다. revert는 cherry-pick 기능의 반대방향이라고 생각하면 된다. 이 반대방향이 무슨의미인지는 필자가 텍스트로 설명하는 것보다 이고잉님의 강의를 꼭 시청해보도록 하자! 여기서는 실습을 통해 revert를 수행하는 방법에 대해 알아보자.

 

먼저 위에서 rebase를 했기 때문에 이번에도 rebase 이전의 상태로 reset을 시켜놓자. 참고로 git reflog 라는 명령어를 사용하면 그동안 내가 git 관련 명령어 어떤 것들을 수행했는지 히스토리 로그를 볼 수 있다.

 

reset을 통해서 rebase하기 전으로 돌아가자

 

그러면 이제 revert를 사용해보자. 우리는 exp 브랜치로 checkout을 수행한 후, exp 브랜치의 [커밋 아이디가 'ddb1df2c'] 인 것을 revert할 것이다. 이 revert를 수행하게 되면 현재 exp 브랜치의 최신 커밋상태에서 추가로 새로운 버전을 만들어내는데, 이 때 [커밋 아이디가 'ddb1df2c' 버전] 에서 [커밋 아이디가 '13f15f67' 버전]으로 역방향으로 수정사항을 반영해 만든다.

 

이게 무슨말인지 잘 생각해보자. 원래는 순차적으로 [커밋 아이디가 '13f15f67' 버전] ➜ [커밋 아이디가 'ddb1df2c' 버전] 으로 버전을 생성하면서 수정사항을 추가했다. 하지만 revert는 이것을 거꾸로 해서 [커밋 아이디가 'ddb1df2c' 버전] ➜ [커밋 아이디가 '13f15f67' 버전] 으로 수정된 사항을 반영하는 셈이다. 즉, 원래 수정된 것을 원래 되돌려 놓는 셈이다!

 

그래서 아래 명령어를 수행해서 revert를 시켜보자.

 

git revert [commit id]

 

revert를 사용해서 수정사항을 원래대로 되돌려보자

 

위 vs code 화면의 왼쪽 화면을 보면 [2E ➜ 2] 로 내용이 원래대로 되돌려진 것을 볼 수 있다. 그런데 revert를 사용할 때도 conflict가 발생할 수도 있는데, 방금 위에서 원래대로 되돌려진 내용이 이미 다른 내용으로 수정되어 있을 때이다. 이럴 때도 마찬가지로 confilct를 잘 해결해주고 아래 명령어를 수행해주어서 revert를 수행해주면 된다.

 

git add common.txt
git revert --continue

지금까지 confilct와 결부되는 cherry-pick, rebase, revert 기능에 대해 배워보았다. 그동안 3가지 기능에 대해 막연히 궁금해하고 제대로 공부해본 적이 없었는데, 이번 기회에 좋은 내용을 공부할 수 있던 기회였다. 물론 필자 포스팅을 보면서 100%이해가 되면 좋겠지만 이고잉님의 원본 영상을 메인으로 공부하고, 보조 자료로서 필자 포스팅을 참조하길 바란다. 이제 점점 Git이 내가 사용하기 무서워하는 툴보다 친숙하고 자신감 있는 툴로 바뀌는 것을 느끼는 요즘이다.

반응형