Thursday, March 27, 2008

Object database ของ Git

ในการเก็บ repository, git มองทุกอย่างเป็น Object
โดยแต่ละ Object จะมี Identity Unique Hex Id ที่เป็นตัวอักษร 40 ตัว
ค่า id นี้เกิดจากการทำ Hashing(SHA1) จากเนื้อหาและ type ของ object นั้น
ข้อดีของการมี id ที่เป็น hash ของ content ก็คือ
จะไม่มีการ merge ซ้ำซ้อน(ที่เกิดจาก patch เดิม)เกิดขึ้นแน่นอน

Object ที่เก็บใน Git database แบ่งออกเป็น 4 แบบคือ
  • Blob object อันนี้ตรงตัว เอาไว้เก็บ content
  • Tree object อันนี้เอาไว้เก็บโครงสร้าง Tree directory, ภายใน object จะมีทั้งข้อมูลพวก ชื่อ file, permissions, รวมถึง link ที่ชี้ไปยัง Blob object ที่เป็นเนื้อหาของ file
  • Tag object อันนี้เป็น pointer ที่ชี้ไปยัง object อื่นๆ
  • Commit object, อันนี้จะเก็บ pointer ที่ชี้ไปยัง Tree object ที่ represent state ณ จังหวะเวลาที่สั่ง commit, ข้อมูลที่เก็บก็มีพวก เวลาที่ commit, ใครเป็นคน commit,
    แต่ข้อมูลที่สำคัญที่สุดก็คือ parent pointer ที่ชี้ไปยัง commit object ที่เกิดก่อนมัน
    โดยปกติ Commit object มักจะมี parent เพียงอันเดียว, แต่ก็มีบางสถานะการณ์ที่สามารถมี parent ได้มากกว่า 1 (เช่นการ merge)


ลองมาแอบดูโครงสร้างมันกัน
เริ่มจาก commit object ก่อน

ลองเรียกดู ประวัติการ commit แค่ 1 ครั้งย้อนหลัง

$ git log -1
commit 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
Merge: 4617ea2... 8c3f76a...
Author: pphetra <pphetra@pphetra.(none)>
Date: Wed Mar 26 16:10:52 2008 +0700

Merge branch 'master' of http://pann/git/og

ลองเอาเลข Id ยาวๆ นั้นมาสอบถาม type ดู

#flag -t คือ ขอดู type ของ object id ที่ระบุ
$ git cat-file -t 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
commit

# -p คือ pretty-print
# จะเห็นว่ามี parent มากกว่า 1 ตัว เนื่องจาก commit นี้เกิดจากการ merge
$ git cat-file -p 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
tree 9a85a027d0f5e29ba6b9874ef10a3a3c3101f17c
parent 4617ea2deeb7b84b84c0b3f4cd173046e5604b95
parent 8c3f76abb50c4ff27b8da6c4530640650d80d179
author pphetra <pphetra@pphetra.(none)> 1206522652 +0700
committer pphetra <pphetra@pphetra.(none)> 1206522652 +0700

Merge branch 'master' of http://pann/git/og


ทดลอง list ดู tree object ที่อยู่ใน commit object ที่ว่า

