-
Git으로 실수했을 때 살아남기팁 2021. 10. 3. 21:14
git add
,git commit
,git push
밖에 모르던 시절 처음 회사에서 일하기 시작하면서 git으로 많은 삽질을 했었다. 코드 리뷰도 없고 다른 사람도 신경 쓸 필요가 없으면 마구잡이로 푸쉬하면 되지만, 회사에서는 git을 통한 협업이 이루어지기 때문에 나 때문에 회사 리포지토리가 망가지지 않도록 해야 한다. 이미 실무에 익숙해진 개발자들은 git에 대해서 어려움을 겪을 일이 드물지만, 초보 개발자에게는 꽤나 무서울 수 있는 것이 바로 git이다. 과거에 git으로 수많은 삽질을 한 결과 알게 된 방법들을 정리해두려고 한다.1. 커밋을 수정하고 싶어!
1.1 커밋 메시지를 잘못 썼어! (`git commit --amend`로 커밋 메시지 고치기)
아주 간단한 상황이다. 이미 커밋을 해버렸는데 커밋 메시지를 잘못 쓴 경우이다. 이 경우에는
git commit --amend
를 통해 메시지를 수정해주면 된다.1.2 이 변경사항도 커밋에 포함되어야 하는데 빠뜨렸어! (`git commit --amend`로 커밋 내용 변경하기)
이미 커밋을 찍었는데, 추가 변경사항이 만들어졌고 이것이 기존 커밋에 함께 추가되는 것이 바람직한 상황이다. 이때도
git add
후git commit --amend
를 해주면 된다.(주의)
git commit --amend를
하면 commit id가 바뀐다. 이는rebase
,cherry-pick
등을 할 때도 마찬가지이다. git에서 무언가 확정적인 행동을 하면 전부 히스토리가 남고, 그때마다 id가 생긴다. 예를 들어 A(기존 상태) -> amend -> B(현재 상태)라고 하자. 이미 리모트에 A가 푸쉬되어 있는 상태라면, 그냥push
를 할 경우 이런 에러가 뜬다.(한국어) 현재 브랜치의 끝이 리모트 브랜치보다 뒤에 있으므로 업데이트가 거부되었습니다. (영어) ... the tip of your current branch is behind its remote counterpart...
remote에 있는 A를 현재 너의 브랜치에서는 모르고 있다는 메시지이다. 로컬에서는 B로 바꾸어버렸으니까 더 이상 A를 가지고 있지 않기 때문이다. 이 상황에서는
git push -f
를 해서 리모트에 있는 A를 B로 덮어써야 한다.그런데 이
-f
옵션은 조심해서 사용해야 한다. 커밋 히스토리를 바꿔버리기 때문이다. 만약 함께 쓰는 브랜치라면 함부로 커밋 히스토리를 바꾸는 일은 삼가야 한다. 이는rebase
도 마찬가지이다. 혼자 쓰는 브랜치라면 커밋 히스토리를 깔끔하게 유자하기 위해서merge
대신rebase
를 신나게 써도 되지만(히스토리를 깔끔하게 관리하는 것이 기본 정책인 팀에서는rebase
가 필수일 것이다), 함께 쓰는 브랜치의 히스토리를 바꾸면 히스토리가 꼬이게 되고 관리하기 힘들어진다.
(이 설명은 github 기준으로 작성되었다. Gerrit과 같은 시스템에서는 큰 문제가 되지 않을 수 있다.)만약 나 혼자만 쓰고 있는 브랜치라면
-f
옵션을 쓰는 것이 괜찮다. (가령 github에서는)코드 리뷰 중이라고 해도 동료가 달아준 코멘트를 여전히 pull request에서 확인할 수 있다. 리뷰를 반영한 변경사항을 원래의 커밋에 포함할지(-f
), 또는 별도의 커밋으로 찍을지(그 후에squash merge
를 할지의 여부도 별개로 결정해야 함)는 팀의 정책에 따르면 될 것 같다.2. 이전 commit에서 뭔가 실수를 했어! (`git rebase --i`로 HEAD가 아닌 commit 관리하기)
하나의 작업을 여러 개의 commit으로 나눠 작업하고 있는 상황이라고 하자 (한 커밋의 단위가 얼마나 되어야 하는가에 대해서는 의견 차가 있지만, 여기에서는 한 리뷰의 단위에서 여러 개의 commit으로 쪼개서 작업을 하는 상황을 가정한다). A(REMOTE HEAD) - B - C - D(LOCAL HEAD)인 상황이라고 하자. 이때
git commit --amend
로는 D는 수정할 수 있지만, A, B, C는 수정할 수 없다.이런 경우에는
git rebase --interactive
를 사용하면 A, B, C를 수정할 수 있다. (단,rebase
는 타겟 브랜치를 설정해줘야 한다. 특정한 브랜치과 비교하여 현재 브랜치가 가지고 있는 변경 사항이rebase
를 하는 대상이 된다.)git rebase -i
를 하면 다음과 같은 화면이 뜬다.두 번째 커밋을 수정하기 위해 다음과 같이
pick
을edit
로 수정하고 (vi일 경우):wq
를 통해 빠져나오자.필요한 수정을 한 뒤
git commit --amend
를 하고git rebase --continue
를 하면 다시 HEAD로 돌아온다.git log
를 통해 두 번째 커밋이 변경되었음을 확인할 수 있다.위에서 언급했고 위 스크린샷에서도 확인할 수 있듯이,
rebase
는 commit id를 변경한다는 점에 주의해야 한다.3. 물은 이미 엎질러져 버렸는데... 어떻게 수습하지?
이것저것 수정하고 왔다갔다 하다가 "아 이거 망했는데..?" 싶은 상황이 올 수 있다. 근데 사실 걱정할 일이 없다. git에서는 웬만한 건 다 복구 가능하다. (.git 폴더를 아예 없애지 않고서야...) 대부분의 수습에는
reflog
,cherry-pick
,reset
이 요긴하게 쓰인다.git reflog
는 그야말로 치트키이다. git 관련 내가 했던commit
,merge
,rebase
,cherry-pick
,reset
, 브랜치 바꾸기 등의 기록을 다 보여준다. 다음은git reflog
화면이다. 내가 스크린샷을 찍기 위해서 이런 저런 실험을 해보고 변경한 과정이 다 나타나있다. commit id를 확인할 수 있기 때문에reset
과cherry-pick
등을 할 때 편리하다.다음과 같은 문제 상황을 생각해볼 수 있을 것 같다.
3.1 잘못된 브랜치에서 작업했어... (
git cherry-pick
을 이용해 커밋 가져오기)이 브랜치에서 작업하고 싶은 것이 아니었는데, 이미 작업을 해버렸다면? 나는 보통 이 상황에 일단 커밋을 찍는다. 그리고 새 브랜치를 판 다음,
git reflog
를 통해 방금 찍은 커밋의 id를 복사해서cherry-pick
해 온다.3.2 여기에 포함될 변경사항이 아니었는데 커밋까지 해버렸어! (
git reset
을 통해 커밋 해제하기)커밋을 안 한 상태라면
git status
를 했을 때 git에서git restore
하라고 친절하게 알려주지만, 이미 커밋을 해버렸다면 어떻게 방금 실수한 변경사항을 뺄 수 있을까? 이 때는git reset
을 한 뒤 원하는 변경만 다시 커밋하면 된다.3.3 그냥 다...행복했던 과거로 되돌리고 싶어.. (
git reset --hard
를 통해 아예 변경하기)이럴 때에는 되돌아가고 싶은 상태를
git reflog
에서 찾은 다음,git reset --hard <commit-id>
를 통해 그 상태로 돌아갈 수 있다.git reset
의 옵션에는 3가지가 존재한다. A - B - C(HEAD)와 같은 상황이라고 가정할 때 각 옵션의 의미는 다음과 같다.git reset --mixed B
를 하면 HEAD는 B로 이동하고, C에 해당하는 변경사항은 unstaged 상태로 존재한다. 파일을 열어보면 C에 해당하는 변경사항은 확인할 수 있지만, git에 의해 추적되는 상태는 아닌 것이다.--mixed
는 디폴트 옵션이다.git reset --soft B
를 하면 HEAD는 B로 이동하고, C에 해당하는 변경사항은 (추적은 하고 있지만) 커밋이 되지 않은 상태로 변경된다.git reset --hard B
를 하면 HEAD가 B로 이동하고, C에 해당하는 변경사항이 아예 사라진다. B 이후의 변경사항이 전혀 필요없을 때 사용하는 방법이다. (물론git reflog
를 통해 언제든 C의 내용을 다시 불러올 수 있다.)
참고로
git reset
의 목표로 commit id를 사용할 수도 있고,HEAD~1
(HEAD보다 한 커밋 전으로),HEAD~2
(HEAD보다 두 커밋 전으로)와 같이 상대적인 표현을 쓸 수도 있다.4. 변경사항을 잘 지우고 싶어! (
git revert
,git rm
을 활용하기)사실 지우는 방법을 몰라서 고민하는 경우는 거의 없겠지만, 그래도 추천하는 방법을 적어두려고 한다.
어떤 한 커밋에 해당하는 변경 전체를 지우기 위해서는
git revert
를 사용한다. 이렇게 해야 실수할 일이 없고, 작업의 의도를 확실하게 남길 수 있다.파일을 지울 때에는
git rm
을 사용한다. 물론 그냥rm
을 하고git add
를 할 수도 있다. 하지만rm
으로 지워버리면 실수로 파일을 지웠을 때 파일을 다시 복구하기 위해서git reflog
를 사용해야 하는 불편함이 있다. 파일을 지운 후 많은 히스토리가 쌓여서 원하는 시점을 못 찾는 경우가 있을 수도 있으니 곤란해질 수도 있다. 심지어 git으로 추적하고 있지 않은 파일을 지워버리는 경우...휴지통에 있기를 바라야 할 것이다. 이는git rm
으로 지웠으면 애초에 하지 않았을 실수이다.5. 다음에는...
이번에는 각 문제 상황을 만났을 때 해결하는 방법에 대해서 다루었다. 다음에는 널리 쓰이는 브랜치 전략,
merge
/rebase
/squash
전략, git에 대한 기술적인 이해 등에 대해서도 다뤄보고 싶다.'팁' 카테고리의 다른 글
git으로 협업하는 방법 (0) 2021.10.16 Intellij 폰트 색깔 바꾸기 (Intellij color scheme) (0) 2021.08.25