[GIT] 깃(Git) 커밋(Commit) 취소하기 (ft. amend/reset/revert)
GIT을 사용하다 보면 커밋을 덮어쓰거나(amend), 취소하거나(reset), 되돌려나(revert) 하는 상황이 발생할 수 있다.
각 명령어를 사용하는 방법에 대해 알아보고자 한다.
git add 취소하기
제목이랑 좀 연관이 없을 수 있는데, 그래도 commit을 하기 위해서는 add를 먼저 해야 하기 때문에 add 취소에 대해 먼저 알아보도록 하자.
현재 아래와 같은 상황이라고 가정해보자.
git은 친절하게도 취소하는 방법에 대해 어느 정도 설명을 해주고 있다.
현재 git staged 상태의 a.txt를 취소하기 위해서는 git restore --staged <file>을 입력하라고 안내하고 있으며,
staged area에 없는 b.txt를 취소하기 위해서는 git restore <file>을 입력하라고 쓰여있다.
여기서 a.txt 와 b.txt를 대상으로 "취소"한다고 하는 의미가 다르다.
우선 a.txt를 취소해보도록 하자.
# git restore --staged <file>
$ git restore --staged a.txt
--staged 옵션에서 유추할 수 있듯이 staged area에서만 즉, commit 대상에서만 취소하는 것이다.
그럼 다시 한번 a.txt 와 b.txt를 취소하면 어떻게 될까?
# git restore <file>
$ git restore .
git status에서도 아무것도 출력이 안 되는 것을 볼 수 있다.
본인이 작업한 것들이 다시 원상복구 됐다고 생각하면 된다. --staged 옵션을 사용하지 않으면 변경사항은 전부 취소된다.
물론 여기서 "변경사항"이라고 말한 것은 새롭게 추가된 파일에 대해서는 이러한 취소가 안된다는 점이다.
(그냥 지워주는 게 빠를 것이라 생각된다.)
커밋 덮어쓰기(amend)
커밋을 덮어쓰는 방법에 대해 알아보자.
이 방법은 이미 존재하는 커밋에 좀 더 추가적으로 커밋을 해야 할 필요가 있거나 메시지를 수정하고자 할 때 사용하는 방법이다.
예를 들어 커밋을 했는데 실수로 잘못 수정해서 커밋을 했다거나, 빼먹은 파일이 있다거나, 메시지를 잘못 입력해서 커밋이 됐을 경우 사용한다고 생각하면 된다.
실제로 한번 써보면서 사용법을 익혀보자.
현재 이런 상황이다 분명 commit을 d.txt와 a.txt, b.txt, c.txt 4개의 파일을 커밋을 했어야 했는데, 실수로 a.txt와 b.txt만 커밋을 하였다.
이럴 경우 해당 커밋에 --amend 옵션을 통해 덮어씌울수가 있다.
우선 git add . 을 통해 전부 staged area에 추가해주자.
그리고 이제 commit --amend옵션을 사용할 것이다.
# git commit --amend -m "new message"
# -m 메시지 옵션은 생략이 가능하다. (기존 메시지를 그대로 사용)
$ git commit --amend -m "new message"
git show를 통해 좀 더 상세한 내용을 보자면 c.txt와 d.txt가 새롭게 추가되었다.
그리고 commit 메시지 또한 new message라고 변경된 것을 볼 수 있다.
이런 식으로 덮어 씌운다고 생각하면 된다.
여기서 하나 주의할 점은 만약 이미 commit 한 대상 예제에서는 b.txt라고 생각해보면, 현재 commit 되어 있는 상태가 아닌 안에 있는 내용들을 전부 수정해서 다시 --amend 옵션을 사용할 경우 새로운 내용으로 덮어 씌운다는 점이다.
--amend 옵션은 새롭게 추가하고 변경하는 게 아니라 "덮어쓰기"라는 점을 염두에 두길 바란다.
커밋 취소하기(reset)
reset은 단어 그대로 취소하는 명령어라고 생각하면 된다.
특정 커밋의 위치까지 커밋된 내용을 전부 취소하는 명령어라고 생각하면 된다.
reset의 성격에 따라 옵션이 있는데, soft/mixed/hard라는 옵션을 가지고 있다.
자 우선 위에서 사용했던 깃 로그를 그대로 살펴보자.
이런 식의 구조로 되어있었는데, 아무리 생각해도 저 new message의 커밋이 마음에 안 든다.
그래서 저걸 git reset 명령어를 통해 되돌려 보자.
# git reset [--soft/--hard/--mixed] "HEAD^"
# git reset [--soft/--hard/--mixed] HEAD~1
# git reset [--soft/--hard/--mixed] <HASH>
$ git reset 87602ee
git reset을 사용하는 방법에는 여러 가지가 있는데,
- "HEAD^" / "HEAD^^" : ^는 한 단계 앞을 의미하며, ^^는 두 단계 앞을 의미한다. (이 표시를 사용하고자 한다면 꼭 쿼터로 감싸서 사용해야 한다. cmder에서 이 명령어를 사용하게 되면 more?이라고 출력되는데 아무래도 ^라는 기호를 다르게 인식하기 때문인 것으로 보인다.)
- ~숫자 : ~1을 사용할 경우 최신 커밋을 reset 한다는 의미이다. 1말 고도 다른 숫자를 넣을 수 있다.
- HASH : 커밋 고유 HASH를 사용해 해당 커밋의 상태까지 reset을 한다. 즉, 기준을 정한다고 생각하면 된다.
지금 위 예제 코드에서는 git reset 87602ee와 git reset HEAD^ 그리고 git reset HEAD~1이 모두 같은 의미를 가진다고 보면 된다. 최신 커밋 하나만 reset을 하였으니.
그리고 여기서 reset 옵션에 대해 좀 더 알아보자면 다음과 같다.
- --soft : 커밋에 사용된 것들을 전부 보존하며, staged 상태로 되돌린다. (add 상태)
- --mixed : 생략 시 기본 옵션이며, 커밋에 사용된 것들을 전부 보존하고, unstaged 상태로 되돌린다. (add 이전 상태)
- --hard : 커밋에 사용된 것들을 전부 "삭제"한다. 즉, 없었던 일로 만든다고 생각하면 된다. (되도록 사용하지 않는 걸 권장한다.)
실제로 해당 옵션들을 하나씩 사용해보자.
git reset --soft
위에서 설명한 것처럼 git reset --soft 옵션을 사용하면 staged 상태로 되돌린다.
$ git reset --soft HEAD~1
git reset --mixed
git reset --mixed 옵션의 경우 생략할 경우 기본적으로 적용된 옵션이다. 그리고 unstaged 상태로 되돌린다.
$ git reset HEAD~1
git reset --hard
git reset --hard 옵션은 말 그대로 commit 자체를 없던 일로 만들어 버린다.
$ git reset --hard HEAD~1
커밋 되돌리기(revert)
위에서 지금까지 설명했던 git reset을 사용하게 되면 만약 해당 커밋이 원격 저장소에 있는 경우 서로 커밋 상태가 틀어져 버리기 때문에 난처한 문제가 발생할 수 있다.
그래서 git reset의 경우에는 로컬에서만 작업을 실시할 때 주로 사용해주면 되며,
만약 원격 저장소에 저장된 상태의 커밋을 취소하고 싶다면 그때 git revert를 사용하면 된다.
git revert는 git reset과 다르게 커밋 이력 자체를 지운 다기보다는, 해당 커밋에서 사용된 것들을 취소한 새로운 커밋을 만든다고 보면 된다. 무슨 말인지 직접 해보면서 알아보자.
# git revert HEAD
# git revert <HASH>
$ git revert HEAD
git revert를 사용할 때 주의할 점은 git reset과 혼동하면 안 된다는 점이다.
git revert는 딱 해당 HEAD만을 가리키기 때문에 최근의 commit을 취소하고자 한다면 git revert HEAD를 사용해야 한다. (git reset의 경우에는 git reset HEAD~1을 사용했다.)
git revert는 위 예제처럼 특정 커밋을 취소하고 취소한 것을 그대로 커밋으로 남겨버린다.
예제를 보면 지금 git revert HEAD의 로그를 보면 총 4개의 파일을 대상으로 revert가 되었는데, 이는 이전 글에서 사용했었지만 3개의 파일을 수정하고 1개의 파일을 새롭게 추가한 커밋이 "new message" 커밋이다.
그래서 로그를 보면 "new message" 커밋에서는 d.txt를 추가했는데, 이를 취소하기 때문에 delete d.txt 로그가 출력되는 것이다. 실제로 보면 d.txt가 삭제됐는데, 이는 직접 해보면서 확인하는 걸 추천한다.
그리고 저 git revert를 실제로 사용해보면 알겠지만 자동 커밋이 되어버리는데, git revert --no-commit 옵션을 추가해 자동 커밋을 안되게 할 순 있지만 또 다른 이슈 발생을 방지하기 위해 해당 옵션은 사용하지 않는 걸 권장한다.
추가적으로 git revert를 사용할 때 충돌이 날 수 있는데, 이는 git rebase 충돌 해결 방식이랑 비슷하게 충돌을 해결해주고 git revert --continue를 해준다거나 git revert --abort로 그냥 취소할 수도 있다.
참고: https://backlog.com/git-tutorial/kr/stepup/stepup7_3.html