본문 바로가기
git

Git 내부 동작 방식 알아보기

by marble25 2022. 5. 7.

버전 관리 시스템을 설계해 보다가 Git 시스템 내부에서는 어떻게 버전을 관리하는지를 찾아보게 되었다.

부끄럽지만, 지금까지 git을 쓰면서 한 번도 내부적으로 어떻게 동작하는지에 대해 관심을 가지지 않았고, 그래서 이번 기회에 세부 구현을 찾아 보았다.

 

https://git-scm.com/book/en/v2

 

Git - Book

 

git-scm.com

git 공식 오피셜 문서였고, 한글로도 번역이 되어 있어서 찾아 보기 편했다.

이 중 내가 관심있게 본 부분은 Git 내부 구현 부분이다.

 

Plumbing vs Porcelain

Git은 내부적으로 많은 저수준 명령어로 구성되어 있고, 이 명령어들을 묶어서 고수준 명령어를 만들었다.

저수준의 명령어는 Plumbing 명령어라고 부르고, 사용자용 고수준 명령어는 Porcelain 명령어라고 부른다. 

우리가 익히 아는 명령어들은 대부분 Porcelain 명령어가 많다.

우리가 Plumbing 명령어를 이용한다면 Git의 내부 구조에 접근할 수 있고, 어떻게 동작하는지 살펴볼 수 있다.

Plumbing 명령어는 주로 새로운 도구를 만들거나 스크립트를 작성할 때에 사용한다.

 

Git 내부 구조

git init을 통해 .git 디렉터리를 만들면 다음과 같이 파일이 생성된다.

  • config: 프로젝트 설정 옵션
  • description: 관련 설명
  • info: .gitignore처럼 무시할 파일 패턴
  • hooks: 커밋이나 리베이스, 푸시 등의 이벤트가 생겼을 때에 자동으로 특정 스크립트를 실행할 수 있도록 클라이언트 훅이나 서버 훅이 포함
  • HEAD: 현재 체크아웃한 브랜치
  • index: Staging Area에 대한 정보
  • objects: 모든 컨텐츠를 저장하는 데이터베이스
  • refs: 커밋 개체의 포인터(브랜치, 태그, 리모트 등)

이 중 아래 4개의 요소들이 git에서 중요하게 다루어진다.

 

Git 개체

파일 추가

git에 새 파일이 stage 상태로 들어가면 objects 디렉터리에 파일이 추가된다.

이 때, 파일이름은 데이터와 헤더를 이용해 생성한 SHA-1 체크섬으로 지어진다. 

해시의 처음 두 글자를 따서 디렉토리 이름에 사용하고 나머지 38글자를 파일 이름에 사용한다.

 

Tree 개체

Git은 유닉스 파일 시스템과 비슷한 방법으로 저장하지만 좀 더 단순하다.

모든 것을 Tree와 Blob 개체로 저장한다.

Tree는 유닉스의 디렉토리에 대응되고 Blob은 Inode나 일반 파일에 대응된다.

 

커밋 개체

트리는 결국 스냅샷을 나타내는 역할을 하게 된다.

여전히 이 스냅샷을 불러오려면 SHA-1 값을 기억해야 하고, 트리에는 스냅샷을 누가 언제 왜 저장했는지에 대한 정보는 없다.

커밋 개체는 스냅샷에서 최상단 트리를 하나 가리키고, Author/Committer 정보, 시간, 커밋 메세지를 포함하게 된다.

Git은 변경된 파일을 Blob 개체로 저장하고 현재 인덱스에 따라서 Tree 개체를 만들게 된다.

이전 커밋 개체와 최상위 트리 개체를 참고해서 커밋 개체를 만든다.

즉 Blob, Tree, 커밋 개체가 Git의 주요 개체이고 이 개체는 전부 .git/objects 디렉토리에 저장된다.

위 그림은 커밋, tree, Blob를 나타낸다. 

커밋이 트리를 가르키고, 트리 안에서 또 다른 트리나 Blob 객체를 가르키게 된다. 

 

Refs 객체

Refs는 특정 커밋을 가리키는 포인터로, 기억하기 어려운 해시값보다 더 쉬운 이름을 지정할 수 있다.

브랜치는 어떤 작업 중 마지막 작업을 가리키는 포인터 또는 Refs이다.

브랜치는 직접 가리키는 커밋과 그 커밋으로 따라갈 수 있는 모든 커밋을 포함하는 개념이다.

HEAD

HEAD는 현 브랜치를 가리키는 간접(symbolic) Refs이다. 

HEAD 파일을 열어보면 다음과 같이 생겼다.

git checkout test를 실행하면 Git이 HEAD를 다음과 같이 바꾼다.

태그

태그 개체는 커밋 개체랑 매우 비슷해서 커밋 개체처럼 누가, 언제 태그를 달았는지 태그 메시지는 무엇이고 어떤 커밋을 가리키는지에 대한 정보가 포함되지만, 태그 개체는 Tree 개체가 아니라 커밋 개체를 가리키는 것이 차이점이다. 

브랜치처럼 커밋 개체를 가리키지만 옮길 수는 없다.

주로 커밋 개체를 가리키는데 쓰이고 버저닝할 때 쓰이지만, Blob 개체에 태그를 다는 방식도 가능하다.

 

리모트

리모트를 추가하고 Push 하면 Git은 각 브랜치마다 Push 한 마지막 커밋이 무엇인지 refs/remotes 디렉토리에 저장한다. 

Refs 브랜치와 달리 리모트 Refs는 Checkout할 수 없고 읽기 용도로만 쓸 수 있는 브랜치이다.

서버의 브랜치가 가리키는 커밋이 무엇인지 적어둔 북마크이다.

 

Packfile

Git은 zlib로 파일 내용을 압축해서 저장하기 때문에 저장 공간이 많이 필요하지 않는다.

만약 하나의 파일을 저장한 후 그 파일을 수정해서 다시 저장한다면 git에서는 두 파일 모두를 가지고 있게 된다.

그렇다면 처음과 두 번째의 차이점만 저장할 수 없을까?

 

Git이 처음 파일을 저장할 때에는 Loose 개체 포맷으로 저장하게 되고, 이 개체를 파일 하나로 압축(Pack)하여 공간을 절약하고 효율을 높일 수 있다.

Git은 Loose 개체가 너무 많을 때, git gc 명령을 실행했을 때, 리모트 서버로 Push할 때 이렇게 압축한다.

objects 디렉토리를 열어보면 한 쌍의 파일이 생기게 된다.

gc 명령을 실행하기 전에는 파일 크기가 15K 정도였는데 새로 만들어진 Packfile은 7K에 불과하게 된다.

Git은 개체를 압축시킬때 먼저 이름이나 크기가 비슷한 파일을 찾는다.

그리고 두 파일을 비교해서 한 파일은 다른 부분만 저장한다.

특이한 점은 원본을 그대로 저장하는 것이 첫 번째가 아니라 두 번째 버전이라는 것이고, 첫 번째 버전은 차이점만 저장된다.

최신 버전에 접근할 때가 더 많고 최신 버전에 접근하는 속도가 더 빨라야 하기 때문이다.

 

 

'git' 카테고리의 다른 글

git flow vs github flow  (0) 2024.01.27
[후기] Sapling 써보기  (0) 2023.11.05
graphite 사용기  (2) 2023.10.23
[TIL] Git Server  (0) 2022.05.15