💻

[기술노트] git reset, revert

날짜
2021/10/05
작성자
오동나무🌳
태그
git
reset
revert
git을 사용하다보면 예상하지 못한 문제를 마주하는 일이 꽤나 빈번하다. 그 중에서도 이전 커밋으로 되돌아가고 싶은, 혹은 꼭 그래야하는 상황도 많이 마주치게 된다. 이때 git reset이나 revert 명령어를 사용하여 커밋을 되돌릴 수 있다. 최근에 새로 들어온 캠퍼들의 질문으로 git revert를 다시 돌아보게 되었는데 내가 revert 명령어를 거의 써본 적이 없다는 것을 인지하게 되었다. 나는 그 동안 일말의 conflict도 겪고싶지 않은 마음에 위험을 감수하면서도 reset을 사용해왔다. (물론 push도 force push로 진행해왔다. 반성한다.) 물론 이러한 선택은 revert에 대한 이해가 부족했기 때문이기도 하다. git과 어느정도 친해졌다고 생각했는데.. 내 착각이었다.
reset과 revert는 모두 커밋을 이전으로 되돌리는 명령어이다. 어떻게 다를까? reset은 커밋 내역을 남겨두지 않고 모두 삭제하며 이전으로 돌아가는 것이고, revert는 커밋 내역을 삭제하지 않은 채 이전으로 돌아가는 것이다. 아주 쉽게 예를 들어보자. 우리는 2021년에 비트코인 가격이 8천만 원을 넘어간다는 사실을 알고 있다. 아마 당시 차트를 보며 "타임머신을 타고 과거로 돌아갈 수 있다면.." 하며 행복한 상상을 해본 사람이 나뿐만은 아닐 거다. 지금이라도 시간을 돌릴 수 있다면 비트코인을 싸게 살 수 있을 때로 돌아가지 않을까 싶다. 좋다. 지금부터 과거로 돌아갈 수 있다는 행복한 상상을 해보자! 이때 당신은 reset으로 돌아갈 것인가? revert로 돌아갈 것인가?
reset으로 돌아가면 돌아간 시점 ~ 현재(돌아가기 전)까지의 기억(commit)을 모두 삭제해버리는 것이다. 그냥 과거로 돌아가서 그 시점부터 다시 인생을 사는 것이다. 물론 reset 명령어도 종류가 몇 가지 있어서 조건이 다를 수는 있지만 말이다. revert로 돌아가는 것이 아마 현명한 선택이다! revert로 돌아가게 되면 모든 기억(commit)을 그대로 가지고 과거로 돌아갈 수 있다. 다만 돌아간 시점과 남아있는 나의 기억들 사이에서 일어나는 충돌(conflict)은 감수해야 할 것이다. 억만장자가 될 수 있는데 그 정도 충돌쯤이야. 이제 다시 현실로 돌아오자. 아래에서는 reset과 revert에 대해 좀 더 자세하게 이해해보도록 하겠다.
"나, 다시 돌아갈래!!" - 박하사탕(2000)

🌳 git reset

reset 명령어는 커밋 내역을 삭제하면서 이전의 커밋으로 돌아가는 명령어이다. 위의 그림처럼 commit 4️⃣에서 commit 2️⃣로 돌아간다면 commit 3️⃣과 4️⃣는 삭제되는 것이다.
원하는대로 이전 커밋으로 돌아갔고, commit 기록도 깔끔해져서 문제가 없어보이기도 하지만 reset 명령어로 돌아가는 과정에서 삭제된 커밋은 복구할 수 없다는 치명적인 단점이 존재한다. 따라서 reset을 해주기 전에는 각별히 신중할 필요가 있다. 협업이나 중요한 프로젝트라면 더욱더! reset 명령어에도 몇 가지 옵션이 있다. 어떤 옵션으로 reset을 시키냐에 따라 다른 결과를 얻을 수 있다. 하나씩 살펴보도록 하자. 좀 더 완전한 이해를 하고 싶다면 글을 보며 직접 여러 실험을 진행해보는 것이 좋겠다.
Working Directory는 현재 작업 중인, 작업한 파일의 내용을 의미하고 Staging Area는 git add 명령어로 commit의 관리 대상으로 올라간 상태를 말한다. 더 이해가 필요하다면 git book 문서를 읽어보는 것도 추천한다.
1) git reset . - Working Directory 유지 - Staging Area 삭제 2) git reset <commit번호> - <commit번호>버전으로 돌아가기 - Working Directory는 유지 - Staging Area 삭제 3) git reset --hard <commit ID> - <commit번호> 버전으로 돌아가기 - Working Directory는 삭제 - Staging Area 삭제 4) git reset --soft <commit ID> - <commit번호> 버전으로 돌아가기 - Working Directory는 유지 - Staging Area에 돌아간 돌아간 시점 ~ 현재까지의 작업 내용이 추가됨
Swift

