Git!! 은 버전 관리 시스템으로서 코드의 변경 사항을 추적해 협업에서 필수적으로 사용되는 SW 중 하나입니다.
Git을 사용할 때 기본적인 명령어만 알고 사용할 줄 알고 내부 동작원리에 대해 알지 못해서 이번 기회에 내부 동작 원리에 대해서 작성해보려고 합니다! 이 포스팅에서 Git이 어떻게 작동하는지, 그리고 Git의 내부에 어떤 객체들이 존재하며 어떻게 관리되는지를 알아보겠습니다.
git 폴더 구성을 살펴봅시다! 프로젝트 폴더에 .git 디렉토리의 구성요소는 다음과 같습니다. index 파일, objects, refs 폴더를 살펴보도록 하겠습니다.

.git 디렉토리는 Git 저장소의 핵심 데이터를 관리하는 폴더로, Git의 모든 버전 관리 정보가 이 디렉토리에 저장됩니다. 이 디렉토리 안에는 다양한 파일과 폴더가 있으며, 그중에서 index 파일, objects 폴더, refs 폴더는 중요한 역할을 합니다.
Git의 index 파일(스테이지 영역 또는 캐시라고도 함)은 작업 디렉토리에서 변경된 파일들의 정보를 추적합니다. 이 파일은 커밋하기 전에 어떤 파일들이 스테이지에 올라와 있는지 관리하며, 다음 커밋에 포함될 파일들을 결정합니다.

Git의 모든 데이터는 objects 폴더에 저장됩니다. 이 폴더는 Git의 데이터베이스로, 저장된 모든 파일, 디렉터리 구조, 커밋 등은 이곳에 저장됩니다. Git은 데이터의 내용에 따라 고유한 SHA-1 해시를 생성하고, 이 해시를 기반으로 objects 폴더에 데이터를 저장합니다.
refs 폴더는 Git에서 브랜치, 태그, 그리고 기타 참조(refs)들을 관리합니다. 이 폴더는 저장소의 브랜치와 태그가 특정 커밋을 가리키도록 하여, 작업을 쉽게 추적하고 관리할 수 있도록 합니다.

이제 git 폴더에 주요 파일 및 폴더의 역할에 대해서 알아봤습니다.
그러면 파일을 어떤 형태로 저장하고 버전 별로 파일의 형태를 어떻게 기록할까요?
Git은 데이터를 저장할 때 SHA-1 해시를 사용해 160비트(40자)의 체크섬을 생성하고, 이를 고유 식별자로 사용했습니다(그럼 지금은..? 👀👀👀👀👀)
이 해시는 File, Directory tree, commit 등 모든 데이터를 식별하고 관리하는 데 활용됩니다.
Git은 내용을 주소로 활용하는 파일 시스템입니다.
데이터를 키-밸류 데이터베이스 형태로 저장합니다.파일 내용을 기준으로 해시값을 생성하고 해당 해시 값을 활용하여 파일을 저장합니다.
구체적으로, SHA-1 해시를 키로 삼아 .git/objects/ 디렉터리 아래에 해시의 앞 두 자로 하위 디렉터리를 만들고, 나머지 38자를 파일명으로 사용해 데이터를 저장합니다. 이렇게 저장된 데이터는 나중에 동일한 해시로 찾아낼 수 있어, 버전 관리 시스템의 기반을 이룹니다.

Git은 SHA-1을 사용하였으나 2017년에는 실제로 SHA-1 충돌을 발견하는 것이 가능해졌습니다. 따라서 Git은 이러한 문제에 대응하기 위해 SHA-1와 SHA-256를 병행하고 있습니다.
SHA-256은 SHA-1보다 더 긴 해시 값을 생성하며, 현재까지 알려진 공격 방법들에 대해 더 강력한 보안을 제공합니다. (더 자세한 내용은 여길 클릭하세요!)
Git은 .git/objects/에 저장하는 파일들은 zlib으로 압축합니다.
Git의 객체(블롭, 트리, 커밋 등)는 SHA-1 해시를 기반으로 생성된 후 zlib을 통해 압축되어 .git/objects/ 디렉터리에 저장됩니다. 각 객체는 고유한 SHA-1 해시를 키로 가지며, 이 해시를 통해 파일을 찾고, 압축을 해제하여 데이터를 복원할 수 있습니다.
NSData를 활용하여 zlib로 압축, 압축해제할 수 있습니다. Git에서 object는 저장소 내의 데이터 및 메타데이터를 관리하는 기본 단위입니다. Git의 객체로는 blob, tree, commit가 있습니다. 이 객체들은 Git의 버전 관리 기능의 핵심을 이루며, 각각 파일의 내용, 디렉터리 구조, 그리고 커밋 히스토리를 관리합니다.
Blob 객체는 Git에서 특정 시점의 파일의 내용을 저장하는 데 사용됩니다.
파일 자체에 대한 정보만을 담고 있으며, 파일 이름이나 경로는 포함하지 않습니다.
Tree 객체는 Git에서 특정 시점의 디렉터리의 구조를 표현합니다.
하나의 tree 객체는 특정 시점의 디렉터리를 나타내며, 해당 디렉터리 안에 포함된 파일과 서브 디렉터리를 기록하고 디렉터리 내 파일(혹은 하위 디렉터리)에 대한 참조(즉, blob 또는 다른 tree 객체에 대한 포인터)를 포함합니다.
각 tree 객체는 해당 디렉터리 내의 파일 및 하위 디렉터리의 목록을 포함하며, 각각의 항목에 대해
을 저장합니다.
Commit 객체는 Git의 버전 관리에서 가장 중요한 객체로, 특정 시점에서의 프로젝트 상태(즉, 스냅샷)를 기록합니다. commit 객체는 프로젝트의 히스토리를 추적하고, 각 커밋은 이전 커밋들과의 관계를 나타내며, 브랜치와 태그의 기반이 됩니다.
이렇게 Git의 object는 각각 파일의 내용, 디렉터리 구조, 커밋 히스토리를 관리하는 역할을 합니다. 이 객체들이 서로 참조 관계를 형성하여 Git이 효율적으로 데이터를 관리하고, 복잡한 버전 관리를 가능하게 해줍니다.