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
위 결과를 보면 HEAD가 refs/heads/main를 참조하고 있습니다.
이는 HEAD가 main 브랜치를 가리키고 있다는 의미입니다.
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 -> main는 HEAD가 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~n는 HEAD의 n 번째 부모 커밋을 가리킵니다.
숫자를 붙여서 몇 세대 위의 조상인지 지정할 수 있습니다.
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