개발자라면 꼭 알아야 할 Git의 작동 원리와 객체 구조
1. Git은 단순한 버전 관리 툴이 아닙니다: 내부는 마치 타임머신처럼 작동합니다
Git을 처음 접하면 “이게 그냥 소스 코드 저장하는 툴 아닌가요?”라고 생각하시겠지만, 그 속을 들여다보면 마치 정교한 타임머신처럼 작동하는 구조를 가지고 있습니다. Git은 모든 변경 사항을 일련의 스냅샷(snapshot) 으로 저장합니다. 그리고 이 스냅샷은 각각 고유한 SHA-1 해시값으로 식별되죠. 일반적인 파일 시스템처럼 파일 자체를 그대로 저장하는 게 아니라, 마치 사진처럼 변경된 시점의 전체 파일 구조를 캡처해서 보관하는 방식입니다. 다만 동일한 파일은 중복 저장하지 않고, 이전에 존재했던 파일과 동일한 내용이라면 Git은 이를 인식하고 중복 없이 처리합니다. 이러한 방식 덕분에 Git은 빠르고, 저장 공간도 효율적으로 사용하며, 수많은 브랜치나 커밋이 있더라도 성능 저하가 거의 없습니다. 그래서 Git은 단순히 파일을 저장하는 도구가 아닌, 시간 속 변화의 흐름을 지능적으로 관리하는 시스템이라고 볼 수 있습니다.
2. Git의 모든 것은 객체입니다: Blob, Tree, Commit, Tag
Git의 내부 구조를 이해하기 위해 가장 중요한 개념 중 하나가 바로 객체(object) 입니다. Git은 데이터를 Blob, Tree, Commit, Tag 네 가지 객체 형태로 저장합니다. Blob은 파일의 실제 내용을 담고 있고, Tree는 디렉토리 구조와 파일 목록을 포함하며, Commit은 스냅샷과 메타데이터를 담고 있습니다. 마지막으로 Tag는 특정 커밋을 가리키는 ‘이름표’ 역할을 합니다. 이 객체들은 모두 Git의 .git/objects 디렉토리 안에 저장되며, 해시값으로 이름이 지정됩니다. 객체들은 서로를 참조하면서 전체 프로젝트의 히스토리를 구성해 나가는데요, 이 구조 덕분에 Git은 특정 시점으로 되돌아가는 것도, 브랜치를 분기하는 것도 손쉽게 수행할 수 있습니다. 각각의 객체는 단독으로 존재하면서도 유기적으로 연결되어 있어 마치 퍼즐 조각이 모여 하나의 그림을 완성하는 것과 같습니다.
3. SHA-1 해시는 Git의 DNA입니다
Git에서 어떤 데이터든 저장되면 반드시 SHA-1 해시값이 생성됩니다. 이 해시는 40자리의 16진수 문자열로 구성되어 있으며, Git은 이를 통해 객체를 식별합니다. 예를 들어 어떤 파일을 수정하고 저장하면, Git은 해당 파일의 내용을 기반으로 새로운 해시값을 계산하고, 이 값으로 객체를 저장합니다. 만약 두 파일의 내용이 완전히 같다면, 생성되는 해시도 동일하므로 Git은 중복 저장을 피할 수 있게 됩니다. SHA-1은 Git 내부에서 일종의 지문처럼 작동하여, 각각의 객체가 정확히 어떤 내용을 가지고 있는지를 보장하고, 무결성을 유지하는 데 큰 역할을 합니다. 이것은 마치 도서관에서 책마다 고유한 ISBN 번호를 부여하여 관리하듯, Git도 모든 데이터에 고유 ID를 부여해 체계적으로 추적하고 관리하는 방식이라 할 수 있습니다.
4. Blob: Git이 파일 내용을 저장하는 방식
Blob은 Git에서 파일의 ‘내용’만을 담는 객체입니다. 예를 들어 hello.txt 파일이 있다고 할 때, Git은 이 파일의 이름이나 경로가 아닌, 오직 내용만을 저장한 Blob 객체를 생성합니다. 이 객체는 저장된 내용의 해시값을 이름으로 삼고 .git/objects/ 안에 저장되죠. 이렇게 파일 이름이나 위치에 상관없이 내용만을 저장하기 때문에, 동일한 내용이 여러 파일에 있을 경우 Git은 하나의 Blob만 생성하고 이를 참조합니다. Blob은 단순하지만, Git 내부에서는 핵심적인 역할을 수행하는 존재입니다. 마치 어떤 이야기를 담은 종이 한 장처럼, 그 자체로는 맥락이 없지만 다른 객체들과 연결될 때 의미를 갖게 됩니다.
5. Tree: 디렉토리 구조를 설명하는 객체
Blob이 파일의 내용이라면, Tree는 그 파일들이 어떻게 구성되어 있는지를 설명하는 디렉토리의 지도입니다. 하나의 Tree 객체는 여러 개의 Blob이나 다른 Tree 객체들을 포함할 수 있습니다. 예를 들어 폴더 안에 파일과 서브 폴더가 있을 경우, Git은 상위 Tree 객체 안에 Blob과 하위 Tree들을 배열하여 저장합니다. 이 Tree 구조는 마치 컴퓨터의 폴더 탐색기처럼, 계층적 구조를 직관적으로 표현하고 있습니다. 그리고 Commit 객체는 항상 하나의 Tree를 참조합니다. 즉, 특정 커밋은 단순히 “이때 어떤 파일이 있었는가?”가 아니라, **“이 시점에 폴더 구조와 파일들의 상태가 이랬다”**는 전체 스냅샷을 참조하고 있는 셈입니다.
6. Commit 객체는 스냅샷의 지휘자입니다
Commit 객체는 프로젝트의 특정 시점을 담고 있는 스냅샷입니다. 여기엔 해당 시점의 Tree 객체, 부모 커밋의 정보(이전 커밋들), 커밋 메시지, 작성자와 타임스탬프 같은 메타데이터가 포함됩니다. 하나의 커밋은 기본적으로 하나의 Tree 객체를 가리키고, 그 Tree는 다시 여러 Blob과 Tree를 포함하고 있으니, 이 하나의 Commit만으로도 전체 프로젝트의 상태를 재구성할 수 있습니다. 마치 영화에서 감독이 “컷!”을 외쳐 장면을 저장하듯, Git에서도 커밋은 프로젝트의 한 장면을 저장하는 역할을 합니다. 여러 개의 커밋이 모이면 타임라인이 되고, 그 타임라인은 우리가 브랜치로 나누거나 병합할 수 있는 근거가 됩니다.
7. Tag 객체는 중요 지점을 위한 표지판입니다
Git에서 Tag는 일종의 체크포인트입니다. 예를 들어 버전 1.0을 릴리스할 때, 해당 커밋에 v1.0이라는 태그를 달면 그 이후에도 언제든 이 커밋으로 쉽게 돌아올 수 있습니다. Tag 객체는 실제로 특정 커밋을 가리키며, Annotated Tag의 경우 작성자, 날짜, 메시지 등도 함께 포함합니다. Git은 브랜치가 계속 움직이는 포인터라면, Tag는 고정된 마커에 가깝습니다. 마치 지도에 있는 핀처럼, 중요한 지점을 기억하고 표시해 두는 역할을 하죠.
8. 저장 방식은 압축과 중복 제거의 마법입니다
Git은 데이터를 단순히 저장하는 것이 아니라, zlib 압축 알고리즘을 사용해 효율적으로 저장합니다. 모든 객체는 저장 전에 zlib으로 압축되어 .git/objects 안에 들어가며, 중복된 Blob이나 Tree는 새로 저장되지 않습니다. 덕분에 수천 개의 커밋과 브랜치가 있어도 .git 디렉토리의 크기가 생각보다 작게 유지되는 것을 확인하실 수 있습니다. 이 압축 방식은 마치 오래된 책을 얇은 전자책으로 바꾸는 것처럼 공간을 줄이면서도 정보를 완전하게 보존하는 마법 같은 기술입니다.
9. Refs와 HEAD: 포인터의 세계
Git은 다양한 브랜치, 태그, 현재 작업 중인 위치 등을 refs와 HEAD 파일을 통해 관리합니다. .git/refs/heads/ 디렉토리에는 각 브랜치 이름이 파일로 존재하며, 그 안에는 해당 브랜치가 가리키는 커밋의 해시값이 들어 있습니다. HEAD는 현재 작업 중인 브랜치를 가리키며, 브랜치를 전환하거나 커밋을 할 때마다 이 포인터도 함께 이동합니다. 포인터 시스템은 마치 책갈피처럼, 내가 어디까지 읽었고, 어디서부터 다시 시작할 수 있는지를 정확하게 알려주는 역할을 합니다.
10. Git의 저장소는 단순한 폴더가 아닌 정보의 집합입니다
많은 분들이 Git 저장소를 단순히 .git 폴더로 이루어진 디렉토리 구조라고 생각하시지만, 그 안에는 수많은 객체, 참조 포인터, 설정값들이 유기적으로 얽혀 있습니다. Git은 이를 통해 복잡한 작업도 간단하게 처리할 수 있으며, 변경 내역을 손실 없이 추적하고, 다양한 브랜치 작업을 효율적으로 수행합니다. 마치 유기적인 생명체처럼, Git은 변화하는 환경 속에서도 안정적으로 데이터를 보존하고, 필요한 순간에 과거로 돌아갈 수 있는 강력한 힘을 가지고 있습니다.
마무리하며: Git을 이해하면 버전 관리가 달라집니다
Git의 내부 구조와 객체 저장 방식을 이해하면, 단순히 명령어를 외우는 수준을 넘어서 Git이 왜 그렇게 작동하는지를 자연스럽게 받아들이실 수 있습니다. 어떤 문제가 발생했을 때도 내부 구조를 알면 당황하지 않고 원인을 분석할 수 있고, 더욱 안정적이고 체계적인 개발 환경을 만들어 나갈 수 있습니다. Git은 단순한 도구가 아닌, 변화와 협업을 위한 강력한 무기입니다. 그 무기를 잘 다루기 위해서는, 겉모습보다 더 깊은 구조를 이해하는 것이 중요합니다.
자주 묻는 질문 (FAQs)
Q1. Git에서 동일한 파일을 여러 번 커밋하면 용량이 늘어나지 않나요?
A1. 아닙니다. Git은 파일 내용이 같다면 동일한 Blob 객체를 사용하기 때문에 중복 저장되지 않습니다.
Q2. SHA-1 해시가 충돌할 가능성은 없나요?
A2. 이론적으로는 있지만, 실제로는 극히 낮습니다. Git은 추가적인 충돌 방지 로직도 갖추고 있습니다.
Q3. Tree 객체는 커밋할 때마다 새로 생성되나요?
A3. 변경된 디렉토리 구조가 있을 때만 새로운 Tree 객체가 생성되며, 동일한 구조는 재사용됩니다.
Q4. Tag와 Branch의 차이점은 무엇인가요?
A4. 브랜치는 이동 가능한 포인터이고, 태그는 고정된 참조입니다. 브랜치는 계속 변화하지만 태그는 정적인 위치를 가리킵니다.
Q5. Git 객체들은 언제까지 저장되나요? 자동으로 삭제되나요?
A5. 기본적으로는 삭제되지 않으며, git gc 명령어나 수동 정리를 통해 정리할 수 있습니다.