[Github] git의 내부 구조 및 이론

Hood·2024년 11월 17일

Github

목록 보기
4/5
post-thumbnail

✍ 간단한 GitHub 사용법

GitHub를 계속 사용하다 보니 한 번쯤 정리해두면 좋겠다는 생각이 들어 이 글을 작성하게 되었습니다.
참고로 이 글은 Mac 환경을 기준으로 작성했습니다.


들어가며

프로젝트 협업을 하다 보면 Git을 자주 사용하게 됩니다.
그렇다면 Git은 협업 도구일까요, 아니면 버전 관리 도구일까요?

정리해서 말하면, Git 자체는 분산 버전 관리 시스템(Version Control System) 입니다.
그리고 GitHubGit 저장소를 원격으로 올리고, 공유하고, 협업할 수 있도록 도와주는 호스팅 서비스이자 협업 플랫폼에 가깝습니다.

즉, 버전 관리는 Git이 담당하고,
원격 저장소 공유와 협업 기능은 GitHub가 도와준다고 이해하시면 됩니다.


Git은 어떻게 많은 버전을 관리할까요?

Git은 커밋 단위로 프로젝트의 변경 이력을 관리합니다.
처음 보면 “커밋이 계속 쌓이면 프로젝트 크기가 엄청 커지지 않을까?”라는 생각이 들 수 있습니다.

하지만 실제로는 생각보다 비효율적으로 저장하지 않습니다.
Git은 프로젝트 전체를 매번 통째로 복사해 저장하는 방식이 아니라,
내용 기반 객체(object) 를 만들고, 이를 해시값으로 관리하면서 효율적으로 저장합니다.

즉, Git은 단순히 “파일을 복사해 백업하는 도구”가 아니라
객체와 참조 구조를 통해 변경 이력을 관리하는 시스템이라고 볼 수 있습니다.


Git 창시자의 표현

Git을 만든 리누스 토르발스는 초창기 README에서
Git을 다소 농담 섞인 표현으로 소개하기도 했습니다.

이 표현은 Git이 화려한 겉모습보다는,
내용을 해시로 추적하고 저장하는 단순하고 강력한 구조에 집중한 도구라는 점을 보여줍니다.

겉보기에는 단순해 보여도,
내부적으로는 매우 정교한 방식으로 파일과 커밋을 관리하고 있습니다.


Git 내부 구조

Git을 사용하려면 먼저 프로젝트 루트 디렉터리에서 저장소를 초기화합니다.

git init

이 명령어를 실행하면 .git이라는 숨김 디렉터리가 생성됩니다.
.git 디렉터리 안에는 Git이 버전 관리를 위해 사용하는 여러 정보가 저장됩니다.

Git의 내부 구조를 이해할 때 가장 중요한 객체는 보통 다음 세 가지입니다.

  • blob
  • tree
  • commit

이 세 객체를 중심으로 Git이 파일과 이력을 어떻게 관리하는지 살펴보겠습니다.


Git 스테이징

코드를 작성한 뒤 커밋하려면 먼저 스테이징(staging) 을 해야 합니다.

우리가 git add 명령어를 실행하면,
Git은 변경된 파일 내용을 다음 커밋에 포함할 대상으로 표시합니다.
이 과정에서 Git은 파일 내용을 기반으로 객체를 만들고,
스테이징 영역에는 “다음 커밋에 어떤 내용이 들어갈지”에 대한 정보가 반영됩니다.

즉, git add는 단순히 “업로드 버튼” 같은 것이 아니라,
다음 커밋에 포함할 파일 상태를 확정하는 과정이라고 볼 수 있습니다.


Blob

blob이란?

blob 오브젝트는 파일의 내용 자체를 저장하는 객체입니다.

여기서 중요한 점은, blob에는 파일 이름이나 디렉터리 정보가 들어 있지 않다는 것입니다.
즉, blob은 “이 파일이 어디에 있었는가”보다
“이 파일 내용이 무엇인가”에 집중한 객체입니다.

Git은 파일 내용을 저장할 때 그 내용을 바탕으로 해시값을 계산합니다.
전통적으로 이 해시는 SHA-1 기반으로 관리되어 왔고,
그 해시값을 기준으로 객체를 식별합니다.

