들어가며
"Git을 잘 쓸 줄 아는 개발자인가?"라는 질문에 자신있게 "Yes"라고 답할 수 있나요? 저는 솔직히 그렇지 못했습니다. 매일 사용하는 도구지만, 정작 내부 동작 원리는 제대로 이해하지 못한 채 명령어만 외워서 사용하고 있었죠. 이번 기회에 Git의 내부 메커니즘을 얕게나마 파보며, Git의 이해도를 높이고자 합니다.
Git의 파일 관리 프로세스
✅ .git 디렉토리 들여다보기
git init 또는 git clone 시 생성되는 .git 폴더는 Git의 모든 것이 담긴 로컬 데이터베이스입니다.

주요 파일/폴더 구조
.git/
├── HEAD # 현재 체크아웃된 브랜치 포인터
├── config # 저장소별 설정
├── index # 스테이징 영역 (바이너리 파일)
├── objects/ # 모든 Git 객체 저장소
├── refs/
│ ├── heads/ # 로컬 브랜치 정보
│ ├── remotes/ # 원격 브랜치 정보
│ └── tags/ # 태그 정보
├── hooks/ # Git 이벤트 훅 스크립트
└── logs/ # 브랜치/HEAD 변경 이력
중요: .git 폴더를 삭제하면 모든 Git 히스토리가 사라지고 일반 폴더가 됩니다!
Git의 파일 관리
Git은 내부적으로 4가지 타입의 객체로 모든 것을 관리합니다:
| blob | 파일의 실제 내용 (Binary Large Object) |
| tree | 디렉토리 구조를 표현 (파일 목록과 blob 연결) |
| commit | tree를 가리키며 메타데이터(작성자, 메시지 등) 포함 |
| tag | 특정 커밋을 참조하는 이름표 |
각 객체는 SHA-1 해시를 통해 고유하게 식별됩니다. 예를 들어 hello.txt 파일의 내용 "hello git"은 blob 객체로 저장되고, 이 파일이 포함된 디렉토리 구조는 tree 객체로 생성되며, git commit 시 해당 tree를 가리키는 commit 객체가 만들어집니다.
따라서, Git은 브랜치마다 파일을 별도로 저장하지 않습니다.
좀 더 순서를 정리하자면, 아래의 방식으로 파일들이 관리되고 있습니다.
- hello.txt의 내용 "hello git"이 blob 객체로 저장됨.
- 이 파일이 포함된 디렉토리 상태가 tree 객체로 생성됨.
- git commit 시, 해당 tree를 가리키는 commit 객체가 만들어짐.
- 이후 버전 릴리즈를 위해 git tag v1.0을 하면 tag 객체가 해당 commit을 가리킴.
이 이상 내부를 알아보는 것은 너무 Deep 하기 때문에, 결론적으로는 “어떻게” git이 이 수많은 파일내용과 커밋 내용들을 다룰 수 있지? 에 대한 해답은
=> 해당 객체들로 관리되어, 서로서로 연관하고 비교해가며 추적할 수 있는 것! 정도로만 이해하고 넘어가겠습니다.
좀 더 내부가 궁금하다면, git의 파일 관리 방법을 참고해주세요!
✅ 4가지 핵심 구성요소
Git은 크게 4가지 영역으로 파일을 관리합니다:

- Working Directory: 실제 작업 중인 로컬 폴더
- Staging Area: Git이 변경사항을 추적하는 중간 영역
- Local Repository: 커밋으로 저장된 스냅샷들이 보관되는 .git 폴더
- Remote Repository: GitHub, GitLab 등의 원격 저장소
파일의 상태 변화
Git에서 파일은 다음과 같은 상태를 거칩니다:
- Untracked: 한 번도 git add되지 않은 파일
=> 예를 들어, 로컬 저장소에서 readme.txt라는 파일을 새로 생성하고 내용도 수정을 했는데 add 명령어를 통해 해당 파일을 등록하지 않은 경우라고 생각하면 됩니다. 이 상태의 파일은 add되지 않았기 때문에, 아무리 수정을 해도 반영되지 않습니다.

- Staged: git add로 스테이징 영역에 등록된 파일
=> 해당 상태부터는 포괄적으로 추적됨[tracked]상태가 되는 것이라고 생각하면 됩니다. 이제 해당 파일은 git으로 관리가 시작되는 것. 물론, 아직 commit은 진행하지 않은 상태.

- Unmodified: 커밋 후 변경되지 않은 파일
=> "commit == 스냅샷을 남긴다" 로 생각하면 이해가 잘 될 것 같습니다. 이제 해당 상태에서 "push"를 진행하게 되면, 해당 커밋의 스냅샷 형태가 원격으로 올라가는 것입니다.

- Modified: 추적 중인 파일이 수정된 상태
=> 위에서 우리가 readme.txt라는 파일을 commit 하여 '수정 없음' 상태로 만들었습니다. 그런데 업데이트가 필요해서, 우리의 로컬 저장소 상의 readme.txt를 고치게 되면 해당 파일은 '수정함[modified]' 상태가 될 것입니다.
=> add/commit 작업을 진행하여 스냅샷을 찍었기 때문에, 이 readme.txt는 추적상태가 되었기 때문에 파일에 변화가 생기면 이를 감지하여 수정되었음을 인지하게 되는 것입니다. (따라서 diff 비교 가능)


중요한 점은, 파일이 한 번 git add되면 그때부터 Git이 해당 파일을 추적(tracked)하기 시작한다는 것입니다. 이후 파일을 수정하면 Git은 변경사항을 감지하여 modified 상태로 표시합니다.
브랜치 전환 시 일어나는 일
저는 실제로 checkout 할 때 마다 궁금했던 부분이 있었습니다: 브랜치를 checkout하면 실제로 보여지는 폴더 / 파일이 다를까? 즉, working directory 에 영향을 끼칠까? 에 대한 질문이었는데요.
=> 결론부터 말씀드리면, 영향을 끼칩니다.
해당 이유는
git checkout은 단순히 "브랜치를 바꿔치기"하는 명령이 아닙니다.
현재 브랜치가 가리키는 커밋의 스냅샷을 Working Directory에 펼쳐놓는 작업도 함께 수행됩니다.
즉, 다음과 같은 일이 벌어집니다.
- .git/HEAD가 새 브랜치를 가리키도록 변경됨
- .git/index(=스테이지 상태)가 새로운 브랜치에 맞게 교체됨
- Working Directory의 실제 파일들이 해당 브랜치 상태로 덮어씌워짐
즉, working Directory는 사용자가 실제로 눈으로 보여지는 파일을 의미하는데, git은 브랜치에 커밋한 스냅샷을 펼쳐놓는 역할도 담당하기 때문에, 실제 파일이 변경된 것 처럼 보일 수 있습니다.
따라서, 만일 git으로 관리되는 폴더라면, 실제 파일을 바라볼 때에도 브랜치마다 보여지는 것이 다를 것이므로, 브랜치에 대한 확인이 필요됩니다.
마치며
Git은 단순한 협업 / 관리 도구가 아니라 개발자의 사고방식을 담는 그릇입니다. 단순 명령어만 외우는 것이 아니라 내부 동작 원리를 이해하면, 복잡하거나 충돌의 상황에서도 대처할 수 있을 것입니다.
📚참고 자료
https://storycompiler.tistory.com/7
https://nozeroslope.tistory.com/191
https://rewind.com/blog/github-vs-bitbucket-vs-gitlab-comparison/
https://jisooo.tistory.com/entry/git-git-rebase로-commit-정리하기-기록용