삽질기록 #5 git detached HEAD

    (2022.02.25)

    오늘도 또 깃!

    이렇게 애매하게 알고 있다 보면 분명히 언젠가 큰 사고를 칠지도 모른다는 생각을 하면서... Pro Git을 조금씩 읽어 봐야겠다고 생각했다.

     

    나는 마지막 커밋으로 돌아가고 싶었을 뿐인데

    오늘의 삽질은 rebase 같은 아이가 아니었다. 생각해 보면 아주 단순하고 당연한 부분에서 삽질을 했다. 페어 프로그래밍을 하다가 뭔가 꼬여서 지금까지 쓴 내용을 날리고 최종 커밋한 부분으로 돌아가고 싶었다. git checkout 마지막_커밋_해시값 명령어를 입력했는데, 뭐라고 한참 뜨더니 오류인가 싶었지만 파일 내용에 변화가 없었다. 커밋 이후에 작성한 내용도 그대로 남아 있었다. git에서 무의미한 명령어 치면서 헛발질 한 게 하루이틀이 아니었기 때문에, 그냥 이게 아닌가보다~ 하고 메시지를 제대로 읽지 않았다. 역시 메시지가 뜨는 걸 항상 읽어야 한다.

     

    말해봐요 나한테 왜 그랬어요 - 넌 메시지를 읽지 않았어

    그러다가 커밋 이후의 수정사항을 날리려면 git reset --hard를 하면 된다는 걸 보고 그 명령어를 써 봤다. 아주 잘 작동했다! 최종 커밋 이후에 작성한 변경사항은 날아가고, 최종 커밋했을 때 상태로 파일들이 돌아와 있었다. 그래서 그 때부터 다시 신나게 작업을 하고 커밋 -> 푸시를 하려고 하니까 "everything is up-to-date"라는 메시지를 마주하게 되었다.

     

    Everything is up-to-date?

    아니 나는 분명히 커밋을 하고 푸시를 했는데, everything is up-to-date라니 무슨 소리야. 그러고 나서 gloga로 확인을 해 보니 나는 분명 step-2라는 브랜치에서 작업을 하고 있었는데 그 브랜치에서 나와 있었다. 그래서 위 사진을 보면 브랜치명이 표시되어 있지 않다. 그래서 부랴부랴 다시 git switch step-2 명령어를 통해서 step-2로 돌아가니까 "you are leaving 1 commit behind"라는 경고가 떴다. 혼란의 연속!

     

    페어 프로그래밍을 하고 있었기 때문에 이 상황에서는 자세하게 상황을 뜯어볼 여유가 없어서 어영부영 어떻게 어떻게 넘어갔는데, 끝나고 나서 다시 상황을 뜯어 보니 문제의 근원은 git checkout 특정_커밋_해시값을 통해 특정 커밋으로 이동하려고 했던 것부터가 문제였다고 볼 수 있다. 첫 번째 사진을 보면 내가 그 명령어를 쓴 다음에 git이 친절하게 "You are in 'detatched HEAD"라고 이야기해준 것을 볼 수 있다. 이게 뭔지 알았다면, 그 때 조치를 취할 수 있었을 것이다.

     

    detached HEAD

    이 detached HEAD라는 게 뭐냐면, 브랜치에 연결되어 있지 않은 HEAD라고 보면 된다. 대부분 git에서는 내 현재 branch와 현재 내가 있는 커밋 위치를 의미하는 HEAD가 같이 붙어 있다. HEAD는 브랜치를 통해서 간접적으로 특정 커밋을 가리킨다. 이런 일반적인 경우를 attached HEAD라고 한다. 그런데 detached HEAD는 브랜치와는 따로 떨어져 있다. HEAD가 브랜치에서 떨어져 나와서 직접 특정 커밋을 가리키는 상태가 되는 것이다. 내가 처음에 git checkout 특정_커밋_해시값을 통해서 HEAD 값을 옮겼는데, 내가 원래 있던 step-2라는 브랜치는 그대로 있는 상태에서, 나는 step-2라는 브랜치와는 연결이 떨어져서 혼자 붕 뜬 상태로 HEAD만 그 특정 커밋으로 옮겨왔던 것이다.

     

    기본적으로 깃 버전관리의 핵심 단위는 브랜치다. 근데 detached HEAD는 어떤 브랜치에도 속하지 않기 때문에 push 자체가 안 된다. 뭐야 근데 분명 근데 커밋은 됐는데? 도대체 뭐지? 그림으로 보면 조금 더 이해가 쉽다.

     

    원래 일반적인 상황에서는 아래 그림처럼 HEAD가 브랜치의 가장 마지막 커밋을 가리키는 형태로 attached되어 있어야 한다.

     

    Attached HEAD

    근데 여기서 git checkout 3(해시값)을 했다면? HEAD가 브랜치와 연결 없이 3이라는 커밋을 혼자서 직접 가리키게 된다. 이게 detached HEAD다.

     

    Detached HEAD

    나는 (메시지를 안 읽어서) 내가 detached HEAD 상태에 있는지 모르고 여기서 막 작업을 하고 커밋을 했다. 그럼 아래와 같은 상태가 된다.

     

    이 상황에서는 커밋이 브랜치와 연결되어 있지 않기 때문에 얘를 push하거나 따로 관리할 수가 없다.

     

    이럴 때 취할 수 있는 시나리오는 크게 두 가지가 있다. 첫 번째는 이 위치에서 새로운 브랜치를 만들어주는 것이고(이 커밋이 관리할 가치가 있을 경우), 두 번째는 그냥 이 커밋은 두고 원래 브랜치로 돌아가는 것이다. 첫 번째 방법을 원한다면

    git branch new-branch
    git switch new-branch

    를 쓰면 된다.

     

    이 커밋은 그냥 실험용이었고, 별 필요 없다면 그냥 내가 원래 있던 브랜치로 돌아오면 된다. git switch step-2를 하면 된다. 두 가지 경우를 그림으로 나타내면 아래와 같을 것이다.

     

    내 상황이었다면 새 커밋을 살려야 하니까, detached HEAD 상황에서 새 브랜치 new-branch를 만들고, 다시 step-2 브랜치로 돌아가서 git rebase new-branch로 fast-forwarding 했...어야 했지만 물론 오늘의 나는 그러지 못했다. 무슨 상황인지 1도 몰랐으니까! 하지만 이제는 이해했으니 다음부터는 조금 더 덜 당황할 수 있겠지.

     

    그래서 오늘의 교훈 두 개.

    1) 최근 커밋 이후 변경사항을 지우려면 git checkout 최근_커밋 말고 git reset --hard를 쓰면 된다.

    2) 오류든 워닝이든 아니면 업데이트 알림이든 메시지를 잘 읽자!


    참고자료

    - Git Detached Head: What This Means and How to Recover

    - 깃의 Detached HEAD

    댓글