Git 저장소 내부 구조와 직접 생성 원리
Git의 기본 객체 구조
Git은 파일 내용(blob), 폴더(tree), 커밋(commit)이라는 세 가지 핵심 객체로 이루어져 있습니다.
blob: 파일 자체의 내용
tree: 디렉토리 구조 및 파일/폴더 목록
commit:
프로젝트의 특정 시점 상태와 메시지, 메타데이터
tree 객체를 가리키는 포인터를 가진다.
작업트리와 인덱스(스테이징 영역)
Git으로 작업할 때는 주로 작업트리에서 파일을 수정합니다. 이 변화는 곧바로 저장소에 반영되지 않고, '인덱스('스테이징 영')'라는 중간 영역에 미리 올립니다. 인덱스는 나중에 커밋할 파일들을 잠시 모아놓는 곳입니다. 그래서 git add 명령으로 인덱스에 올리고, 이후 커밋하면 해당 변화가 이런 순서로 기록됩니다.
tree -f .git
저장소 기본 구성요소



최소한의 git 저장소는 .git 폴더 내부에
objects: 실제 git 객체(blob, tree, commit 등)를 저장refs: 객체들을 가리키는 '이름'(예: 브랜치)을 관리refs/heads: 브랜치 정보를 파일로 관리 가 반드시 있어야 합니다.
HEAD와 브랜치의 개념
HEAD 파일은 현재 작업 중인 브랜치가 무엇인지 알려주는 역할을 합니다. HEAD의 내용은 "ref: refs/heads/master"와 같이 브랜치 위치를 가리킵니다. 브랜치 자체는 단순히 이름에 해당하는 파일(refs/heads/master)에 커밋의 SHA-1 해시를 기록한 것입니다.

Git Status를 통해 현재 활성된 브랜치가 무엇인지 알기 위해, HEAD를 찾는다.
HEAD는 실제로 파일이다. 이를 만들어보자
HEAD는 refs/head/master를 가르키고 있다는 내용으로 작성한다.


다시 git status를 통해 확인해보면, 정상적인 상태로 나온다.
master 브랜치를 실제로 만들지 않았지만 git은 master 브랜치에 위치한다(가리킨다)고 알고 있다는 것을 알 수 있다.
하지만 git log를 수행하면 커밋이 없기에 정상적으로 보이지 않는다.

플러밍 명령어와 포셀린 명령어의 차이
Git 명령어는 두 가지 층위가 있습니다.
포셀린(Porcelain): 사용자 친화적 인터페이스 (예: git init, git add, git commit 등)
플러밍(Plumbing): 내부 구조에 직접 접근하는 저수준 명령어 (예: git hash-object, git cat-file 등) 저수준 명령을 직접 사용하면 저장소 내부 작업 원리를 깊게 이해할 수 있습니다.
여기까지 포셀린 명령어를 통해 git에 친숙해졌고, 이제 플러밍 명령어를 사용하고자 한다.
블롭 생성과 저장 방식
Git에서 새 파일 내용을 저장할 때, 파일 전체를 blob 객체로 만들어 저장합니다. 변경된 부분만 따로 기록하지 않고, 파일 전체를 해시로 구분된 객체로 관리합니다. 디렉토리 구조는 SHA-1 해시의 앞 2자리로 폴더를 만들고, 나머지를 파일 이름으로 하는 구조입니다. 이렇게 다수 파일이 있을 때 검색 속도가 훨씬 빨라집니다.
실습) 실제 파일 시스템의 파일을 만드는 것보다 블롭을 만들고 그 내용을 객체 데이터 베이스에 작성하려고 한다. 이를 하는데 실제 파일이 존재할 필요는 없고, 플러밍 명령어로 처리 할수 있다.

Git 객체(Object)의 해시를 계산하고, 필요하다면 Git 객체 데이터베이스(.git/objects)에 저장하는 명령어인 git hash-object 를 이용해 blob을 저장해보자
echo Brief channel is aswome
문자열
"Brief channel is aswome"을 표준 출력(stdout)으로 보냅니다.즉, 이 문장이 다음 명령어의 입력으로 들어갑니다.
2. git hash-object
Git 객체(Object)의 해시를 계산하고, 필요하다면 Git 객체 데이터베이스(
.git/objects)에 저장하는 명령어입니다.기본적으로 SHA-1 해시(최근엔 SHA-256 옵션도 있음)를 반환합니다.
Git이 내부적으로 파일, 커밋, 트리 등을 관리할 때 쓰는 방식 그대로 동작합니다.
3. --stdin
입력을 파일이 아니라 표준 입력(stdin) 으로부터 받겠다는 뜻입니다.
여기서는
echo의 출력이 표준 입력으로 전달됩니다. 따라서"Brief channel is aswome"라는 문자열을 Git 객체화 대상으로 삼습니다.
4. -w (write)
단순히 해시만 계산하는 게 아니라, 실제로 Git 객체 DB에 저장하라는 옵션입니다.
.git/objects/디렉토리에 압축된 blob 객체 파일이 생성됩니다.
실행 결과
출력: 입력 문자열을 blob 객체로 만들었을 때의 해시 값(SHA-1 또는 SHA-256) 을 출력합니다. 예:
12afd3090092069b609372fd6279a82ce574bdae내부 동작:
Git은
"Brief channel is aswome\n"(주의:echo는 보통 줄바꿈까지 넣습니다) 내용을 blob 객체로 만듭니다.blob <크기>0<내용>형식으로 저장하고 SHA-1 해시를 계산합니다.그 결과 파일은
.git/objects/12/afd3090092069b609372fd6279a82ce574bdae같은 경로에 저장됩니다.해시의


