Git
git rebase

git rebase 사용법 --continue, --abort, 취소, --interactive, squash 등

이번 포스팅의 주제는 git rebase 명령어입니다. git rebase 명령어는 git merge 명령어와 함께 둘 이상의 브랜치를 병합하는 대표적인 방법입니다. 이에 대한 기본적인 사용법과 자주 사용하는 옵션들에 대해 다뤄보겠습니다.

1. git rebase 제대로 이해하기

이전에 git merge 포스트에서 언급했듯이, git rebase 명령어로 둘 이상의 브랜치를 병합하면 일직선의 커밋 히스토리를 얻게 됩니다. rebase의 동작 원리를 도표로 표현하면 아래와 같습니다.

git rebase 동작 원리

예를 들어, feature-a 브랜치에서 main 브랜치로 rebase를 실행한다고 가정하겠습니다. Git은 두 브랜치의 공통 조상으로부터 뻗어나간 feature-a 브랜치의 모든 커밋을 main 브랜치의 마지막 커밋에 새롭게 붙여나갑니다. 동시에 두 갈래였던 기존 feature-a 브랜치의 커밋은 삭제합니다.

결과적으로 main 브랜치의 마지막 브랜치에서 시작한 새 feature-a 브랜치를 얻었습니다. 이렇게 커밋 히스토리를 수정해서 두 갈래였던 히스토리를 하나로 만듭니다.

이 때 사용하는 명령어는 다음과 같으며, git merge 명령어와는 반대로 source branch로 이동해서 target branch를 대상으로 명령어를 실행합니다.

위 예제에서는 다음과 같습니다.

$ git switch feature-a
$ git rebase main

실제 실행한 화면은 다음과 같습니다.

git rebase 실제 실행 결과

공통 조상 6519 커밋을 갖는 develop, main 브랜치를 rebase로 병합하였습니다. 그 결과로, main 브랜치에서 뻗어나간 새로운 develop 브랜치가 생겨났고, 기존 develop 브랜치는 사라졌습니다.

만약 rebase 과정 중 conflict가 발생하면, 다음과 같은 메시지가 출력됩니다.

$ git rebase main

실행 결과:

Auto-merging 1.md
CONFLICT (content): Merge conflict in 1.md
error: could not apply 7fc042b... 1.md edited in develop
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm ", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 7fc042b... 1.md edited in develop

여기서 rebase를 완료하고 싶다면, 충돌을 해결한 후 해당 파일들을 git add 명령어로 staging 합니다. 이후, 옵션을 사용해서 rebase 과정을 재개합니다.

rebase 과정을 이어가기 위한 옵션은 다음과 같습니다.

$ git rebase --continue

만약 충돌을 살펴보고 rebase를 취소하고 싶다면, 다음 명령어를 입력해서 rebase 실행 전 상태로 돌아갑니다.

$ git rebase --abort

이 2가지 옵션은 이후 섹션에서 각각 더 자세히 살펴보겠습니다.

2. conflict 발생 시, --abort 옵션으로 rebase 취소

Git은 rebase 진행 중에, conflict 발생이 확인되면 일시중지합니다. 그리고나서, 우리에게 충돌을 해결하고 rebase를 이어갈 것인지 아니면 취소할 것인지 선택권을 줍니다. 이 때, 취소하고자 한다면 사용할 수 있는 옵션이 --abort 옵션입니다.

이 옵션을 사용하면, 작업 트리가 rebase 실행 이전으로 돌아갑니다. 따라서, rebase를 계속 진행하기에 올바르지 않은 충돌이나 이슈가 생겼을 때 유용하게 사용할 수 있습니다.

명령어는 다음과 같이 사용합니다.

$ git rebase --abort

어떤 상황에 사용할 수 있는지 실제 실행 결과로 확인해보겠습니다.

git rebase --abort 실행 결과

로그 메시지를 보면, 1.md 파일을 두 브랜치가 수정한 상태입니다. 이 상황에서 rebase 명령을 실행했고 conflict 발생이 확인되어 rebase 가 잠시 멈춤 상태가 되었습니다. 이 때 --abort 옵션을 사용해서 rebase를 취소하자, 실행 전 상황으로 돌아오게 됩니다.

3. conflict 발생 시, --continue 옵션으로 rebase 완료

다음은 conflict가 발생했을 때, 이것을 해결하고 rebase 과정을 완료하는 --continue 옵션을 알아봅니다. 충돌을 해결하는 과정은 git merge 과정과 동일합니다. 충돌이 있어난 파일을 에디터로 열어 원하는 내용을 선택해주어야 합니다. 이에 대한 더 자세한 정보는 git merge 포스트에서 확인하시기 바랍니다.

파일을 수정한 후에는 git add 명령어로 해당 파일들을 staging 합니다. 그 다음 merge처럼 새로 커밋하는 것이 아니라, rebase 명령어의 옵션으로 이 rebase 과정을 이어갑니다.

다음과 같이 사용합니다.

$ git rebase --continue

실제 충돌 사례에서 실행한 결과를 확인해보겠습니다.

git rebase --continue 실행 결과

로그 메시지를 보면, 1.md 파일을 두 브랜치가 수정을 한 상태입니다. reabase를 실행하자 confilct 발생이 확인됐고 Git은 rebase 과정을 일시중지합니다.

이 때, 에디터로 충돌하는 라인을 정리해주고 staging한 후, --continue 옵션을 사용했습니다. 그 결과로, rebase가 성공적으로 완료된 것을 확인할 수 있습니다.

4. git reset 명령어로 실행 완료한 rebase 취소

만약 이미 실행 완료한 rebase를 취소하고자 한다면, git reflog 명령어와 git reset 명령어를 사용해야 합니다.

