git rebase -i 사용하기

Git을 사용하다보면, 이미 커밋한 히스토리를 변경 또는 삭제하고 싶은 경우가 자주 발생한다. 이때 사용할 수 있는 명령어가 바로 $ git rebase -i이다.
-i--interactive 의 약어로 말그대로 git rebase 명령어를 대화형으로 실행하겠다는 의미이다.

$ git rebase -i [수정을 시작할 커밋의 이전 커밋]

이렇게 명령어를 실행하면 "수정을 시작할 커밋의 이전 커밋" ~ "현재 커밋(HEAD)" 범위에 있는 모든 커밋들의 리스트가 출력된다. 예를 들어 $ git rebase -i HEAD~3 를 실행하면 HEAD~2, HEAD~1, HEAD 커밋들이 출력된다.
사용자는 rebase 하기 전에 그 리스트를 입맛에 맞게 직접 수정함으로써 이전 커밋들의 히스토리를 변경할 수 있다.

테스트를 위해 미리 커밋을 4개 작성하였다.

$ git rebase -i HEAD~3 명령어를 실행하면 아래와 같은 에디터 창이 나타난다.

에디터 창에는 커밋들의 리스트와 사용할 수 있는 명령어들에 대한 설명이 주석으로 출력된다. 비록 모두 에디터로 편집이 가능한 텍스트들이지만, 아래 주석을 제외한 상단의 텍스트들은 단순 텍스트 이상의 의미를 지니고 있다.

이제 본격적으로 각 명령어들의 기능에 대해 하나씩 알아보기로 하자.

pick

pick 은 커밋을 사용하겠다는 의미이다. 이를 이용해서 커밋의 순서를 바꿀 수도 있고, 커밋의 해쉬값을 이용해 특정 커밋을 가져올 수도 있다.

기본적으로 pick으로 설정되어있기 때문에 아무것도 변경하지 않고 종료한다면, 커밋에 대해 어떤 변경도 일어나지 않는다.

위처럼 2번째, 3번째 줄의 커밋의 순서를 바꾸고 저장 후 종료하면,

실제 커밋 히스토리에서 커밋의 순서가 변경된 것을 확인할 수 있다. 단순히 커밋의 순서만 변경된거지만, 커밋 해쉬값들 역시 변경된 것을 볼 수 있다.

특정 커밋을 가져오는 것을 테스트하기 위해 커밋을 추가로 하나 생성하였다.

그리고 $ git reset 명령어를 이용해 커밋을 되돌렸다.

$ git reset --hard HEAD^

$ git rebase -i를 이용해 이 커밋을 다시 가져와보자.

앞서 생성했던 커밋의 해쉬값과 커밋 메시지를 중간에 삽입하였다.(커밋 해쉬값이 기억이 나지 않는다면 $ git reflog 명령어를 통해 알아낼 수 있다.)

저장 후 종료하면, 커밋 히스토리와 실제 working directory에도 반영이 되었음을 확인할 수 있다.

reword

reword 는 커밋 메시지를 변경하는 명령어이다.

커밋 메시지를 변경할 커밋 앞에 reword 명령어를 쓴 후 저장하면, 해당 커밋의 메시지를 다시 작성하는 에디터 창이 열린다. -i"대화형" 명령어 옵션이라고 했던 의미가 이제서야 체감될 것이다.

에디터 창에서 커밋 메시지를 변경한 후, 커밋 히스토리에서 결과를 한번 확인해보자.

이전 커밋 메시지만 변경할 때는 간단히 $ git commit --amend 를 이용해도 된다.

edit

앞서 설명한 reword는 커밋 메시지만 변경하는 명령어였다면, edit 은 커밋 메시지 뿐만 아니라 커밋의 작업 내용도 변경할 수 있는 명령어이다.

저장 후 종료하면, 변경할 커밋으로 checkout 된다. 그 상태에서 변경할 작업을 수행하면 된다.

나는 lion.txt 를 삭제한 후, tiger.txt 를 생성하였다.

변경한 사항들을 아래처럼 해당 커밋에 반영하면 된다.

$ git add lion.txt
$ git add tiger.txt
$ git commit --amend

커밋 메시지도 변경하고 싶다면 에디터 창에서 변경하면 된다. 커밋이 성공적으로 수행되었다면, 이제 마지막으로 아래 명령어를 실행하면 된다.

$ git rebase --continue

squash

squash 은 해당 커밋을 이전 커밋과 합치는 명령어이다.

저장 후 종료하면, 커밋 메시지를 수정할 수 있는 에디터 창이 뜬다. 합쳐질 커밋들의 메시지를 확인한 후, 그대로 종료하면 이전 커밋과 하나로 합쳐졌음을 확인할 수 있다.

fixup

fixupsquash 와 동일하게 해당 커밋을 이전 커밋과 합치는 명령어지만, 커밋 메시지는 합치지 않는다. 결과적으로 이전 커밋 메시지만 남게 된다. 그 점만 빼면 완벽히 앞의 예제와 동일하므로 예제는 생략하도록 하겠다.

exec

exec 를 이용하면, 각각의 커밋이 적용된 후 실행할 shell 명령어를 지정할 수 있다.

각각의 커밋이 적용된 후 실행된다는 것을 확인하기 위해 커밋 중간중간에 $ git rev-parse HEAD 명령어를 삽입하였다. 이 명령어는 HEAD가 가리키고 있는 커밋의 해쉬값을 출력해준다.

저장 후 종료하면, 아래와 같이 터미널에 출력되는 것을 확인할 수 있다.

drop

drop 은 커밋 히스토리에서 커밋을 삭제하는 명령어이다.

커밋이 삭제됨을 확인할 수 있다.

사실 그냥 커밋 한 줄을 통째로 지워도 동일한 결과를 얻을 수 있다.

주의사항

$ git rebase 는 이전의 커밋 히스토리를 변경하기 때문에 항상 주의해서 사용하여야 한다. 만약 이미 GitHub과 같은 원격 저장소에 push까지 한 커밋이라면 변경한 커밋들은 원격 저장소에 push되지 않을 것이다.
이 때 $ git push --force 또는 $ git push -f 명령어로 강제로 원격 저장소에 커밋 히스토리를 덮어쓸 수도 있다. 하지만 만약 이전에 push한 커밋들을 다른 개발자들과 공유하고 있었다면 커밋 히스토리의 불일치가 발생해 흔히 말하는 "git이 꼬였다." 하는 상황이 발생할 수 있다.
따라서 협업 시 브랜치를 공유하는 상황에서 $ git rebase 는 가급적이면 지양하여야 한다.