git-revert: 기본 사용법 및 메커니즘 이해
본 포스트의 목적은 git-revert의 기본적인 사용방법과 메커니즘에 대해서 이해하는 것이다. 아래와 같은 의문들을 해결한다.
reverted commit은 git에서 어떻게 인식할까?
merge commit은 git-revert 할 수 있을까?
Introduction
현업에 근무하면서 git-revert 사용해본 일이 거의 없었는데, 이번 주에 revert하다가 커밋이 꼬여버린 케이스를 목격했다. 그 과정에서 아래 의문들이 떠올랐다.
reverted commit은 git에서 어떻게 인식할까?
merge commit은 git-revert 할 수 있을까?
의문을 해결하기 위해 아래 git-revert 공식 문서를 읽어보았다.
git-revert 기본 사용법
처음에는 revert가 git이 아닌 GitHub의 편의기능인 줄 알았다. GitHub PR 페이지에서 revert 기능을 제공하기 때문이다.
revert할 commit을 생성하기 위해 임의 PR을 만들어 merge 했다. 우측 하단의 revert 버튼을 누르면, git revert <merged commit>
과 같은 동작을 한다.
GitHub에서 이렇게 revert 브랜치 및 PR을 생성할 수 있으며, merge 시 해당 commit이 revert 된다.
해당 commit을 revert한 브랜치의 상태는 위와 같다. ea2d74f
commit을 revert했지만 해당 커밋은 여전히 위 브랜치에 존재한다. (다시 merge하려고 해도, diff로 취급되지 않는다.)
위의 예시는 squash commit으로 merge한 케이스인데, merge commit으로 해도 동일하다. 아래는 merge commit을 생성하고 revert한 후 diff를 확인한 결과이다.
즉, 해당 commit이 merge되었다는(해당 branch에 존재한다는) 정보 자체를 revert한 것이 아니다.
git-revert conflict
revert 하려는 커밋 이후의 커밋이 존재한다면 revert 시 conflict가 발생할 수 있다 (평소에 커밋을 잘 정리해놓으면 이런 경우 해결이 쉬워진다)
small tip: 위는 revert 중 conflict가 발생하여 reverting 상태인 화면이다. shell에 git 관련 설정을 해놓으면, 어느 브랜치에서 어느 상태(e.g. reverting)인지 알 수 있다. 이렇게 해놓으면 휴먼 에러를 꽤 많이 방지해준다.
git-revert merge commit
-m parent-number
--mainline parent-number
Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.
Reverting a merge commit declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge. This may or may not be what you want.
보통 merge는 revert 할 수 없다. merge의 어느 쪽에 mainline으로 취급해야하는지 모르기 때문.
merge를 revert하는 것은 merge에 의해 발생한 트리 변경사항을 되돌리고 싶다는 것이다.
merge commit은 2개의 parent를 가지기 때문에, revert하려면 mainline이 어느 쪽인지 지정해줘야 한다. -m의 m은 merge가 아니라 mainline의 약자다. 더 자세한 설명은 아래 링크에서.
git-log에서Merge: ea2d74f 3fe386b
라는 설명을 확인할 수 있다.
ea2d74f
는 main branch에 존재하던 commit, 3fe386b
는 merge된 branch의 commit이다.
쉬운 이해를 위해 아래 예시에서 merge commit을 revert해보겠다.
merge commit: da664c2
merge 전 main 브랜치의 마지막 커밋: 8907ae1
merge된 branch의 마지막 commit: fd90943
da664c2의 parent는 아래와 같다. parent number는 Merge:
에 순서대로 1,2,3,…
이다. (8907ae1이 parent 1,
git revert da664c2 -m 1
실행 시 아래 커밋 메시지가 준비된다
의미: 1은 8907ae1을 가리키고, 이 side를 mainline으로 취급하고 나머지 parent의 변경사항을 되돌린다는 것이다.
GitHub에서 merge commit을 revert하면 이 쪽에 해당한다.
git revert da664c2 -m 2
실행 시 Already up to date! 메시지가 출력된다.
이 쪽은 revert할 변경사항이 없기 때문이다.
git-revert: 여러 커밋
git revert <commit>…
git revert -n master~5..master~2
위와 같이 여러 커밋들을 revert 할 수 있다. 아래 예시를 보자.
revert 전
revert 후
git revert head…head~2
를 실행했다.
2개의 revert commit이 생겼다
이 쯤에서 처음의 의문들을 답변해보면,
reverted commit은 git에서 어떻게 인식할까?
git-revert는 commit 자체를 revert한다기보다는 해당 commit의 변경사항을 revert하는 것이다. revert 후에도 그 commit은 여전히 그 branch에 존재한다.
revert를 revert하고 싶다면, revert된 commit을 다시 merge할 수는 없다. revert commit을 다시 revert하는 방법은 가능하다.
merge commit은 git-revert 할 수 있을까?
CLI에서 실행 시 어느 커밋이 mainline인지 모호하므로 -m parent-number
옵션을 통해 지정해줘야 한다. (보통은 -m 1
로 지정하면 될 듯) GitHub에서는 알아서 처리해주는 듯하다.
Conclusion
git revert 기능을 git revert [-m parent-number] <commit>…
명령어로 사용하는 방법을 실제 실행 결과와 함께 소개했다.
git에서 reverted된 commit을 어떻게 인식하는지 소개했다. reverted commit은 여전히 그 branch에 남아있다.
revert된 변경사항을 다시 반영하고 싶으면, reverted commit을 다시 revert 하면 된다. 실제 git-revert 문제를 겪었을 때는 커밋이 꼬인 브랜치를 revert 전 상태로 force-push하여 해결했으나, force-push가 어려운 상황일 경우 이러한 방법이 유용하다.