$ git ls-tree 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
100644 blob 9088635d5273240813d7fab8daf26c389a8a51d9 .classpath
100644 blob 787a8786a3ec7b2fde43abbb4d81c3ea409b25fb .gitignore
100644 blob f08ecbab3a1d3c8277620d35928e01913be1d1f4 .project
100644 blob 46734c60ef2bb210adc2f86307bc00fb53df7309 APACHE2_HEADER
100644 blob 60c078179819ea624495166795ce6224771a7ba6 KEYS
100644 blob a139573f2386fabc6b246f04b2d1686b00ddd71d LICENSE
100644 blob 2677396f5edba3a549c447115eeae1a5eeea8a14 NOTICE
100644 blob fa5d691f0975f8afc054b16b77ba71079d819c25 OPTIONAL_LIBRARIES
100644 blob 7e7d6d5977e3fd752f4dd82a593efa836618aa63 README
100755 blob 87591cad0666cdb051721fffd04f9c01aae94e5b ant
100644 blob 96c49bfa7b369a5330df284f3dcb1a38c82fbac9 ant.bat
040000 tree 6ed701fa39af921e80b34481efb5b87634ab424e applications
100644 blob 78122b51b6bc277c2ac67c0347ff83fee54f7b7d build.xml
040000 tree 3cd7b0cd4dfeee73df183be101987170b7bd102a framework
040000 tree f14e65a88ad8552e18ebca8a85659f24bb5b5ee8 hot-deploy
100755 blob 7b0c125d5880833e11c656f552717c5e0b6f77a0 ij.ofbiz
100644 blob 1c7326b63b39e8b8b03289c52cceed30d3b17e3a rc.ofbiz
040000 tree f3bc94c18fa6861e111a4306c235e92fa9b6f737 runtime
040000 tree a2dc45456637d729def157b82d2983ebc2db23a4 specialpurpose
100644 blob 9085cf0ef6b6acbd77c39432f7e79f4863f661fa startofbiz.bat
100755 blob b5457726ccfdae38b87da761e9eba237075dc78f startofbiz.sh
100755 blob 9c6dbbcf1449a1fcb4e9d23de29e78180f0a4cd9 stopofbiz.sh

จะเห็นว่ามีทั้ง file permission และ type ที่เป็น file และ directory (tree)

ลองเปิด directory .git/object แล้ว
เอา ID 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071 ที่ represent commit object
ไปเลียบๆมองๆดู ก็จะเห็นแบบนี้



internal structure เก็บกันอย่างนี้นี่เอง

ไอ้พวกเลขเลข Hex ยาวๆ 40 ตัวอักษรนี่มันไม่ค่อยสะดวกในการใช้งาน
ก็เลยมี concept ที่เรียกว่า reference ขึ้นมา
โดยเราสามารถใช้ naming ในการ represent object ที่เราต้องการได้

ประเภทของ reference ก็จะประกอบด้วย
  • heads
  • tags

ความแตกต่างหลักๆของ heads กับ tags ก็คือ
tags แสดงจุดๆหนึ่งที่อยู่ใน history และไม่สามารถแก้ไขค่าได้
ส่วน heads จะแสดง ยอด(tip) ของ history ของเรา
ซึ่งจะเปลี่ยนแปลงทุกครั้งที่มีการ commit ลงไป

ถ้าลองไปเปิด directory .git/refs/heads ดู ก็จะเห็น
file ที่มีชื่อ ตาม branch ที่เราตั้งไว้
(ยกเว้น master ที่เป็นชื่อ default ที่ git assign ให้เรา
ตอนที่เรา clone หรือ init repository ขึ้นมา)


เนื้อหาที่อยู่ใน file พวกนี้ ก็คือ เลข Object id นั่นเอง

$ cd .git/refs/heads
$ cat master
2c3c66169b4f9acd3a9dc358a9db8cde5d92c071

Related link from Roti

3 comments:

Sirn said...

ผมไม่ค่อยถูกใจนิดๆ ตรงที่มันสร้าง object ตอนสั่ง git add เลย แต่พอสั่ง git reset แล้ว มันกลับไม่ยอมลบ object ที่สร้างไว้ให้ ต้องมาสั่ง git-gc --prune เก็บเอาเองอีกที ไม่งั้น clone ไปไหนมาไหนก็จะติดไปด้วย น่ากลัวมาก (รู้สึกว่าแปลกดี)

PPhetra said...

ตอนแรก git-reset มันออกแบบมาแบบนี้ไง

This command is useful if you notice some small error in a recent
commit (or set of commits) and want to redo that part without showing
the undo in the history.

มันก็เลยไม่ลบ object ทิ้ง

อืมม์ ถ้าเป็นอย่างนี้ แสดงว่า การ ลบ branch , git branch -d branch_name , ก็น่าจะไม่ได้ลบ object ตามด้วย

PPhetra said...

จริงด้วย , delete branch ไม่ได้ลบ object ตาม