그래서 같은 내용의 파일이라면,
파일 이름이 다르더라도 동일한 내용이라면 같은 blob으로 연결될 수 있습니다.

쉽게 말하면 blob
“파일 내용의 스냅샷” 이라고 이해하시면 됩니다.


Tree

프로젝트는 보통 여러 파일과 여러 디렉터리로 구성됩니다.
그래서 파일 내용만 저장한다고 해서 전체 프로젝트 구조를 알 수는 없습니다.

이때 사용하는 것이 tree 오브젝트입니다.

tree 오브젝트는 디렉터리를 표현하며,
그 안에는 다음과 같은 정보가 들어 있습니다.

  • 파일 이름
  • 디렉터리 이름
  • 각 항목이 가리키는 blob 또는 하위 tree
  • 파일 모드 정보

즉, tree
“프로젝트의 폴더 구조와 파일 배치 정보를 담고 있는 객체” 입니다.

쉽게 비유하면, blob이 파일 내용이라면
tree는 그 파일들이 어떤 이름으로 어떤 폴더 안에 들어 있는지를 보여주는 구조도에 가깝습니다.


Commit

스테이징된 내용을 바탕으로 하나의 커밋을 만들면,
Git은 commit 오브젝트를 생성합니다.

commit 오브젝트에는 보통 다음과 같은 정보가 담깁니다.

  • 최상위 tree 오브젝트 정보
  • 부모 commit 정보
  • 작성자(author) 정보
  • 커미터(committer) 정보
  • 타임스탬프
  • 커밋 메시지

즉, commit은 단순히 “저장 버튼”이 아니라
어느 시점의 프로젝트 구조(tree)를 가리키고 있는 기록 객체입니다.

쉽게 비유하면,
tree가 특정 시점의 프로젝트 파일 구조라면
commit은 그 구조에 대해 “누가, 언제, 왜 이 상태를 만들었는지”를 설명하는 메모라고 볼 수 있습니다.

그리고 각 커밋은 보통 이전 커밋을 함께 가리키기 때문에,
Git은 이 연결 구조를 따라 프로젝트의 변경 이력을 추적할 수 있습니다.


왜 commit은 고유할까요?

커밋 객체를 보면 다음과 같은 정보가 포함됩니다.

  • 루트 tree 객체의 해시값
  • 부모 commit의 해시값
  • 작성자와 커미터 정보
  • 작성 시각
  • 커밋 메시지

즉, 이 중 하나라도 달라지면 커밋의 내용도 달라지기 때문에
결과적으로 커밋 해시도 달라집니다.

그래서 커밋은 고유하게 식별될 수 있고,
Git은 이를 통해 특정 시점의 프로젝트 상태를 정확하게 추적할 수 있습니다.


merge 충돌은 왜 생길까요?

가끔 “부모 커밋 해시가 포함되어 있기 때문에 충돌이 난다”처럼 이해하기도 하는데,
실제로 merge conflict가 발생하는 직접적인 이유는 조금 다릅니다.

병합 충돌은 보통 서로 다른 브랜치에서 같은 파일의 같은 부분을 다르게 수정했을 때 발생합니다.
Git이 자동으로 어느 쪽 내용을 선택해야 할지 판단하지 못하면 충돌이 생기고,
그때 개발자가 직접 내용을 정리해야 합니다.

즉, 충돌의 핵심은
같은 지점을 서로 다르게 변경한 이력이 만났기 때문이라고 이해하시면 됩니다.


📌 정리

지금까지 Git의 내부 구조를 간단히 정리해보면 다음과 같습니다.

  • blob
    파일 내용 자체를 저장하는 객체

  • tree
    디렉터리 구조와 파일 이름, 그리고 어떤 blob/tree를 가리키는지 저장하는 객체

  • commit
    특정 시점의 tree와 부모 커밋, 작성자 정보, 메시지 등을 담는 객체

결국 Git은 파일을 통째로 단순 복사하는 방식이 아니라,
이러한 객체들을 만들고 서로 연결하면서 프로젝트의 이력을 관리합니다.

브랜치는 결국 특정 커밋을 가리키는 참조(reference) 이고,
우리는 그 참조를 움직이면서 프로젝트를 여러 흐름으로 나누어 작업하게 됩니다.

출처

profile
달을 향해 쏴라, 빗나가도 별이 될 테니 👊

0개의 댓글