Git

[Git] Git의 내부 구조와 동작 원리를 알아보자

동구름이 2024. 7. 15. 22:26

 git 명령어는 자주 사용했지만, 실제 동작 원리에 대한 이해는 부족했던 것 같다. 특히 깃 충돌을 겪을 때, 부족한 개념지식으로 해결이 꼬이는 경우가 간혹가다 있었는데 오늘도 작은 규모의 커밋에서 그런 경험을 했다.

 

우선 깃 내부 동작 과정에 대해서 학습해보려 한다.

 

Git 내부 구조 살펴보기

내부 구조는 어떻게 확인할 수 있을까? 바로 .git이라는 폴더를 통해 내용을 살펴볼 수 있다.

특정 경로에 깃을 사용하면 .git이라는 폴더가 생성된다. 맥북의 경우 (command+shift+”.”) 을 누르면 숨겨진 . 파일을 표시할 수 있다.

 

clone을 뜬 폴더에서 (command+shift+”.”)를 수행한 결과이다. 숨겨진 .git 디렉토리가 나타난다.

 

 

내부로 들어가 구성 요소들을 살펴보면, 위와 같다.

 

여기서 중요한 요소들인 /objects , /ref , /log , /hooks , HEAD , index 들을 살펴보자

 

 

 

/Objects

/objects 디렉토리는 git에서 활용하는 데이터들이 저장되는 곳이다.

 

 /objects 내부로 들어가면 여러 폴더가 있고 그 아래에 파일이 하나씩 있는 구조를 볼 수 있다. 사진에서 보이듯이 구조가 독특한데, 2글자의 폴더 밑에 38글자의 파일명을 가지는 구조라는 것을 볼 수 있다.

 

 이런 독특한 구조를 두는 이유가 뭘까??

 

 

“Git은 내용을 주소로 활용하는 파일시스템(content-addressable filesystem)이다”

 

 

바로 위 개념 덕분이다. 어렵게 생각하지말자.

 

 