1) git reset .

이 명령어는 Staging Area에 add된 내용을 삭제하는 명령어이다. 이때 작업했던 Working Directory(파일, 내용들)은 삭제되지 않는다. 실수로 git add를 해서 Staging Area에 의도대로 올라가지 않았을 때 꽤 자주 사용하게 되는 명령어이다.
git reset . 명령어를 입력하면 Staging Area에 올라갔던 test.txt의 변경 사항이 삭제된다.
💡
git checkout . 이 명령어를 사용하면 가장 최근의 커밋 상태로 돌아간다. 즉 Working Directory를 가장 최신 커밋 버전으로 돌리는 것이다. 마지막 커밋 이후에 내가 작업했던 내용을 모두 지우는 용도로 자주 사용하게 된다. 물론 작업 내용이 지워지는 것이므로 중요한 작업 중이라면 신중하게 사용하자!

2) git reset <commit ID>

<commit ID> 버전으로 돌아가는 동시에 이후의 커밋 내역은 모두 삭제된다. 이때 Working Directory는 그대로 유지되고, Staging Area는 빈 상태로 돌아가게 된다. 아래의 사진에서 왼쪽은 text 파일의 내부이다. Working Directory가 그대로 유지된다는 말은 이전 commit으로 되돌아가더라도 text 파일의 내용은 그대로 유지된다는 말이다.
사진 1-1
이렇게 4개의 커밋이 쌓여있다고 했을 때 git reset <commit ID> 명령어로 2번째 커밋으로 돌아간다면 어떻게 될까? Working Directory(작업 내용)은 그대로 남아있고, Staging Area 역시 비어있을 것이다. 아래의 사진은 git reset 명령어를 입력해 commit 2️⃣로 되돌아간 후의 상태이다. 텍스트 파일에는 1, 2, 3, 4가 모두 남아있지만 commit은 1, 2만 남아있는 상태이다. 또한 Staging Area 역시 비어있다.
커밋은 2로 되돌아간 상태. commit 3, 4는 날아가서 없어진 상태이다.

3) git reset —hard <commit ID>

hard라는 단어처럼 강력하게 reset이 이루어지는 명령어이다. 별다른 옵션 없이 reset 명령어로만 되돌아간다면 위에서 확인한 것처럼 Working Directory는 그대로 남아 있다. reset hard 명령어를 입력하면 커밋이 되돌아감을 물론, Working Directory까지 모두 삭제된다. 아래 사진을 보면 텍스트 파일에서도 3번째, 4번째 줄의 텍스트가 사라져있다.

4) git reset —soft <commit ID>

reset hard를 이해했다면 soft도 감이 잡힐 것이다. reset soft는 이전의 커밋으로 돌아가는 것은 같지만 Working Directory가 그대로 유지되며, Staging Area에 작업 내용이 올라간 상태로 돌아간다. commit 4️⃣에서 2️⃣로 돌아왔지만 Working Directory와 Staging Area에는 4️⃣에 대한 내용이 그대로 남아있기 때문에 곧바로 commit 4️⃣의 상태로 커밋이 가능하다. 다만 commit 3️⃣은 이미 날아갔기 때문에 복구가 불가능하다.

🌳 git revert