git cat-file
Git 내부 객체(커밋, 트리, 태그, 블롭 등)의 내용을 검사할 수 있는 도구입니다.
Git이 저장소 내부에서 관리하는 객체들은 해시값(예: SHA-1)으로 식별됩니다.
git cat-file은 이 해시를 기반으로 객체의 타입이나 내용을 출력할 수 있습니다.
2. 옵션 -t
-t는 type을 뜻합니다.지정한 해시값의 Git 객체가 어떤 타입인지 출력합니다.
가능한 타입:
blob→ 파일 내용tree→ 디렉토리 구조commit→ 커밋 객체tag→ 태그 객체

-p 는 pretty print을 뜻합니다.
지정한 해시값의 Git 객체의 내용을 출력합니다.
이제 git add를 사용하여 무언가를 staging 할때,
Git은 파일의 변경 사항만이 아닌, 파일 내용 전체를 Blob으로 생성하고 데이터 베이스에 저장하게 된다.
지금까지 git add(포셀린 명령) 대신해 git hash-object(플러밍 명령)으로 Blob을 생성하고, 데이터 베이스에 저장했다. 그리고 git cat-file로 데이터 베이스에 저장된 Blob을 확인했다.
하지만, git status를 해보면, commit된 내용이 없다는 것이 보인다. 무언가를 더 해야한다.

가지고 있는 하나의 블롭을 스테이징하기 위해 , 이제 index 추가를 해보자.
git update-index --add --cacheinfo 명령을 사용해야 한다. 이는 인덱스(stage)에 특정 파일 객체(blob)을 직접 등록하는 저수준 명령어 입니다.

git update-index --add --cacheinfo 100644 <blob_sha1> <파일명>--add: 인덱스에 새 항목을 추가하는 옵션.
--cacheinfo: 파일 모드, blob 해시, 경로를 직접 지정하여 인덱스에 항목을 삽입.
<mode> ( 100644 )
OSIX 파일 권한에 대응하는 Git의 파일 모드.
100644→ 일반 파일, 읽기/쓰기100755→ 실행 파일120000→ 심볼릭 링크
<object>: Git 객체 데이터베이스 안에 있는 blob의 SHA-1 해시
<path>: 워킹 디렉토리 경로 이름.(실제 파일이 없어도 인덱스에 이 이름으로 연결됨)
index가 생성되었다.!


git status로 확인해보자

녹색은 brief.txt 라는 파일을 스테이징 하고 있음을 나타낸다.
빨간색은 brief.txt 파일이 삭제되었다 해당 사실은 스테이징 되지 않았음을 나타낸다.
실제로 우리는 해당 파일을 생성한적이 없는 파일에 대해 blob 객체를 생성하고 스테이징 했기 때문이다.
이를 해결하고자 파일시스템에 brief.txt 를 작성해야한다. brief.txt의 내용은 blob을 활용한다.

blob객체의 내용을 출력하고 이를 brief.txt로 작성하는 명령이다.
다시 git status로 확인한다. 이로써 커밋할 준비가 되었다.

인덱스 추가와 관리
인덱스는 .git 폴더의 index 파일에 존재하며, 파일의 이름과 blob 객체를 연결합니다. 직접 플러밍 명령어로 인덱스를 갱신할 수도 있습니다. 예:
git update-index --add --cacheinfo 100644 <blob_sha1> <파일명>커밋을 위해, 인덱스(스테이징)영역으로부터 커밋 객체를 만들어보자. 하지만 커밋 객체는 트리에 대한 참조를 가지므로 먼저 트리를 만들어야 한다.
트리를 만들기 위해서는 git write-tree 명령을 사용한다.
디렉토리 구조를 반영해서 하위 트리 객체를 재귀적으로 만들어냄.
Blob은 이미 존재하기 때문에 단순히 참조를 기록함.

