16 คะแนน โดย GN⁺ 2026-01-29 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • เพื่อทำความเข้าใจ โครงสร้างภายในของระบบควบคุมเวอร์ชัน จึงได้ลองลงมือสร้างระบบที่คล้าย Git ขึ้นมาเอง
  • ใช้ แฮช SHA-256 และ การบีบอัด zstd แทน SHA-1 และ zlib ของ Git โดยจัดโครงสร้างรีโพซิทอรีไว้ในไดเรกทอรี .tvc
  • เขียนด้วย Rust และค่อย ๆ พัฒนาฟังก์ชัน แฮชไฟล์·บีบอัด·คอมมิต·เช็กเอาต์ ทีละขั้น
  • อ็อบเจ็กต์คอมมิตประกอบด้วยแฮชของทรี, คอมมิตแม่, ผู้เขียน และข้อความคอมมิต โดยไฟล์ที่เหมือนเดิมจะไม่ถูกเก็บซ้ำหากแฮชตรงกัน
  • ได้สัมผัสด้วยตัวเองว่า Git คือ ที่เก็บไฟล์แบบอ้างอิงตามเนื้อหา และย้ำถึงความสำคัญของฟอร์แมตข้อมูลที่มีโครงสร้าง

วิธีการแฮชและบีบอัด

  • Git ใช้ แฮช SHA-1 เพื่อระบุอ็อบเจ็กต์ทั้งหมด แต่ในโปรเจกต์นี้ใช้ SHA-256
    • SHA-1 นั้นเก่าและมีจุดอ่อนด้านความปลอดภัย แต่โปรเจกต์นี้ใช้เพียงเพื่อระบุเนื้อหาไฟล์ จึงไม่ได้ให้ความสำคัญกับความปลอดภัยมากนัก
  • เลือกใช้ไลบรารีบีบอัด zstd ของ Facebook แทน zlib ของ Git
    • มองว่า zstd มีประสิทธิภาพดีกว่า และไม่ได้ตั้งเป้าให้เข้ากันได้กับ Git
  • ชื่อโปรเจกต์คือ “tvc (Tony’s Version Control)” โดยใช้ไฟล์ .tvc และ .tvcignore เป็นโครงสร้างคู่กันกับของ Git

ขั้นตอนการพัฒนา

  • ลำดับการพัฒนาคือ อ่านอาร์กิวเมนต์คำสั่ง → อ่านกฎการ ignore → แสดงรายการไฟล์ → แฮชและบีบอัด → สร้างทรี·คอมมิต → จัดการ HEAD → เช็กเอาต์คอมมิต
  • เขียนด้วย Rust โดยคำสั่ง ls จะใช้กฎจาก .tvcignore เพื่อไล่สำรวจไฟล์ที่ไม่ถูก ignore แบบรีเคอร์ซีฟ และแสดง แฮช SHA-256 ของแต่ละไฟล์
  • ใช้ไลบรารี zstd เพื่อทำฟังก์ชัน บีบอัดและคลายบีบอัดไฟล์ ได้อย่างเรียบง่าย

โครงสร้างคอมมิต

  • อ็อบเจ็กต์คอมมิตประกอบด้วยข้อมูลต่อไปนี้
    1. ประเภทอ็อบเจ็กต์ (“commit”)
    2. สถานะของระบบไฟล์ในขณะนั้น (แฮชทรี)
    3. คอมมิตก่อนหน้า (HEAD)
    4. ผู้เขียน (author)
    5. ข้อความคอมมิต
  • ต่างจาก Git ตรงที่ ละการแยกระหว่าง author และ committer และไม่ได้พัฒนาฟีเจอร์ merge หรือ rebase
  • เมื่อสร้างคอมมิต จะสร้างอ็อบเจ็กต์ทรี ทำแฮช บีบอัด แล้วเก็บไว้ใน .tvc/objects/ พร้อมอัปเดตไฟล์ HEAD
  • ไฟล์ที่เหมือนกันจะไม่ถูกเก็บซ้ำหากแฮชตรงกัน จึงช่วย ป้องกันการเก็บข้อมูลซ้ำซ้อน ได้

ทรีอ็อบเจ็กต์และการเช็กเอาต์

  • ฟังก์ชัน generate_tree() จะไล่วนผ่านไดเรกทอรี ทำแฮช·บีบอัด·จัดเก็บแต่ละไฟล์ และประกอบชื่อไฟล์กับแฮชเป็นสตริง
    • ไดเรกทอรีย่อยจะถูกประมวลผลแบบรีเคอร์ซีฟเพื่อสร้างโครงสร้างทรี
  • พาร์สอ็อบเจ็กต์คอมมิตและทรีเป็นสตรักต์ (Commit, Tree) เพื่อให้จัดการในหน่วยความจำได้สะดวก
  • ฟังก์ชัน generate_fs() จะสร้างระบบไฟล์ขึ้นมาใหม่จากโครงสร้างทรี และทำการเช็กเอาต์ไปยังพาธที่กำหนด