일반적인 커밋은 git commit 포스트에서 보았듯이, git log 명령어가 출력하는 커밋 ID로도 취소/삭제가 가능합니다. 하지만 git rebase 명령어를 실행했다면, 이미 기존 커밋을 변경한 상태이기 때문에 현재 커밋 히스토리의 정보로 되돌릴 수 없습니다.

git reflog 명령어는 브랜치, 태그 등 ref 로 저장되는 모든 값에 수행한 작업 로그를 보여줍니다. 디버깅을 위한 보다 자세한 git log 라고 이해해도 무방합니다. 이 덕분에, git rebase 명령을 실행했더라도 이전 커밋으로 돌아갈 수 있습니다.

다음은, git reflog 명령어가 출력한 로그 중 섹션 3에서 실행한 rebase 관련 부분입니다.

$ git reflog

실행 결과:

69fd9e0 (HEAD -> develop) HEAD@{0}: rebase (finish): returning to refs/heads/develop
69fd9e0 (HEAD -> develop) HEAD@{1}: rebase (continue): 1.md edited in develop
5e110f7 (main) HEAD@{2}: rebase (start): checkout main
7fc042b HEAD@{3}: rebase (abort): returning to refs/heads/develop

로그를 보면 직전에 실행한 rebase의 시작점이 HEAD@{2} 이며, 그 이전으로 돌아가기 위해서는 HEAD@{3}으로 돌아가야 한다는 것을 알 수 있습니다.

돌아가야 할 위치를 알았으니 git reset 명령어를 실행합니다. 이 때 사라지는 변경사항들을 작업 트리에 유지하려면 --mixed 옵션을, 다 지워진 채로 돌아가려면 --hard 옵션을 사용합니다.

다음은 --mixed 옵션을 사용한 결과입니다.

git reset --mixed 명령어로 git rebase  되돌린 결과

rebase 취소 후 삭제된 커밋들의 변경 사항이 작업 트리에 남은 것도 확인할 수 있습니다.

5. --interactive 옵션으로 원하는대로 커밋 히스토리 변경 (feat. squash)

git rebase 명령어의 --interactive 혹은 -i 옵션은 interactive rebase session 을 실행하는 옵션입니다. 이 세션 상에서, 우리는 현재 브랜치의 커밋 수정 혹은 squash, 순서 변경 등을 할 수 있습니다.

그 결과로, 현재 브랜치의 커밋 히스토리를 변경하게 됩니다. 그동안 보았던 git rebase 명령어와 달리, 하나의 브랜치의 커밋만 조작하고 있다는 사실을 꼭 기억해주세요.

이제, 실제 실행 결과를 보면서 이어가겠습니다.

세션 실행을 위해서는, 조작을 원하는 커밋의 직전 커밋 ID를 다음과 같이 입력해주어야 합니다.

$ git rebase -i [commit-hash]

현재 커밋 히스토리는 다음과 같습니다.

현재 커밋 히스토리

여기서 위 명령어를 입력하면 아래와 같은 창이 열립니다.

interactive rebase session 예시

여기서 #로 시작하는 모든 주석은 사용법을 알려주는 부분입니다. 주석이 없는 가장 위 부분은 이번 rebase 세션의 대상이 되는 커밋 목록으로, 오래된 순서대로 보여집니다.

pick 7fc042b 1.md edited in develop
pick 6d80db8 1.md edited in develop 2

우리 예시에서는 이 부분입니다. 여기서 pick 부분을 다른 명령어로 바꾼 후, 저장해서 원하는 작업 수행하는 방식입니다.

5.1. 주요 명령어와 사용 방법

  • pick: 현재 상태 유지
  • reword: 커밋 메시지만 변경
  • edit: 작성자, 날짜 등 세부 정보 변경
  • squash: 직전 커밋과 합치면서 새로운 커밋 메시지 생성
  • fixup: 직전 커밋과 합치면서 직전 커밋 메시지 사용
  • drop: 커밋 삭제

예를 들어, 7fd0 커밋의 메시지를 변경하고, 6d80 커밋을 직전 커밋에 합치려면, 다음과 같이 수정, 저장하고 에디터를 닫습니다.

reword 7fc042b 1.md edited in develop
fixup 6d80db8 1.md edited in develop 2

reword, edit, squash 처럼 새로운 정보가 필요한 작업을 수행하게 되면, Git은 필요한 만큼 새 에디터를 열어서 필요한 정보를 요구합니다.

git rebase -i 명령어에서 reword 실행

위 화면은 reword 명령에 따라, 새 커밋 메시지를 요구하는 에디터 창입니다. 아래 주석의 내용을 보면 현재 reword 작업 중이며 (Last command done), 다음에는 fixup 작업을 실행할 것임을 보여줍니다 (Next command to do).

git interactive session 결과

작업 결과를 보면, 위에서 적은 명령대로 커밋 메시지 수정, 직전 커밋과 병합 작업이 완료되었습니다.

이전에 git commit --amend 명령어에서 보았듯이, 간단한 메시지 수정이라도 Git은 새로운 커밋을 생성 후 교체하는 방법을 사용합니다. 따라서 협업 시에는 rebase 의 모든 명령을 주의해서 사용해야 합니다.

6. 마치며

git rebase 명령어는 Git의 불문율이라고 할 수 있는 커밋 히스토리를 바꾸는 능력을 가졌습니다. 이렇게 강력한 명령어인 만큼, 협업 과정에서 예기치 못한 혼란을 야기하기에 충분한 명령어이기도 합니다.

따라서, git rebase 명령어는 팀의 컨벤션에 맞게, 항상 공개적으로 사용하길 바랍니다.

copyright for git rebase

© 2023 All rights reserved.