Search

Git의 세 가지 트리와 Reset 이해

Git은 근본적으로 세 개의 트리를 관리하는 시스템입니다. 이 관점에서 Git을 이해하면 reset과 checkout 같은 명령어들의 동작 원리가 명확해집니다.

Git의 세 가지 트리

이전에 Git의 기본 구조를 다루면서 세 가지 트리에 대해 살펴보았습니다. 다시 한번 정리하자면, Git에서 말하는 '트리'는 자료구조의 트리가 아니라 파일의 묶음을 의미합니다. Git은 다음 세 가지 트리를 관리하는 컨텐츠 관리 시스템으로 이해할 수 있습니다.

HEAD

HEAD는 현재 브랜치를 가리키는 포인터이며, 브랜치는 해당 브랜치의 마지막 커밋을 가리킵니다. 따라서 HEAD는 현재 브랜치 마지막 커밋의 스냅샷입니다. 이 커밋은 다음 커밋의 부모가 됩니다.

Index

Index는 다음에 커밋할 스냅샷입니다. 우리가 Staging Area라고 부르는 영역이 바로 이 Index입니다. git commit 명령을 실행했을 때 Git이 처리할 내용들이 존재하는 곳입니다.
Index는 워킹 디렉토리에서 마지막으로 Checkout한 브랜치의 파일 목록과 내용으로 채워집니다. 파일을 수정하고 git add 명령으로 Index를 업데이트하면, git commit 명령 실행 시 이 Index가 새 커밋으로 변환됩니다.

워킹 디렉토리

워킹 디렉토리는 실제 파일이 존재하는 공간입니다. 앞의 두 트리는 .git 디렉토리에 효율적인 형태로 저장되지만, 워킹 디렉토리는 사용자가 직접 편집할 수 있는 실제 파일들로 구성됩니다. 커밋하기 전 변경사항을 자유롭게 수정할 수 있는 샌드박스로 생각할 수 있습니다.

워크플로

Git의 주 목적은 프로젝트의 스냅샷을 지속적으로 저장하는 것입니다. 이 세 트리를 사용해 스냅샷을 관리하는 과정을 단계별로 살펴보겠습니다.

초기 상태

새로운 저장소를 만들거나 브랜치를 체크아웃하면 세 트리가 모두 동일한 내용을 가지게 됩니다. 이때 git status를 실행하면 "nothing to commit, working tree clean" 메시지가 나타납니다.

파일 수정

파일을 수정하면 워킹 디렉토리만 변경됩니다. 이때 git status는 "Changes not staged for commit" 메시지와 함께 수정된 파일을 빨간색으로 표시합니다. 워킹 디렉토리와 Index의 내용이 다르기 때문입니다.

Staging (git add)

git add 명령으로 변경사항을 Index에 추가합니다. 이는 워킹 디렉토리의 내용을 Index로 복사하는 과정입니다. 이제 워킹 디렉토리와 Index는 같지만 HEAD와는 다릅니다. git status는 "Changes to be committed" 메시지와 함께 파일을 녹색으로 표시합니다.

커밋 (git commit)

git commit 명령을 실행하면 다음 과정이 진행됩니다:
1.
Index의 내용을 스냅샷으로 영구히 저장합니다.
2.
이 스냅샷을 가리키는 커밋 객체를 생성합니다.
3.
현재 브랜치가 새로운 커밋을 가리키도록 업데이트합니다.
4.
HEAD는 여전히 현재 브랜치를 가리키므로, 결과적으로 HEAD도 새 커밋을 가리키게 됩니다.
이제 다시 세 트리가 모두 같은 내용을 담게 되어, git status는 깨끗한 상태를 보여줍니다.

브랜치 체크아웃

브랜치를 체크아웃하면 다음 과정이 일어납니다:
1.
HEAD가 새로운 브랜치를 가리킵니다.
2.
새 브랜치가 가리키는 커밋의 스냅샷을 Index에 놓습니다.
3.
Index의 내용을 워킹 디렉토리로 복사합니다.
이 과정을 통해 다시 세 트리가 동기화됩니다.

