Git에서 HEAD란 무엇인가?

Git에서 HEAD란 무엇인가?

Git을 사용하다 보면 HEAD라는 단어를 자주 접하게 됩니다. git log를 실행하면 (HEAD -> main)이라고 표시되고, git reset HEAD~1처럼 명령어에서도 자주 등장하죠.

하지만 HEAD가 정확히 무엇인지 물어보면 선뜻 대답하기 어려운 분들이 많습니다. “현재 브랜치를 가리키는 거 아니야?”라고 생각할 수 있지만, 사실 HEAD는 그보다 조금 더 복잡한 개념입니다.

이번 글에서는 HEAD의 정확한 의미와 함께, 실무에서 유용하게 활용할 수 있는 HEAD~, HEAD^ 같은 표현식까지 알아보겠습니다.

HEAD란 무엇인가?

Git에서 HEAD현재 작업 중인 위치를 가리키는 포인터입니다. 쉽게 말해, “지금 내가 어떤 커밋을 기준으로 작업하고 있는가”를 나타내죠.

HEAD는 실제로 .git/HEAD 파일에 저장되어 있습니다. 직접 확인해볼까요?

$ cat .git/HEAD
ref: refs/heads/main

위 결과를 보면 HEADrefs/heads/main를 참조하고 있습니다. 이는 HEADmain 브랜치를 가리키고 있다는 의미입니다.

git symbolic-ref 명령어를 사용하면 더 간편하게 확인할 수 있습니다.

$ git symbolic-ref HEAD
refs/heads/main

$ git symbolic-ref --short HEAD
main

HEAD가 어떤 커밋이나 브랜치를 가리키고 있는지는 git rev-parse 명령어로 알아냅니다.

# HEAD가 가리키는 전체 커밋 해시 확인
$ git rev-parse HEAD
a80b4e5f987e666cd6473fae575fd0dd34967007

# HEAD가 가리키는 브랜치 이름 확인
$ git rev-parse --abbrev-ref HEAD
main

git log 명령어로도 HEAD의 위치를 확인할 수 있습니다.

$ git log --oneline -3
a80b4e5 (HEAD -> main, origin/main, origin/HEAD) 세 번째 커밋
6af2cdf 번째 커밋
f9a5b77 번째 커밋

여기서 HEAD -> mainHEAD가 main 브랜치를 가리키고 있음을 보여줍니다.

브랜치를 가리키는 HEAD

HEAD는 대부분의 경우 브랜치를 가리킵니다.

이 상태에서 새로운 커밋을 만들면, HEAD가 가리키는 브랜치도 함께 새 커밋으로 이동하죠.

# 현재 HEAD와 main 브랜치가 같은 커밋을 가리킴
$ git log --oneline -1
a80b4e5 (HEAD -> main) 이전 커밋

# 새로운 커밋 생성
$ git commit -m "새로운 커밋"
[main b91c6f8] 새로운 커밋

# HEAD와 main 브랜치 모두 새 커밋으로 이동
$ git log --oneline -2
b91c6f8 (HEAD -> main) 새로운 커밋
a80b4e5 이전 커밋

커밋을 가리키는 HEAD

HEAD는 브랜치 대신에 특정 커밋을 직접 가리킬 수도 있는데요. 이렇게 브랜치로 부터 분리된 HEAD를 보통 Detached HEAD라고 부릅니다.

Detached HEAD 상태에서는 .git/HEAD 파일에 브랜치 참조가 아닌 커밋 해시가 직접 저장됩니다.

$ git switch --detach a80b4e5
Note: switching to 'a80b4e5'.

You are in 'detached HEAD' state...

$ cat .git/HEAD
a80b4e5...

HEAD는 주로 아래와 같은 상황에서 Detached가 됩니다.

  • 특정 커밋을 직접 이동할 때: git switch --detach <커밋해시>
  • 특정 태그로 이동할 때: git switch --detach v1.0.0
  • git rebase 진행 중

이러헥 HEAD가 Detached된 상태에서 새로운 커밋을 추가하면, 그 커밋은 어떤 브랜치에도 속하지 않습니다. 그래서 이 상태에서 다른 브랜치로 이동하면 해당 커밋은 고아가 되어 나중에 찾기 어려워질 수 있죠.

$ git switch --detach a80b4e5
# ... 작업 후 커밋 ...
$ git switch main
Warning: you are leaving 1 commit behind, not connected to
any of your branches...

Detached HEAD 상태에서 빠져나오려면 다음과 같이 하면 됩니다.

# 기존 브랜치로 돌아가기
$ git switch main