บทเรียนจากโปรเจกต์

  • ได้สัมผัสด้วยตัวเองว่า Git คือ ที่เก็บไฟล์แบบอ้างอิงตามเนื้อหา (key-value)
  • ส่วนที่ยากที่สุดคือ การพาร์สฟอร์แมตของอ็อบเจ็กต์ และครั้งหน้ามีแผนจะใช้ฟอร์แมตที่ชัดเจนอย่าง YAML หรือ JSON
  • โค้ดทั้งหมดเปิดเผยไว้ใน GitHub repository (tonystr/t-version-control)

1 ความคิดเห็น

 
GN⁺ 2026-01-29
ความเห็นจาก Hacker News
  • น่าสนใจที่ Git เป็น SCM ตัวเดียวที่รองรับ recursive merge strategy
    วิธีนี้มีประโยชน์มากเพราะมันจำประวัติการแก้ conflict ในอดีตได้โดยอัตโนมัติ
    หลายคนยังคงชอบ rebase มากกว่า แต่ถ้าจะทำระบบ merge ก็ควรมี กลไกเก็บประวัติการแก้ conflict ไว้เสมอ
    ดูเพิ่มเติม: Merge made by recursive strategy

    • ที่ทำงานเก่าของผม ถ้าไม่เปิดใช้ ฟีเจอร์ rerere ของ git มันจะจำการแก้ conflict ก่อนหน้าไม่ได้
      ดูเพิ่มเติม: Git Tools - Rerere
    • มีบทความที่ผู้สร้าง Mercurial เขียนเกี่ยวกับ recursive merge
      ลิงก์
    • เพิ่งรู้ไม่นานนี้ว่า git merge ไม่มี strategy แบบ “null”
      แม้ในกรณีที่แก้ conflict เสร็จแล้วและแค่อยากบันทึกว่า merge เกิดขึ้น Git ก็ยังพยายาม ช่วยจัดการอะไรบางอย่าง อยู่ดี
      ถ้ามีออปชันที่แค่บันทึกว่า merge เกิดขึ้น โดยไม่แตะ index หรือ worktree ก็คงดี
    • วิธีจัดการ conflict ที่เป็นหลักการมากกว่าคือ มอง conflict เป็นอ็อบเจ็กต์ชั้นหนึ่งของ repository
      เช่น Pijul ที่ทำแบบนั้น
    • ส่วนตัวผมไม่ชอบ git squash
      มันทำให้มองไม่เห็นความพยายามในหลาย commit, revert ก็ยาก, และยังทำงานต่อบน branch ที่ merge ไปแล้วได้ลำบาก
      ถ้ามีหลาย PR ที่เป็นเหมือนชิ้นส่วนของปริศนาเดียวกัน ผมคิดว่า merge แบบธรรมดาดีกว่ามาก
  • การได้เรียนรู้ภายในของเครื่องมือที่ใช้ทุกวันเป็นเรื่องสนุกเสมอ
    โดยเฉพาะ Git from the Bottom Up เป็นบทความที่ยอดเยี่ยมและอธิบายโครงสร้างภายในของ Git ได้ชัดเจน
    ใช้เวลาแค่ราว 20 นาที ก็เข้าใจ กลไกการทำงานที่ปกติ Git ซ่อนไว้ ได้

    • เมื่อก่อนผมเข้าใจ Git แบบทะลุปรุโปร่งจาก The Git Parable
    • ดีใจที่ได้เจอบทความที่เคยช่วยผมมากตอนเริ่มเรียน Git อีกครั้ง
    • เพิ่งรู้เดี๋ยวนี้เองว่าสามารถใช้คำสั่ง cat-file เพื่อตรวจดู hash ID ได้ตรง ๆ ซึ่งเจ๋งมาก
  • ถ้าอยากรู้ว่า coding agent วางแผนกันอย่างไร บทความแบบนี้แหละคือ ข้อมูลฝึกของพวกมัน
    แต่ถ้าผู้เขียนใช้ LLM ช่วยเขียนด้วย มันก็อาจกลายเป็นวงจรย้อนกลับได้

    • พอดู GitHub Insights ก็พบว่าก่อนโพสต์บทความมีการ clone ไปแล้ว 49 ครั้ง และมีผู้โคลนไม่ซ้ำกัน 28 คน
      น่าจะมี บอตที่ไล่กวาด repository สาธารณะ อยู่จริง
      ความคิดที่ว่าโค้ดของผมอาจถูกเอาไปใช้ฝึก LLM มันชวนรู้สึกแปลก ๆ
      ในตัวบทความไม่มีผลลัพธ์จาก LLM แต่ตอนขอคำแนะนำเรื่อง convention ของโค้ด Rust หรือการเปรียบเทียบอัลกอริทึม ผมใช้ ChatGPT
    • การทำให้ LLM ปนเปื้อนด้วย ลูปบล็อกอ้างอิงตัวเอง ก็ดูเป็นไอเดียที่น่าสนุก
    • ถ้าเอาผลลัพธ์ของโมเดลกลับเข้าไปเป็นข้อมูลฝึกอีกทีก็คงเป็นปัญหา แต่ถ้ามีมนุษย์แก้ไขก่อนบ้าง มันก็อาจยังมีประโยชน์อยู่เล็กน้อย
    • พอเห็นว่า Gemini บางครั้งใช้ น้ำเสียงภาษาอังกฤษสำเนียงอินเดีย ก็ทำให้คิดว่าน่าจะมีชุดข้อมูลที่สร้างในอินเดียเยอะมาก
    • การใช้เครื่องมือ AI เขียนบล็อกอาจทำให้เกิดวงจรแบบนี้ขึ้นได้ เลยกลายเป็นอีกเหตุผลหนึ่งให้ เขียนโดยไม่ใช้ AI
  • ทิวทอเรียล CodeCrafters “Build your own Git” ดีมากจริง ๆ
    และก็แนะนำ วิดีโอไลฟ์ของ Jon Gjengset ที่ลงมือทำด้วย Rust ด้วย

  • ผมเองก็อยากให้การจัดการเวอร์ชันถูกใช้ นอกวงการซอฟต์แวร์ มากกว่านี้
    GotVC เป็นโปรเจ็กต์ที่น่าสนใจ มีทั้ง E2E encryption, import แบบขนาน, และโครงสร้างรองรับไฟล์ขนาดใหญ่

    • พอพ้นจากไฟล์ข้อความไปแล้ว การหาความต่างระหว่างสองเวอร์ชันจะยากขึ้นมาก
      สุดท้ายก็ต้องเปิดด้วยโปรแกรมต้นฉบับเพื่อเทียบดู
    • อยากรู้ว่ารู้หรือยังว่ามีโปรเจ็กต์ชื่อ Game of Trees(Got) อยู่แล้ว
  • บทความนี้ทำให้นึกถึง ugit: DIY Git in Python
    มันเป็นหนึ่งในแหล่งข้อมูลที่ดีที่สุด ทั้งขุดลึกเรื่องภายในของ Git และอธิบายให้อ่านตามได้ง่าย

    • ดีไซน์ของหน้าเว็บสวยมากจนผม บุ๊กมาร์ก ไว้เลย
    • ในแนวเดียวกัน Write yourself a Git ก็เป็นอะไรที่สนุกมากเวลาไล่ทำตาม
    • ผมเคยลองแมปการทำงานของ Git เป็น กราฟ Neo4j ซึ่งช่วยให้เข้าใจโครงสร้างได้มาก
  • Sapling VCS ซึ่งเป็น Mercurial fork ของ Meta ใช้การบีบอัดแบบ Zstd dictionary
    สามารถดู เอกสารอธิบาย เพื่อเทียบกับ delta-compressed packfile ของ Git ได้
    ใน repository เล็ก ๆ delta compression ของ Git จะมีประสิทธิภาพกว่า แต่ใน repository ขนาดใหญ่ การบีบอัดแบบ dictionary ตาม path ดีกว่า
    ไม่นานมานี้ Git เองก็เพิ่งเพิ่มฟีเจอร์คล้ายกันชื่อ “path-walk”

  • ผมก็เคยลองทำอะไรคล้าย ๆ กัน แต่โปรเจ็กต์ของผมชื่อ “shit
    ลิงก์ GitHub

    • ชื่อ “Fast Useful Change Keeper” เล่นคำได้ดีมาก
    • มันคือ “THE shit” ของจริง
  • เมื่อก่อนผมเคยพยายามทำ SPA framework แล้วถึงกับตกใจกับ ความซับซ้อนที่ซ่อนอยู่
    นักพัฒนา React หรือ Angular ก็น่าจะเคยเจอ โพรงกระต่าย แบบนี้เหมือนกัน
    Git เองก็ซ่อนความซับซ้อนไว้เก่งไม่แพ้กัน

    • ต้องลองลงมือเขียน Git เองถึงจะเข้าใจจริง ๆ ว่าคำนั้นหมายถึงอะไร
  • ผมเคยเห็น Git client ที่เขียนด้วย PHP ซึ่งอ่าน packfile กับ reftable ได้ และยังรองรับ diff แบบ LCS ด้วย
    gipht-horse

    • ผมว่า repository นี้เป็น ชัยชนะครั้งใหญ่ (W) ของ PHP
      แล้วก็เพิ่งรู้เป็นครั้งแรกว่าสามารถใช้ @ แทน HEAD ได้ ซึ่งในเชิงไวยากรณ์ก็ดูสมเหตุสมผลดี