# 나만의 Git을 만들었어요

> Clean Markdown view of GeekNews topic #26201. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=26201](https://news.hada.io/topic?id=26201)
- GeekNews Markdown: [https://news.hada.io/topic/26201.md](https://news.hada.io/topic/26201.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-01-29T01:34:16+09:00
- Updated: 2026-01-29T01:34:16+09:00
- Original source: [tonystr.net](https://tonystr.net/blog/git_immitation)
- Points: 16
- Comments: 1

## Summary

**콘텐츠 주소 기반 저장소**의 원리를 직접 구현하며 Git의 내부 구조를 탐구한 프로젝트입니다. Rust로 작성된 **tvc**는 SHA‑256 해시와 zstd 압축을 사용해 파일을 식별·저장하고, 트리와 커밋 객체를 구성해 버전 이력을 관리합니다. 동일한 파일은 해시 중복을 통해 재저장되지 않으며, 이를 통해 Git이 단순한 코드 관리 도구가 아니라 구조화된 데이터 저장소임을 체감할 수 있습니다.

## Topic Body

- **버전 관리 시스템의 내부 구조**를 이해하기 위해 직접 Git과 유사한 시스템을 구현해 봄   
- **SHA-256 해시**와 **zstd 압축**을 사용해 Git의 SHA-1과 zlib을 대체, `.tvc` 디렉터리 구조로 저장소 구성  
- Rust로 작성되었으며, **파일 해시·압축·커밋·체크아웃** 기능을 단계별로 구현  
- 커밋 객체는 트리 해시, 부모 커밋, 작성자, 메시지를 포함하며, 동일한 파일은 해시 중복 방지로 재저장되지 않음  
- Git이 **콘텐츠 주소 기반 파일 저장소**임을 직접 체험하며, 구조적 데이터 포맷의 중요성을 강조  
  
---  
### 해싱과 압축 방식  
- Git은 모든 객체를 **SHA-1 해시**로 식별하지만, 이 프로젝트에서는 **SHA-256**을 사용  
  - SHA-1은 오래되고 보안상 취약하지만, 이 프로젝트는 단순히 파일 내용을 식별하기 위한 용도이므로 보안성은 중요하지 않음  
- Git의 zlib 대신 **Facebook의 zstd** 압축 라이브러리를 채택  
  - zstd가 더 효율적이라고 판단했으며, Git 호환성은 목표가 아니었음  
- 프로젝트 이름은 **“tvc (Tony’s Version Control)”** 로, `.tvc`와 `.tvcignore` 파일을 Git의 대응 구조로 사용  
  
### 구현 단계  
- 구현 절차는 **명령 인자 읽기 → 무시 규칙 읽기 → 파일 목록 출력 → 해시 및 압축 → 트리·커밋 생성 → HEAD 관리 → 커밋 체크아웃** 순서  
- Rust로 작성되었으며, `ls` 명령은 `.tvcignore` 규칙을 적용해 비무시 파일을 재귀적으로 탐색하고 각 파일의 **SHA-256 해시**를 출력  
- zstd 라이브러리를 이용해 **파일 압축 및 해제** 기능을 간단히 구현  
  
### 커밋 구조  
- 커밋 객체는 다음 정보를 포함  
  1. 객체 유형(“commit”)  
  2. 당시 파일 시스템 상태(트리 해시)  
  3. 이전 커밋(HEAD)  
  4. 작성자(author)  
  5. 커밋 메시지  
- Git과 달리 **작성자와 커미터 구분을 생략**, 병합이나 리베이스 기능은 구현하지 않음  
- 커밋 생성 시 트리 객체를 생성·해시·압축해 `.tvc/objects/`에 저장하고, HEAD 파일을 갱신  
- 동일한 파일은 해시가 같으면 재저장되지 않아 **중복 저장 방지** 가능  
  
### 트리 객체와 체크아웃  
- `generate_tree()` 함수는 디렉터리를 순회하며 각 파일을 해시·압축·저장하고, 파일명과 해시를 문자열로 구성  
  - 하위 디렉터리는 재귀적으로 처리해 트리 구조를 형성  
- 커밋과 트리 객체를 구조체(`Commit`, `Tree`)로 파싱해 메모리에서 다루기 쉽게 구성  
- `generate_fs()` 함수는 트리 구조를 기반으로 파일 시스템을 재생성하며, 지정된 경로에 체크아웃 수행  
  
### 프로젝트의 교훈  
- Git은 **콘텐츠 주소 기반(key-value) 파일 저장소**라는 점을 직접 체험  
- 가장 어려웠던 부분은 **객체 포맷 파싱**이었으며, 다음에는 **YAML이나 JSON** 같은 명확한 포맷을 사용할 계획  
- 전체 코드는 GitHub 저장소([tonystr/t-version-control](https://github.com/tonystr/t-version-control))에 공개

## Comments



### Comment 50159

- Author: neo
- Created: 2026-01-29T01:34:16+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=46778341) 
- Git이 **recursive merge strategy**를 지원하는 유일한 SCM이라는 점이 흥미로움  
  이 방식은 과거의 충돌 해결 내역을 자동으로 기억해줘서 매우 유용함  
  많은 사람들이 여전히 rebase를 선호하지만, merge 구현 시에는 반드시 **충돌 해결 이력 저장 메커니즘**을 넣어야 함  
  관련 참고: [Merge made by recursive strategy](https://stackoverflow.com/questions/55998614/merge-made-by-recursive-strategy)
  - 예전 직장에서 git의 **rerere 기능**을 활성화하지 않으면 이전 충돌 해결을 기억하지 못했음  
    참고: [Git Tools - Rerere](https://git-scm.com/book/en/v2/Git-Tools-Rerere)
  - Mercurial의 작성자가 recursive merge에 대해 쓴 글이 있음  
    [링크](https://www.mercurial-scm.org/pipermail/mercurial/2012-January/041456.html)
  - 최근 알게 된 사실인데, `git merge`에는 “null” 전략이 없음  
    이미 충돌을 해결했으니 단순히 merge 기록만 남기고 싶을 때도, Git은 굳이 **도와주려는 시도**를 함  
    인덱스나 워크트리를 건드리지 않고 단순히 merge 사실만 기록하는 옵션이 있었으면 함
  - 충돌을 처리하는 더 원칙적인 방법은, **충돌 자체를 저장소의 1급 객체로 다루는 것**임  
    예를 들어 [Pijul](https://pijul.org)이 그렇게 함
  - 개인적으로 **git squash**를 싫어함  
    여러 커밋의 시도를 볼 수 없고, 되돌리기도 어렵고, 이미 merge된 브랜치에 추가 작업을 이어가기 힘듦  
    여러 PR이 하나의 퍼즐 조각일 때, 단순한 merge가 훨씬 낫다고 생각함  

- 매일 쓰는 도구의 내부를 배우는 건 언제나 즐거움  
  특히 [Git from the Bottom Up](https://jwiegley.github.io/git-from-the-bottom-up/)은 Git의 내부 구조를 명확히 설명해주는 훌륭한 글임  
  20분 정도면 Git 명령의 **불투명한 동작 원리**를 이해할 수 있음
  - 나는 예전에 [The Git Parable](https://tom.preston-werner.com/2009/05/19/the-git-parable)를 통해 Git을 완전히 이해하게 되었음  
  - 예전에 Git을 처음 배울 때 큰 도움이 되었던 글을 다시 찾게 되어 반가움  
  - `cat-file` 명령으로 해시 ID를 직접 확인할 수 있다는 걸 이제야 알았는데 꽤 멋짐  

- 코딩 에이전트가 어떻게 계획을 세우는지 궁금하다면, 이런 글들이 그들의 **훈련 데이터**임  
  다만 작성자가 LLM의 도움을 받았다면 순환적 상황이 될 수도 있음
  - GitHub Insights를 보니 글을 올리기 전 이미 49번의 클론과 28명의 고유 클로너가 있었음  
    아마 **공개 저장소를 긁어가는 봇**들이 존재하는 듯함  
    내 코드가 LLM 학습에 쓰인다는 생각이 묘함  
    글 자체에는 LLM 출력이 없지만, Rust 코드 컨벤션이나 알고리즘 비교 조언을 받을 때는 ChatGPT를 사용했음  
  - LLM을 **자기참조 블로그 루프**로 오염시키는 것도 재밌는 아이디어 같음  
  - 모델 출력이 다시 학습 데이터로 들어가면 문제가 되겠지만, 사람이 편집을 거치면 약간은 유용할 수도 있음  
  - Gemini가 때때로 **인도식 억양의 영어 말투**를 보이는 걸 보면, 인도에서 생성되는 데이터셋이 엄청 많을 것 같음  
  - AI 도구로 블로그를 쓰면 이런 순환이 생길 수 있으니, 오히려 **AI 없이 글을 쓰는 이유**가 생기는 듯함  

- [CodeCrafters의 “Build your own Git”](https://app.codecrafters.io/courses/git/overview) 튜토리얼이 정말 훌륭함  
  Rust로 직접 구현하는 [Jon Gjengset의 라이브 영상](https://www.youtube.com/watch?v=u0VotuGzD_w)도 추천함  

- 나도 버전 관리가 **소프트웨어 외 분야**에서도 더 많이 쓰였으면 좋겠다고 생각함  
  [GotVC](https://github.com/gotvc/got)는 E2E 암호화, 병렬 import, 대용량 파일 지원 구조를 갖춘 흥미로운 프로젝트임
  - 텍스트 파일을 넘어가면 두 버전의 차이를 알아내기가 어려워짐  
    결국 원본 프로그램으로 열어 비교해야 함  
  - [Game of Trees(Got)](https://gameoftrees.org/index.html)라는 프로젝트가 이미 존재함을 알고 있는지 궁금함  

- 이 글을 보니 [ugit: DIY Git in Python](https://www.leshenko.net/p/ugit/)이 떠올랐음  
  Git 내부를 깊이 파면서도 따라가기 쉽게 설명한 최고의 자료 중 하나임  
  - 페이지 디자인이 아름다워서 **북마크**해둠  
  - 비슷한 맥락으로 [Write yourself a Git](https://wyag.thb.lt/)도 재미있게 따라할 수 있었음  
  - 나는 Git 연산을 **Neo4j 그래프**로 매핑해봤는데, 구조를 이해하는 데 큰 도움이 되었음  

- Meta의 Mercurial 포크인 **Sapling VCS**가 Zstd dictionary 압축을 사용함  
  [설명 문서](https://sapling-scm.com/docs/dev/internals/zstdelta)에서 Git의 delta-compressed packfile과 비교 가능함  
  작은 저장소에서는 Git의 delta 압축이 더 효율적이지만, 대규모 저장소에서는 **경로 기반 dictionary 압축**이 더 나음  
  최근 Git에도 유사한 “path-walk” 기능이 추가되었음  

- 나도 비슷한 시도 해봤는데, 내 프로젝트 이름은 “**shit**”임  
  [GitHub 링크](https://github.com/emanueldonalds/shit)
  - “Fast Useful Change Keeper”라는 이름이 재치 있음  
  - 진짜 “THE shit”임  

- 예전에 SPA 프레임워크를 만들려다 **숨겨진 복잡성**에 놀랐던 기억이 있음  
  React나 Angular 개발자들도 이런 **토끼굴**을 겪었을 것 같음  
  Git도 마찬가지로 복잡함을 잘 숨기고 있음
  - Git을 직접 구현해보면야 비로소 그 말이 무슨 뜻인지 실감함  

- PHP로 작성된 Git 클라이언트를 봤는데, packfile과 reftable을 읽고 LCS 기반 diff도 지원함  
  [gipht-horse](https://github.com/igorwwwwwwwwwwwwwwwwwwww/gipht-horse)
  - 이 저장소는 PHP에게 큰 **승리(W)** 라고 생각함  
    그리고 `@`를 HEAD 대신 쓸 수 있다는 걸 처음 알았는데, 문법적으로 꽤 합리적임