Reset의 동작 원리

reset 명령은 이 세 트리를 단계적으로 조작합니다. 트리를 조작하는 동작은 세 단계로 이루어집니다.

1단계: HEAD 이동

reset 명령이 하는 첫 번째 일은 HEAD가 가리키는 브랜치를 이동시키는 것입니다. checkout 명령과 달리 HEAD 자체를 이동시키지 않고, HEAD가 가리키는 브랜치가 가리키는 커밋을 변경합니다.
git reset 9e5e6a4 명령을 실행하면 현재 브랜치가 9e5e6a4 커밋을 가리키게 됩니다. --soft 옵션을 사용하면 여기까지만 수행합니다. 이는 가장 최근의 git commit 명령을 되돌리는 효과가 있습니다.

2단계: Index 업데이트

reset 명령은 Index를 현재 HEAD가 가리키는 스냅샷으로 업데이트할 수 있습니다. --mixed 옵션을 사용하거나 옵션 없이 실행하면 이 단계까지 수행합니다. 이는 git commit 명령을 되돌리고 git add 명령까지 되돌리는 효과가 있습니다.

3단계: 워킹 디렉토리 업데이트

-hard 옵션을 사용하면 워킹 디렉토리까지 업데이트합니다. 이 옵션은 워킹 디렉토리의 파일을 강제로 덮어쓰기 때문에 주의해서 사용해야 합니다. 커밋하지 않은 변경사항이 있다면 복구할 수 없습니다.

경로를 지정한 Reset

reset 명령에 파일 경로를 지정하면 1단계를 건너뛰고 해당 파일에만 나머지 단계를 적용합니다. HEAD는 포인터이므로 파일별로 다른 커밋을 가리킬 수 없기 때문입니다.
git reset file.txt 명령은 git reset --mixed HEAD file.txt의 축약형입니다. 이 명령은 file.txt 파일을 HEAD에서 Index로 복사하여 해당 파일을 Unstaged 상태로 만듭니다.

Reset과 Checkout의 차이

reset과 checkout 명령 모두 세 트리를 조작하지만 중요한 차이가 있습니다.
첫째, checkout은 워킹 디렉토리를 안전하게 다룹니다. 저장하지 않은 변경사항이 있는지 확인하고, 변경하지 않은 파일만 업데이트합니다. 반면 reset --hard는 확인 없이 모든 것을 덮어씁니다.
둘째, HEAD를 업데이트하는 방식이 다릅니다. reset은 HEAD가 가리키는 브랜치를 움직이지만, checkout은 HEAD 자체를 다른 브랜치로 옮깁니다.

실제 활용

커밋 합치기

여러 개의 작은 커밋을 하나로 합치려면 reset을 활용할 수 있습니다. git reset --soft HEAD~2 명령으로 HEAD 포인터를 두 커밋 이전으로 되돌린 후, git commit을 실행하면 중간 커밋들이 하나로 합쳐집니다.

파일 단위 작업

특정 파일만 이전 상태로 되돌리려면 경로를 지정한 reset을 사용합니다. git reset HEAD~ file.txt 명령은 file.txt 파일만 이전 커밋 상태로 되돌립니다.

안전한 사용을 위한 요약

reset 명령 사용 시 다음 사항을 기억해야 합니다.
-soft: HEAD가 가리키는 브랜치만 이동. 안전합니다.
-mixed (기본값): 브랜치 이동 + Index 업데이트. 안전합니다.
-hard: 브랜치 이동 + Index 업데이트 + 워킹 디렉토리 덮어쓰기. 주의가 필요합니다.
Git의 세 트리 구조를 이해하면 reset과 checkout 같은 명령어의 동작을 정확히 예측할 수 있습니다. 원리의 이해를 통해 Git을 더 효과적으로 사용할 수 있게 합니다.

참고