revert는 커밋 이력을 삭제하지 않고, 남겨둔채로 돌아오는 것이다. 엄밀히 말하면 커밋을 없던 일로 해줄테니 다시 커밋해. 라는 명령어이며 실질적으로 이전으로 되돌아가기 + 커밋하기 기능을 한다. 이렇게만 보면 reset보다 안전해 보이고 좋아보이기도 한다. 하지만 revert는 충돌(conflict)이 동반할 수 있다는 점에서 주의가 필요하다. 이전으로 되돌아왔지만 돌아온 시점 ~ 돌아오기 전 사이에 무시되는 커밋이 존재하기 때문이다. 없던 일로 해준다고는 했지만 실질적으로는 둘이 원만하게 해결해. 라는 식이다. 물론 충돌이 일어나지 않게 revert하는 방법도 있다. 돌아온 시점 ~ 돌아오기 전 사이에 커밋이 없는 경우이다. 이 부분은 아래에서 다시 다루도록 하겠다.
revert를 해줄 경우 다음 커밋은 commit 4️⃣ 다음으로 쌓인다. 커밋의 흐름이 부자연스럽게 기록되는 것처럼 보일 수도 있는데 안전한 관리를 위해서는 불가피한 선택이다. 커밋 이력이 reset보다는 깔끔하게 남지 않지만 중요한 프로젝트나 협업을 할 때에는 커밋을 삭제해버리는 reset은 너무 위험해 보인다.

1) git revert <commit ID>

commit 4️⃣에서 2️⃣로 revert를 해보자. 어떤 일이 일어날까?
CONFLICT가 발생했다. 언제 만나도 반갑지는 않은 녀석이다. 충돌이 일어나는 이유는 앞서 설명했듯이 commit 3️⃣, 4️⃣ 이력이 남아있기 때문이다. 둘 사이를 원만하게 해결해주어야 한다.
그런데 텍스트를 주목해보자. 분명히 commit 2️⃣로 돌아가고 싶었는데 충돌의 흔적을 보면 1. 첫 번째 커밋이라는 텍스트만 남아있다. 처음에는 commit 2️⃣로 돌아갔다면 2. 두 번째 커밋이라는 텍스트까지 충돌이 일어나야하는게 아닌가? 생각이 들었다. 다시 revert의 의미를 곱씹어 보자. revert는 커밋을 없던 일로 해줄테니 다시 커밋해. 라는 명령어이다. 즉, commit 2️⃣로 되돌아간다는 의미가 아니라 commit 2️⃣를 없던 일로 해주겠다는 명령어인 것이다. 그렇다면 납득이 되는 결과이다.
충돌을 해결하고 다시 커밋해보면 모든 커밋 이력이 그대로 남아있고 그 위로 새로운 커밋이 쌓이는 것을 확인할 수 있다. 충돌을 해결한다면 reset보다 더 안전하게 관리가 가능한 것이다.

2) 충돌이 일어나지 않게 revert하기

위에서 언급했듯 충돌이 일어나지 않게 revert를 하는 방법도 있다. revert를 할 때 충돌이 일어나는 이유를 다시 생각해보자. 돌아온 시점 ~ 돌아오기 전 사이에 commit들이 존재하기 때문이다. 즉, commit 2️⃣로 돌아왔지만 3️⃣, 4️⃣는 여전히 남아있기 때문이다. 그렇다면 그 사이에 commit이 없다면 어떻게 될까? 충돌이 일어나지 않는다. commit 4️⃣에서 revert commit 4️⃣를 해주는 것이다. 그러면 commit 4️⃣가 없던 일이 되는 동시에 그 사이에 다른 커밋이 없으므로 충돌이 일어나지 않는다.
commit 4️⃣가 없던 일이 되었다. revert 명령어를 입력하면 중간에 commit 메세지를 설정하는 단계가 있다. 사진에선 생략.
💡
그러나 commit 메세지를 수정하기 위해서 revert를 사용하지는 말자. git commit —amend 라는 유용한 명령어가 있다.

3) git revert <commit ID>..<commit ID>