# 현재 위치에서 새 브랜치 생성하기
$ git switch -c new-branch

참고로 Git v2.23 이전에서는 git checkout 명령어로도 동일한 작업을 할 수 있습니다.

$ git checkout a80b4e5      # git switch --detach a80b4e5
$ git checkout main         # git switch main
$ git checkout -b new-branch  # git switch -c new-branch

HEAD로 일반 커밋의 조상 참조

HEAD를 잘 활용하면 최근 커밋을 아주 쉽게 참조할 수 있는데요. HEAD를 기준으로 조상 커밋을 참조하는 편리한 표현식을 제공하기 때문입니다.

HEAD~nHEADn 번째 부모 커밋을 가리킵니다. 숫자를 붙여서 몇 세대 위의 조상인지 지정할 수 있습니다.

  • HEAD~ 또는 HEAD~1: 부모 커밋 (1세대 위)
  • HEAD~2: 조부모 커밋 (2세대 위)
  • HEAD~3: 증조부모 커밋 (3세대 위)
$ git log --oneline -4
d4e5f6g (HEAD -> master) 네 번째 커밋
c3d4e5f 번째 커밋
b2c3d4e 번째 커밋
a1b2c3d 번째 커밋

이 표현식은 다양한 Git 명령어와 함께 유용하게 활용됩니다. 예를 들어 git diff로 변경 내용을 비교하거나 git reset으로 커밋을 되돌릴 때 쓸 수 있죠.

# 마지막 커밋과 현재 변경사항 비교
$ git diff HEAD~1

# HEAD와 이전 커밋 비교
$ git diff HEAD~1 HEAD

# 마지막 커밋을 취소하고 변경사항은 작업 디렉토리에 유지
$ git reset HEAD~1

# 최근 3개 커밋을 대화형으로 리베이스 (squash, reword 등)
$ git rebase -i HEAD~3

HEAD로 병합 커밋의 부모 참조

병합 커밋(merge commit)의 경우 부모 커밋이 2개이기 때문에 다른 표현식을 사용해야합니다.

예를 들어, feature 브랜치에서 작업 중에 main 브랜치의 최신 변경사항을 가져오는 상황을 살펴볼까요?

# feature 브랜치에서 작업 중
$ git log --oneline -1
f4e5d6c (HEAD -> feature) feature 브랜치 커밋

# main 브랜치에 새로운 커밋이 추가된 상황
# main의 최신 변경사항을 feature 브랜치에 병합
$ git merge main

이제 커밋 히스토리를 보면 두 개의 부모가 있습니다.

$ git log --oneline --graph -3
*   m7e8f9a (HEAD -> feature) Merge main into feature
|\
| * a1b2c3d (main) main 브랜치 커밋
* | f4e5d6c feature 브랜치 커밋
|/
* b2c3d4e 공통 조상 커밋

이럴 때는 ~(틸트) 기호 대신에 ^(캐럿) 기호를 사용해야 합니다.

  • HEAD^ 또는 HEAD^1: 병합을 시킨 브랜치에서 부모 커밋
  • HEAD^2: 병합을 당한 브랜치에서 부모 커밋
# 첫 번째 부모: 병합을 실행한 브랜치(feature)의 커밋
$ git show HEAD^1 --oneline -s
f4e5d6c feature 브랜치 커밋

# 두 번째 부모: 병합된 브랜치(main)의 커밋
$ git show HEAD^2 --oneline -s
a1b2c3d main 브랜치 커밋

마치며

HEAD는 Git에서 “지금 내가 어디에 있는가”를 나타내는 아주 중요한 개념입니다.

이번 글에서 다룬 내용을 정리하면 다음과 같습니다.

  • HEAD는 현재 작업 중인 위치를 가리키는 포인터로, .git/HEAD 파일에 저장됩니다.
  • 일반적으로 HEAD는 브랜치를 가리키며, 새 커밋을 만들면 브랜치와 함께 이동합니다.
  • HEAD가 브랜치가 아닌 커밋을 직접 가리키면 Detached HEAD 상태가 됩니다.
  • HEAD~n을 사용하면 n세대 위의 조상 커밋을 쉽게 참조할 수 있습니다.
  • 병합 커밋에서는 HEAD^1, HEAD^2로 각각의 부모 커밋을 선택할 수 있습니다.

HEAD의 개념을 잘 이해하고 있으면 git reset, git rebase, git switch 같은 명령어를 사용할 때 훨씬 자신감 있게 작업할 수 있을 것입니다.

HEAD를 활용해서 특정 커밋의 내용을 확인하는 방법은 git show 사용법에서 자세히 다루고 있으니 참고 바랍니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord