14P by GN⁺ 2일전 | ★ favorite | 댓글 1개
  • 버전 관리 시스템의 내부 구조를 이해하기 위해 직접 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)에 공개
Hacker News 의견들
  • Git이 recursive merge strategy를 지원하는 유일한 SCM이라는 점이 흥미로움
    이 방식은 과거의 충돌 해결 내역을 자동으로 기억해줘서 매우 유용함
    많은 사람들이 여전히 rebase를 선호하지만, merge 구현 시에는 반드시 충돌 해결 이력 저장 메커니즘을 넣어야 함
    관련 참고: Merge made by recursive strategy

    • 예전 직장에서 git의 rerere 기능을 활성화하지 않으면 이전 충돌 해결을 기억하지 못했음
      참고: Git Tools - Rerere
    • Mercurial의 작성자가 recursive merge에 대해 쓴 글이 있음
      링크
    • 최근 알게 된 사실인데, git merge에는 “null” 전략이 없음
      이미 충돌을 해결했으니 단순히 merge 기록만 남기고 싶을 때도, Git은 굳이 도와주려는 시도를 함
      인덱스나 워크트리를 건드리지 않고 단순히 merge 사실만 기록하는 옵션이 있었으면 함
    • 충돌을 처리하는 더 원칙적인 방법은, 충돌 자체를 저장소의 1급 객체로 다루는 것
      예를 들어 Pijul이 그렇게 함
    • 개인적으로 git squash를 싫어함
      여러 커밋의 시도를 볼 수 없고, 되돌리기도 어렵고, 이미 merge된 브랜치에 추가 작업을 이어가기 힘듦
      여러 PR이 하나의 퍼즐 조각일 때, 단순한 merge가 훨씬 낫다고 생각함
  • 매일 쓰는 도구의 내부를 배우는 건 언제나 즐거움
    특히 Git from the Bottom Up은 Git의 내부 구조를 명확히 설명해주는 훌륭한 글임
    20분 정도면 Git 명령의 불투명한 동작 원리를 이해할 수 있음

    • 나는 예전에 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” 튜토리얼이 정말 훌륭함
    Rust로 직접 구현하는 Jon Gjengset의 라이브 영상도 추천함

  • 나도 버전 관리가 소프트웨어 외 분야에서도 더 많이 쓰였으면 좋겠다고 생각함
    GotVC는 E2E 암호화, 병렬 import, 대용량 파일 지원 구조를 갖춘 흥미로운 프로젝트임

    • 텍스트 파일을 넘어가면 두 버전의 차이를 알아내기가 어려워짐
      결국 원본 프로그램으로 열어 비교해야 함
    • Game of Trees(Got)라는 프로젝트가 이미 존재함을 알고 있는지 궁금함
  • 이 글을 보니 ugit: DIY Git in Python이 떠올랐음
    Git 내부를 깊이 파면서도 따라가기 쉽게 설명한 최고의 자료 중 하나임

    • 페이지 디자인이 아름다워서 북마크해둠
    • 비슷한 맥락으로 Write yourself a Git도 재미있게 따라할 수 있었음
    • 나는 Git 연산을 Neo4j 그래프로 매핑해봤는데, 구조를 이해하는 데 큰 도움이 되었음
  • Meta의 Mercurial 포크인 Sapling VCS가 Zstd dictionary 압축을 사용함
    설명 문서에서 Git의 delta-compressed packfile과 비교 가능함
    작은 저장소에서는 Git의 delta 압축이 더 효율적이지만, 대규모 저장소에서는 경로 기반 dictionary 압축이 더 나음
    최근 Git에도 유사한 “path-walk” 기능이 추가되었음

  • 나도 비슷한 시도 해봤는데, 내 프로젝트 이름은 “shit”임
    GitHub 링크

    • “Fast Useful Change Keeper”라는 이름이 재치 있음
    • 진짜 “THE shit”임
  • 예전에 SPA 프레임워크를 만들려다 숨겨진 복잡성에 놀랐던 기억이 있음
    React나 Angular 개발자들도 이런 토끼굴을 겪었을 것 같음
    Git도 마찬가지로 복잡함을 잘 숨기고 있음

    • Git을 직접 구현해보면야 비로소 그 말이 무슨 뜻인지 실감함
  • PHP로 작성된 Git 클라이언트를 봤는데, packfile과 reftable을 읽고 LCS 기반 diff도 지원함
    gipht-horse

    • 이 저장소는 PHP에게 큰 승리(W) 라고 생각함
      그리고 @를 HEAD 대신 쓸 수 있다는 걸 처음 알았는데, 문법적으로 꽤 합리적임