그렇다면 commit 2️⃣의 상태까지 reset을 사용하지 않고, 충돌을 피하면서 되돌아갈 수 있을까? 위의 방법을 이용해서 한 단계씩 뒤로가는 방법을 생각해볼 수 있다. revert 4️⃣, revert 3️⃣을 순서대로 진행해주면 원하는 상태까지 안전하게 되돌아갈 수 있을 것이다. 그렇다면 하나하나 명령어를 입력하면서 돌아가야하는 걸까? 2개의 커밋 정도만 돌아가는거면 무리가 없겠지만 꽤 많은 커밋을 되돌려야 한다면? 그때는 git revert <commit ID>..<commit ID> 를 사용해볼 수 있다. 아래의 사진은 git revert <commit 2️⃣>..<commit 4️⃣> 명령어를 수행한 결과이다. 물론 충돌은 일어나지 않았다.
revert 4️⃣, revert 3️⃣에 대한 커밋 메세지를 순차적으로 수정해주어야한다.
내 예상은 git revert <commit 3️⃣>..<commit 4️⃣> 를 입력해야 3️⃣, 4️⃣가 없던 일이 될 줄 알았는데 이렇게 입력할 경우 commit 4️⃣만 없던 일이 되었다. 그래서 commit 2️⃣의 상태로 돌아가고 싶다면 git revert <commit 2️⃣>..<commit 4️⃣> 명령어를 입력해야한다.

4) git revert —no-commit <commit ID>

git revert --no-commit <commit ID> // no 앞에는 이중 대쉬 '--'가 사용된다. 노션에서 표현이 안되어서 주석처리.
Swift
이 명령어는 revert를 할 때 자동으로 commit이 되지 않도록 하는 명령어이다. commit은 되지 않지만 Staging Area에는 변경사항이 올라가는 상태로 되돌아간다. 하지만 충돌이 일어나는 revert를 실행한 경우에는 당연히 유효하지 않은 옵션이다.

🌳 언제 무엇을 써야할까?

reset과 revert의 사용에 대해서는 어느정도 이해가 된 것 같다. 그렇다면 언제 어떤 명령어를 써야할까? 개인적으로 내린 결론이므로 사람마다 생각이 다를 수 있다. 스스로 모든 걸 책임질 수 있다면 reset을 사용해도 괜찮다. 만약 그렇지 않으면 revert를 쓰자. 스스로 책임질 수 있는 상황이라면 혼자 소소하게 진행하는 개인 프로젝트라던지, 정말 대수롭지 않은 commit만 쌓여있어서 실수로 날려먹더라도 스스로의 힘으로 원래대로 복구가 가능한 수준이라던지 하는 상황을 말한다. 그러나 원격 저장소를 이용하거나 협업을 진행할 경우에는 위험할 수 있다. 작업중인 branch를 혼동하여 잘못 reset 시킬 경우에는 정말 곤란한 상황이 발생할 수도 있다. 또한 reset을 사용하면 force push를 해야하는 경우도 늘어나게 될텐데 이는 결코 좋은 습관이 아닌 것 같다. 무엇이든 연습은 실전처럼 해야하지 않을까. 앞으로 revert를 즐겨 쓸 수 있도록 하겠다.

글을 마치며

Swift나 iOS, CS와는 달리 유독 git에 대해서는 호기심이 많이 생기지는 않았던 것 같다. 아마 마음 속의 우선 순위에서 밀려서 그랬지 않았나 싶다. 열심히 코드를 작성하고, 프로젝트를 진행하더라도 관리를 못하면 꽝이다. 여러 git 커맨드에 익숙해져서 빠르고 정확하게 프로젝트를 관리해보자. 최근에는 야곰 아카데미에 새로운 캠퍼들이 들어왔다. 새로운 캠퍼들과 소통하면서 나도 역시 부족하구나, 배울 점이 정말 많구나 하는 것을 새삼 깨닫는 요즘이다. 배울 사람이 늘어나서 좋다. 늘 배울 수 있음에 감사하다.
#git #reset #revert #commit #commit 되돌리기 #conflict #충돌 #reset revert 차이
TOP