트리 객체가 생성되었고(objects 디렉토리 하위 생성), 트리 객체의 내용으로 blob 객체를 가리키고 있다.
git status 를 확인해보자

아직 커밋을 하지 않았기에 별 다른 점은 없다. 커밋 객체를 만들자
커밋 객체는 tree 객체를 가지는 포인트를 가지는 것으로, git commit-tree 명령어를 사용한다.
git commit-tree <tree_sha1> [-p <parent_sha1> ...] < <message_file><tree_sha1> : git write-tree로 얻은 트리 객체 해시.
우리가 방금 만든 tree의 객체의 해시
-p <parent_sha1> : 부모 커밋 해시(없으면 루트 커밋이 됨).
우리는 최초의 커밋이므로 부모 커밋 해시는 없다.
<message_file> : 커밋 메시지



상태를 확인해보자. 이런 아무런 변화가 없다

왜지? 강의자는 Git은 어떤 커밋을 찾아야할지 모르기때문이라고 하며, Git이 특정 커밋을 어떻게 찾느냐고 물어본다. (이게 뭔말이여 )
Git은 특정 커밋을 찾기위해 HEAD를 본다. HEAD의 내용을 보고 master 브런치로 간다.

HEAD가 refs/heads/master를 가리키는 것을 알수 있다.


하지만 우리는 master 브랜치를 생성한적이 없기 때문에 참조헤드만 존재하는 것을 알수 있다.
master 브랜치는 무엇인가? 이 또한 다른 것과 마찬가지로 파일이며, 현재 가리키는 커밋 객체의 해시를 내용으로 포함한다.
그래서 우리는 master를 작성해야 하며 방금 만든 커밋 객체의 해시를 내용으로 작성한다.


참고로, master를 작성한 명령은 대체하는 plumbing 명령은 존재한다. 나중에 설명하기 위해 사용하지는 않는다.

상태를 보면, 이제 더이상 커밋할 것이 없음을 알수 있다!!

정리하면, branch 라는 것은 .git/ref/heads 내부의 파일일 뿐이며, 해당 커밋 객체의 해시를 포함한다.( 커밋에 대한 명명된 참조)
git log를 확인하면

트리 객체와 커밋
객체의 생성 과정
커밋 전에는 인덱스의 내용을 하나의 tree 객체로 저장합니다. 이 tree 객체는 파일 및 하위 폴더의 목록을 포함합니다. 그 다음, 커밋 객체를 만들 때 tree 객체를 참조하고, 커밋 메시지, 작성자 등의 정보를 넣습니다. 커밋이 최초라면 부모 커밋 정보는 없습니다. 커밋 객체의 생성 역시 플러밍 명령어로 가능하며, 새 커밋의 SHA-1 값을 브랜치 파일에 직접 기록하면 브랜치가 해당 커밋을 가리키게 됩니다.
브랜치 생성과 전환 방식
브랜치는 사실 .git/refs/heads 내의 파일입니다. 새 브랜치를 생성하려면 해당 디렉토리에 커밋 SHA-1 값을 담은 파일을 직접 만들면 됩니다. HEAD 파일의 내용을 변경해주면 브랜치를 전환할 수 있습니다.
커밋과 브랜치의 연결 원리
커밋 객체를 만든 후, 브랜치 파일에 해당 커밋 SHA-1을 저장하면 HEAD → 브랜치 파일 → 커밋 객체 → 이전 커밋 체인 이런 식으로 데이터가 연결됩니다. 이 원리를 직접 따라해보면 Git의 내부 동작 방식이 한눈에 들어옵니다.
Q. git 내부에 어떤 두개의 디렉토리가 있어야 하는가?
objects: 객체( blob, tree, commit) 들을 보관
refs: 객체에 대한 참조 또는 객체의 이름을 보관
Q. .git/obejcts 내 디렉토리 구조
해시의 첫 번째 바이트를 이름으로 가지는 디렉토리와 그 하위에 나머지 바이트들을 이름으로 가지는 디렉토리
검색속도를 빠르게 하기 위함

Q. blob의 생성 시점
index( staging 영역)에 추가할 때 생성된다.
Q. git add 사용시 어디에 그것들이 저장되는가?
.git/index 파일에 해시가 추가된다.
자 이제 저장소와 커밋을 만든 것처럼 브랜치를 만들어보자
( porcelain 명령을 사용하지 않고, 브랜치를 만들고, 전환해보자 )
test 라는 이름의 브랜치를 만들자( 이는 git branch test 와 동일하다 )
브랜치는 단순히, .git/ref/heads내 파일이라는 것을 이미 배웠다
master를 만들 때 처럼 test 브랜치도 만들자


test 브랜치로 전환 해보자 ( git checkout test 처럼 )
출처 및 참고 :
git hash-object → git update-index --cacheinfo → git write-tree → git commit-tree 로 연결