git은 stupid 한 내용 트래커라는.. 비하? 섞인 멘트도 있다.. 그만큼 단순하다는 것이다. (https://linux.die.net/man/1/git)

 

 

깃은 key-value형태의 데이터베이스이다. 그리고 여기서 key는 깃에 담기는 데이터의 SHA-1 해시값을 키 값으로 저장한다. 그리고 이 SHA-1 해시값을 통해 보관된 데이터를 찾을 수 있다. 이를 정리하면, 위의 개념 그대로 내용을 통해 주소를 파악할 수 있는 것이다.

 

 

여기서 왜 2+38의 40자인가는 SHA-1 를 사용하기 때문이다. 이 부분은 간략히만 살펴보겠다.

 

Git은 데이터를 저장할 때 SHA-1 해시 함수를 사용하여 데이터의 해시 값을 계산한다. SHA-1 함수는 어떤 길이의 데이터라도 입력으로 받아 160비트의 고정 길이 출력을 생성하는데, 160비트를 16진수로 표현하면 아래와 같은 40자가 되는 것이다.

caec a268 56bc ef25 5d6c 0d78 9a73 df6d 84aa e0

 

이 40자의 키 값은 거의 고유하기 때문에, 식별자로 활용할 수 있다.

 

 

/Objects 에 저장되는 파일 형태

깃이 내부적으로 저장하는 오브젝트는 크게 blob, tree, commit 3가지가 있다.

blob

일반 파일의 내용을 보관한다. 파일의 메타 데이터가 아닌, 데이터 자체를 저장한다.

특징은 동일한 내용의 파일은 동일한 Blob 오브젝트를 공유한다는 것이다. 예를 들어, 여러 디렉토리에 동일한 내용의 파일이 존재하면, Git은 하나의 Blob 오브젝트만 생성한다.

tree

blob들이 어떤 디렉터리 위치에 어떤 속성과 이름으로 저장되는 지를 나타낸다. 쉽게 말해, 디렉토리 구조와 파일의 메타데이터를 저장함으로써 폴더 구조를 git에서도 관리해주는 역할이다.

commit

매 커밋 순간의 스냅샷을 가리킨다.

Commit 오브젝트는 커밋 메시지, 작성자 정보, 타임스탬프, 부모 커밋(이전 커밋)을 포함한다. 각 Commit 오브젝트는 프로젝트의 상태를 나타내는 Tree 오브젝트를 가르킨다.


INDEX

index 파일은 stage에 있는 파일들을 말한다.

 

stage란 무엇일까?

 

 

git add 명령을 수행 후 git status 를 찍어보면, stage 관련 용어들이 등장하는 것을 볼 수 있다. 즉 git add 명령 수행시 해당 파일들이 올라가는 곳이라고 생각할 수 있다.

 

 

Object와 Index를 잘 기억해두자. 깃 명령어 내부 동작을 파악할 때 주요한 개념이다.!

 


/ref

 refs에는 git에서 관리하는 branch들의 정보가 있다. 오늘 작업 내용에서 브랜치를 따로 만들지 않아 main 브랜치만 존재하는 것을 볼 수 있다.

 

 heads는 로컬, remotes는 원격 저장소를 가르킨다.

 


/logs

마찬가지로 각 브랜치 별 작업 목록이 로그로 기록된다.

 

 

내부를 열어보면 작업별 로그가 찍혀있는 것을 볼 수 있다.


HEAD

HEAD는 로컬 저장소가 현재 가르키는 브랜치를 참조한다. 현재 이미지에서는 main브랜치를 참조하는 것을 확인할 수 있다.

 

 


여기까지 깃의 내부 구조를 살펴보았다. 이제 이를 바탕으로 각 명령어가 어떻게 작동하는지 살펴보자.

 

 

 

 

git 명령어의 동작 원리 이해하기

우선 .git이 있는 곳에 새로운 파일이 추가되었다고 가정해보자

$ git status
현재 브랜치 main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        newfile.txt

nothing added to commit but untracked files present (use "git add" to track)

새로운 파일이 추적되지 않는다는 것을 git이 어떻게 알아차릴까?

Git이 새로운 파일을 추적되지 않는 파일로 인식하는 과정은 다음과 같다

  1. 현재 상태와 비교: git status 명령을 실행하면, Git은 현재 디렉토리의 상태와 Git의 index(또는 staging area)에 저장된 상태를 비교한다.
  2. 추적되지 않는 파일 식별: Git이 현재 디렉토리에서 index에 없는 파일을 찾으면, 이를 "Untracked files"로 분류한다. 이 파일들은 Git의 버전 관리에 포함되지 않았기 때문에 추적되지 않는 것으로 표시된다.

 

 

git add

git add 명령은 깃의 staging area에 올리는 것이다. add를 하게 되면 깃의 object 파일과 index 파일이 생성된다.

앞서 살펴본 것처럼 object 파일에는 파일의 내용이 담기고, index에는 object 파일의 이름과 add한 파일의 이름이 담기게 된다.

 

내부적으로 어떻게 동작하는지 조금 더 자세히 살펴보면, Blob 오브젝트 생성Index 업데이트로 살펴볼 수 있다.

 

  1. Blob 오브젝트 생성: git add를 실행하면, 새로운 파일의 내용이 Blob 오브젝트로 저장된다. 이 오브젝트의 이름은 파일 내용의 SHA-1 해시값이다.
  2. Index 업데이트: index 파일에는 파일의 이름과 Blob 오브젝트의 SHA-1 해시값이 저장된다. 이는 staging area에 어떤 파일들이 포함되어 있는지, 그리고 그 파일들이 어떤 내용을 가지고 있는지 추적하는 데 사용된다.

 

 

git commit

git add 명령을 통해 파일을 staging area에 올렸다면, 이제 git commit 명령을 사용하여 이 변경 사항을 로컬 저장소에 커밋할 수 있다. git commit은 크게 다음과 같은 과정을 거친다.

  1. Blob 오브젝트: git add 단계에서 생성된 Blob Object는 파일의 내용이 바뀌지 않는 한 그대로 사용된다.
  2. Tree 오브젝트 생성: Tree 오브젝트는 현재 디렉토리 구조를 나타내는 오브젝트이다. 각 디렉토리와 파일의 이름, Blob 오브젝트의 SHA-1 해시값이 포함된다. 커밋할 때마다 새로운 Tree 오브젝트가 생성된다.
  3. Commit 오브젝트 생성: Commit 오브젝트는 커밋 메시지, 작성자 정보, 현재 시간, 부모 커밋의 SHA-1 해시값(이전 커밋을 가리키는 포인터), 그리고 현재 Tree 오브젝트의 SHA-1 해시값을 포함한다. 새로운 커밋을 만들 때마다 이 Commit 오브젝트가 생성된다.

 

git push

git push 명령을 사용하면 로컬 저장소의 브랜치와 원격 저장소의 브랜치를 동기화한다. 로컬의 변경 내용을 원격 저장소로 push 하는 과정은 다음과 같다.

 

  1. 로컬 브랜치와 원격 브랜치 비교: 로컬 저장소와 원격 저장소의 상태를 비교하여 푸시할 커밋을 결정한다.
  2. 오브젝트 전송: 로컬 저장소에서 원격 저장소로 푸시할 오브젝트(Blob, Tree, Commit)를 전송한다
  3. 원격 브랜치 업데이트: 원격 저장소의 브랜치가 로컬 브랜치의 최신 커밋을 가리키도록 업데이트한다.

 

 

 

 

참고자료

https://tecoble.techcourse.co.kr/post/2021-07-08-dot-git/