IlmHamroh
JavaScript Full-stack/7-qism. Git va GitHub asoslari11/36-dars19 daqiqa
Mundarija (34)

Git ichkaridan: blob, tree, commit obyektlari va SHA

Qisqacha: Git har bir faylni, papkani va commitni obyekt sifatida .git/objects ga yozadi. Obyekt nomi — uning mazmunidan hisoblangan SHA hash: bir xil mazmun doim bir xil nom oladi. To'rt tur bor: blob (fayl mazmuni), tree (papka), commit (surat + muallif + ota) va annotated tag. Commit farqlarni emas, butun loyihaning suratini (snapshot) ko'rsatadi. O'zgarmagan fayllar esa qayta saqlanmaydi.

Bu darsda

  • .git/objects ichida nima borligini git cat-file bilan o'qiysiz.
  • Blob, tree, commit va annotated tag obyektlarini farqlaysiz.
  • SHA hash qanday hisoblanishini qo'lda tekshirasiz va nega Git "mazmun bo'yicha manzillanadigan" ombor ekanini tushuntirasiz.
  • Snapshot tushunchasini va packfile, git gc ishini bilasiz.
  • Git'dagi SHA-1 holatini va SHA-256'ga o'tish rejasini bilasiz.

Oldin bilishingiz kerak: Birinchi repo: init, status, add, commit, Xatoni tuzatish 2: reset va revert.

1. Nega bu kerak?

Oldingi darslarda Git'dan bir necha "sirli" narsalar chiqdi:

  • Har commitning dab5bff kabi nomi bor. U qayerdan keladi?
  • create mode 100644 — bu raqam nima (Birinchi repo darsida "keyin ochamiz" degan edik)?
  • Oldingi darsda revert'dan keyin menyu faylining nomi 63aadda — xuddi bir necha commit oldingidek bo'lib qoldi. Nega?

Bularning hammasiga bitta javob bor. Uni bilgan odam Git'ni "sehrli quti" deb emas, oddiy va mantiqiy ombor deb ko'radi. Keyingi darslarda branch, merge va rebase'ni o'rganganda ham shu rasm yordam beradi: har bir buyruq shu obyektlar ustida ishlaydi.

Hayotiy o'xshatish — kutubxona arxivi. Har bir hujjat alohida jildda saqlanadi. Jild raqami hujjat mazmunidan hisoblanadi: mazmun bir xil bo'lsa, raqam ham bir xil. Shuning uchun bir hujjatning ikki nusxasi hech qachon ikki jildga tushmaydi. Jildlar ro'yxati — papka. Ro'yxat va sanasi yozilgan varaqa — commit.

Bu dars "chuqur" qavatdan. Hamma buyruqlarni yodlash shart emas. Maqsad — rasmni tushunish.

2. Birinchi obyekt

2.1 Bo'sh repo

Tajriba uchun alohida repo ochamiz. kurs/sinov-git/ — tashlab yuboriladigan tajribalar papkasi (Yaxshi commit darsida ochgan edik). Uning ichida yangi repo:

bash
cd ~/kurs
mkdir -p sinov-git/obyektlar
cd sinov-git/obyektlar
git init
find .git/objects -type f

find (Fayl qidirish darsidan) hech narsa chiqarmadi — yangi repoda obyekt yo'q. Bitta fayl yaratamiz va Git'dan uning nomini so'raymiz:

bash
echo 'salom' > salom.txt
git hash-object salom.txt
text
4de65895076ffaf8572ae06909fa475a10567eea

git hash-object — "bu fayl Git'ga qo'shilsa, qanday nom oladi?" degan savolga javob. Hali hech narsa yozilmadi. Endi faylni qutiga solamiz:

bash
git add salom.txt
find .git/objects -type f
text
.git/objects/4d/e65895076ffaf8572ae06909fa475a10567eea

(git add CRLF ogohlantirishini ham chiqardi — bu darsda uni chiqishlardan olib tashlaymiz.) .git/objects da birinchi fayl paydo bo'ldi. Nomiga qarang: 4d — papka, qolgan 38 belgi — fayl nomi. Birga — aynan hash-object aytgan nom. Git obyektlarni nomining birinchi ikki belgisi bo'yicha papkalarga taqsimlaydi — bitta papkada yuz minglab fayl to'planmasin.

Demak, git add faylni qutiga solganda uning mazmunini allaqachon omborga yozadi. Commit keyin faqat "shu mazmunlar ro'yxati"ni muhrlaydi.

2.2 git cat-file — obyektni o'qish

Obyekt faylini cat bilan ochsangiz, tushunarsiz belgilar ko'rasiz: Git uni siqib saqlaydi (zlib — ZIP'ga o'xshash keng tarqalgan siqish usuli). O'qish uchun maxsus buyruq bor — git cat-file. Qisqa nom (kamida 4 belgi) ham yetadi:

bash
git cat-file -t 4de6589
git cat-file -p 4de6589
git cat-file -s 4de6589
text
blob
salom
6

Uchta flag:

Flag Nimani ko'rsatadi
-t Turi (type)
-p Mazmuni, o'qiladigan ko'rinishda (pretty-print)
-s Hajmi, baytda (size)

Hajm 6: salom — 5 harf, oxiridagi yangi qator belgisi (LF) — yana bitta.

Blob (blob) — fayl mazmunini saqlaydigan obyekt. E'tibor bering: blobda fayl nomi yo'q. Faqat mazmun. Nom boshqa joyda — tree'da saqlanadi.

3. SHA — mazmundan hisoblangan nom

3.1 Hash nima?

Hash (xesh) — istalgan uzunlikdagi ma'lumotdan qat'iy uzunlikdagi "barmoq izi" hisoblaydigan funksiya natijasi. Bir xil ma'lumot — doim bir xil hash. Bitta harf o'zgarsa — butunlay boshqa hash.

Git bu ish uchun SHA-1 algoritmidan foydalanadi. Natija — 160 bit, ya'ni 40 ta o'n oltilik belgi (0–9 va a–f). Bu belgilar sizga CSS ranglaridan tanish: #2f6b4f ham o'n oltilik son. Shuning uchun commit nomlari 40 belgili. git log --oneline esa faqat boshidagi 7 tasini ko'rsatadi.

Bitta harfni katta qilamiz:

bash
echo 'Salom' | git hash-object --stdin
text
68fb6b9f8441e6b49ecd11a1708a82e0fa48cfe8

4de6589... bilan umumiy hech narsasi yo'q. --stdin — "faylni emas, pipe orqali kelgan matnni hisobla" (Oqimlar va yo'naltirish).

3.2 Qo'lda tekshirish

Git sir saqlamaydi — hashni o'zimiz hisoblab ko'ramiz. Git mazmun oldiga sarlavha qo'shadi: tur, bo'sh joy, hajm va nol bayt (\0). Keyin hammasidan SHA-1 hisoblaydi. Git Bash'dagi sha1sum dasturi bilan:

bash
printf 'blob 6\0salom\n' | sha1sum
text
4de65895076ffaf8572ae06909fa475a10567eea *-

Aynan o'sha nom! Oxiridagi *- — sha1sum ning belgisi: "pipe'dan o'qildi". printf — echo ning aniqroq varianti, \0 va \n ni haqiqiy baytlarga aylantiradi.

Bu tajriba Git'ning butun g'oyasini ko'rsatadi: nom mazmundan kelib chiqadi. Buni mazmun bo'yicha manzillanadigan ombor (content-addressable storage) deyishadi. Obyektni topish uchun uning mazmunidan hisoblangan kalit ishlatiladi.

3.3 Bu nima beradi?

Uchta foyda:

  1. Takrorlanish yo'q. Bir xil mazmun — bitta obyekt, nechta faylda bo'lmasin.
  2. Buzilishni sezish. Diskdagi obyektning bitta bayti buzilsa, uning hashi nomiga mos kelmay qoladi. Git buni sezadi.
  3. Tarixni sezdirmay o'zgartirib bo'lmaydi. Commit hashi uning mazmunidan, mazmunida esa ota commitning hashi bor. Bittasini o'zgartirsangiz, undan keyingi hamma commitlarning nomi o'zgaradi. Versiya nazorati darsidagi "tarix himoyalangan" gapi shu.

Birinchisini ko'ramiz. Xuddi shu mazmunli ikkinchi fayl:

bash
cp salom.txt nusxa.txt
git add nusxa.txt
find .git/objects -type f
text
.git/objects/4d/e65895076ffaf8572ae06909fa475a10567eea

Yangi obyekt paydo bo'lmadi! nusxa.txt ning mazmuni salom.txt niki bilan bir xil — Git uni allaqachon saqlagan.

Tekshirib ko'ring: Ali va Malika bir-birini bilmagan holda, ikki xil kompyuterda salom so'zi yozilgan faylni Git'ga qo'shdi. Ularning blob nomlari bir xil bo'ladimi?

Javob

Ha, 4de6589... — ikkalasida ham. Blob nomi faqat mazmundan (va sarlavhadagi tur va hajmdan) hisoblanadi. Fayl nomi, kompyuter, vaqt va muallif blobga kirmaydi. Commitlarining nomi esa boshqa bo'ladi — commitda muallif va vaqt bor.

4. Tree — papka obyekti

Commit qilamiz va omborga yana qaraymiz:

bash
git commit -m "Sinov: ikki bir xil fayl qo'sh"
find .git/objects -type f
text
.git/objects/3e/9399e40428688660f81b445f51d18104b34646
.git/objects/4d/e65895076ffaf8572ae06909fa475a10567eea
.git/objects/6a/bfac771e1d970c8e39b4222565d55ff67dddbd

Ikki yangi obyekt: bittasi commit, bittasi — tree. HEAD^{tree} — "HEAD commitining tree'si" degani:

bash
git cat-file -p 'HEAD^{tree}'
text
100644 blob 4de65895076ffaf8572ae06909fa475a10567eea	nusxa.txt
100644 blob 4de65895076ffaf8572ae06909fa475a10567eea	salom.txt

Tree (tree) — papka obyekti: ichidagi har bir element uchun bitta qator. Qatorda to'rt narsa bor: rejim, tur, hash va nom. Endi nom paydo bo'ldi! Ikki nom bitta blobga ishora qiladi.

Rejim raqamlari — Birinchi repo darsidagi create mode 100644 ning javobi:

Rejim Ma'nosi
100644 Oddiy fayl
100755 Ishga tushiriladigan fayl (skript)
040000 Papka (ichki tree)
120000 Symlink (Qator oxiri va symlink)

'HEAD^{tree}' ni qo'shtirnoqqa oldik: ba'zi shell'lar { } belgilarini o'zicha talqin qiladi.

5. Commit obyekti

5.1 «Bahor» commitining ichi

Endi haqiqiy loyihaga o'tamiz. «Bahor» reposida:

bash
cd ~/kurs/bahor
git cat-file -t HEAD
git cat-file -p HEAD
text
commit
tree d9cddcf9de0bae3aef2ff681544ad9dd61a94545
parent bd98d1bb2aeffb83a40f6745994ca8670af0a34d
author Aziz Karimov <aziz@example.com> 1790917920 +0500
committer Aziz Karimov <aziz@example.com> 1790917920 +0500

Menyu: lag'mon narxini qo'sh

Misollarni biz Birinchi repo darsi oxiridagi «Bahor»da ko'rsatamiz — HEAD bu yerda 07b061d. Sizda keyingi darslardagi commitlar ham bor, shuning uchun hashlar boshqa bo'ladi. Tuzilma esa aynan shu. Commit (commit) obyektida:

  • tree — loyiha ildiz papkasining tree'si. Butun surat shu bitta hash orqali.
  • parent — ota commit. Oldingi darsda merge commitda ikkita parent qatorini ko'rgan edik.
  • author — kim yozgan, committer — kim commit qilgan. Odatda bir odam. Keyinroq, masalan boshqaning o'zgarishini ko'chirganda, farq qiladi.
  • 1790917920 +0500 — vaqt: 1970-yil 1-yanvardan beri o'tgan soniyalar va Toshkent vaqt mintaqasi. git log uni odam tiliga o'giradi.
  • Bo'sh qatordan keyin — xabar.

Birinchi commitda parent qatori yo'q — Birinchi repo darsidagi (root-commit) aynan shu:

bash
git cat-file -p dab5bff
text
tree 882dc103895a022ed2a3fc590d58ba8b75e354d7
author Aziz Karimov <aziz@example.com> 1790830800 +0500
committer Aziz Karimov <aziz@example.com> 1790830800 +0500

Bahor: sayt tuzilmasini yarat

5.2 Tree'dan blob'gacha

Commitning tree'sini ochamiz:

bash
git cat-file -p d9cddcf
text
040000 tree 0e65318d4cd157e38120ba67e99e85ec791d317d	aloqa
040000 tree 7259bb6ed354ea899eb9ffac55fbecd138bf3010	assets
040000 tree b05140fea49b423debd476078f408a9510c22c34	bron
100644 blob 42c0d4ea734d13d27a8c329b349d90177720f38e	index.html
040000 tree b2030974140093bfd7ebdc0c88166441b2dd681d	menyu

Papkalar — tree, fayl — blob. Chuqurroq tushish uchun HEAD:yo'l yozuvi qulay:

bash
git cat-file -p HEAD:menyu
git cat-file -p HEAD:menyu/index.html
text
100644 blob 8eecc123b58fcf432c4cc5deddaa0e4403d712c6	index.html
<h1>Menyu</h1>
<p>Osh — 35 000 so'm</p>
<p>Lag'mon — 28 000 so'm</p>

Commit → tree → tree → blob. Versiya nazorati darsidagi git show dab5bff:menyu/index.html buyrug'i aynan shu yo'lni bosib o'tadi.

flowchart TD
  C["commit 07b061d<br/>Menyu: lag'mon"] --> T["tree d9cddcf<br/>(ildiz)"]
  C -. parent .-> P["commit bd98d1b"]
  T --> A["tree: aloqa"]
  T --> B["tree: bron"]
  T --> M["tree: menyu"]
  T --> I["blob: index.html"]
  M --> MI["blob 8eecc12<br/>menyu/index.html"]
  P --> T2["tree 3b1be60"]
  T2 --> B

Nimaga qarang: pastdagi bd98d1b commitining tree'si ham xuddi o'sha bron tree'siga ishora qiladi. Ikki commit bitta papkani bo'lishib turibdi — keyingi bo'limda buni tekshiramiz.

git ls-tree -r HEAD butun daraxtni bitta ro'yxatda beradi:

text
100644 blob c1bd40cb28a904afa0bab24c72aa9a4c66c60dc7	aloqa/index.html
100644 blob 693ce9b84057d37872139150e32284abbbe76754	assets/css/asosiy.css
100644 blob 5dc70960c8f0a146925427d63e7fa512647d5f5e	bron/index.html
100644 blob 6b69d3ab9821267c2683122f1df88f5b662de3d1	bron/rahmat.html
100644 blob 42c0d4ea734d13d27a8c329b349d90177720f38e	index.html
100644 blob 8eecc123b58fcf432c4cc5deddaa0e4403d712c6	menyu/index.html

Tekshirib ko'ring: Blobda fayl nomi yo'q. Unda Git menyu/index.html degan nomni qayerdan biladi?

Javob

Tree'lardan. Ildiz tree'da menyu nomi va uning tree hashi yozilgan, menyu tree'sida esa index.html nomi va blob hashi. Yo'l — tree'lar zanjiri bo'ylab yig'ilgan nomlar.

6. Snapshot: farq emas, surat

6.1 Har commit — butun loyiha

Ko'p versiya nazorati tizimlari har versiyada faqat farqni saqlaydi: "3-qatorga shu qo'shildi". Git boshqacha fikrlaydi. Har bir commit — butun loyihaning to'liq surati (snapshot): ildiz tree orqali har bir faylga yo'l bor. Eski commitni ochish uchun Git farqlarni birma-bir qo'shib chiqmaydi — shunchaki o'sha tree'ni o'qiydi.

"Har commitda butun loyiha? Disk to'lib ketmaydimi?" degan savol tug'iladi. Javob — hash. O'zgarmagan fayl va papka bir xil hashga ega, demak qayta saqlanmaydi. Tekshiramiz. Oxirgi commit faqat menyuni o'zgartirgan edi:

bash
git rev-parse HEAD:bron HEAD~1:bron
git rev-parse HEAD:menyu HEAD~1:menyu
text
b05140fea49b423debd476078f408a9510c22c34
b05140fea49b423debd476078f408a9510c22c34
b2030974140093bfd7ebdc0c88166441b2dd681d
71209f281b31e9b29178ac7a5eb815c3afb69516

git rev-parse — nomni to'liq hashga aylantiradigan buyruq (uni keyingi darsda yaqindan ko'ramiz). bron — ikki commitda bitta tree. menyu — ikki xil, chunki ichidagi fayl o'zgargan. aloqa papkasi esa uch commit davomida bir xil:

bash
git rev-parse HEAD:aloqa HEAD~1:aloqa HEAD~2:aloqa
text
0e65318d4cd157e38120ba67e99e85ec791d317d
0e65318d4cd157e38120ba67e99e85ec791d317d
0e65318d4cd157e38120ba67e99e85ec791d317d

Bitta fayl o'zgarsa, Git uchta yangi obyekt yozadi: yangi blob, uning papkasi uchun yangi tree va ildiz uchun yangi tree. Qolgan hamma narsa oldingi commit bilan bo'lishiladi.

Endi oldingi darsdagi savolga javob. Revert menyuni osh o'zgarishidan oldingi mazmunga qaytardi. Mazmun bir xil — demak blob nomi ham bir xil: 63aadda. Git yangi blob yozmadi, eskisini qayta ishlatdi.

6.2 DAG — commitlar grafi

Har commit o'z otasiga ishora qiladi. Ota — o'z otasiga. Bu zanjir faqat orqaga yo'nalgan va hech qachon aylana yasamaydi: commit o'zidan keyin yaratilgan commitga ishora qila olmaydi. Hashi hali ma'lum emas-ku.

Bunday tuzilma DAG (directed acyclic graph) — "yo'naltirilgan, aylanasiz graf" deyiladi. Graf — nuqtalar va ularni bog'lovchi chiziqlar. "Yo'naltirilgan" — chiziq bir tomonga qaraydi (bola → ota). "Aylanasiz" — chiziqlar bo'ylab yurib, boshlangan nuqtaga qaytib bo'lmaydi. Merge commitda ikki ota bo'lgani uchun tarix oddiy zanjir emas, aynan graf. Branch va merge darslaridagi hamma rasmlar — shu DAG.

7. Annotated tag obyekti

To'rtinchi obyekt turi — annotated tag. Tag (tag) — muhim commitga doimiy nom berish, masalan v1.0. Tag'larni Tag va relizlar darsida to'liq o'rganamiz. Hozir faqat ichini ko'ramiz:

bash
git tag -a v1.0 -m "Bahor sayti: birinchi versiya"
git cat-file -t v1.0
git cat-file -p v1.0
text
tag
object 07b061d79648257f6761433b3704325a26bbab77
type commit
tag v1.0
tagger Aziz Karimov <aziz@example.com> 1791608400 +0500

Bahor sayti: birinchi versiya

-a (annotated) bilan yaratilgan tag — alohida obyekt: qaysi commitga ishora qilishi (object), kim va qachon qo'ygani (tagger) va xabari bor. -a siz yaratilgan "yengil" tag esa obyekt emas — shunchaki commitga yopishtirilgan nom:

bash
git tag yengil
git cat-file -t yengil
text
commit

Git to'rt tur obyekt biladi va boshqasi yo'q:

Obyekt Nima saqlaydi O'xshatish
blob Fayl mazmuni Hujjat matni
tree Nomlar va ichki obyektlar ro'yxati Jild mundarijasi
commit Tree, ota, muallif, vaqt, xabar Sanali muhr
tag Obyekt, tagger, xabar Muhrga yopishtirilgan yorliq

8. Packfile va git gc

8.1 Bo'sh obyektlar va to'plam

Hozircha har obyekt alohida faylda. Bunday obyekt loose object — "bo'sh (yig'ilmagan) obyekt" deyiladi. git count-objects -v ularni sanaydi:

bash
git count-objects -v
text
count: 30
size: 2
in-pack: 0
packs: 0
size-pack: 0
prune-packable: 0
garbage: 0
size-garbage: 0

count: 30 — 30 ta bo'sh obyekt, size: 2 — ular diskda taxminan 2 KiB (kilobayt) joy oladi. Olti commitli kichik loyihada 30 obyekt. Katta loyihada yuz minglab bo'ladi. Yuz ming kichik fayl — disk uchun og'ir.

Shuning uchun Git vaqti-vaqti bilan obyektlarni bitta katta faylga yig'adi. Bu fayl packfile — "to'plam fayli" deyiladi. Qo'lda ishga tushiramiz:

bash
git gc
git count-objects -v
ls .git/objects/pack
text
count: 0
size: 0
in-pack: 30
packs: 1
size-pack: 4
prune-packable: 0
garbage: 0
size-garbage: 0
pack-08e86b80d6ba739be94669fbdd505efc16f87167.idx
pack-08e86b80d6ba739be94669fbdd505efc16f87167.pack
pack-08e86b80d6ba739be94669fbdd505efc16f87167.rev

count: 0, in-pack: 30 — hamma obyekt endi bitta to'plamda. .pack — obyektlarning o'zi, .idx — tez topish uchun mundarija. .rev — yordamchi indeks. git gc (garbage collection) — "axlat yig'ish": obyektlarni to'playdi va hech qayerdan ishora qilinmaydigan eski obyektlarni vaqti kelganda o'chiradi.

git gc ni odatda qo'lda yozmaysiz. Git ko'p amallardan keyin o'zi tekshiradi va kerak bo'lsa ishga tushiradi. Obyektlarni o'qish esa o'zgarmaydi — git cat-file -p HEAD:menyu/index.html to'plam ichidan ham avvalgidek ishlaydi.

8.2 Snapshot va delta ziddiyatmi?

Bu yerda bitta nozik joy bor. Yuqorida "Git farqlarni emas, suratlarni saqlaydi" dedik. Lekin to'plam ichiga qarasak:

bash
git verify-pack -v .git/objects/pack/pack-*.idx

Natijadan bir necha qator (qolganini ... bilan qisqartirdik):

text
07b061d79648257f6761433b3704325a26bbab77 commit 239 173 12
...
3b1be60aa232bae33edf268db5b1b9911cc39058 tree   27 40 1569 1 d9cddcf9de0bae3aef2ff681544ad9dd61a94545
...
non delta: 26 objects
chain length = 1: 2 objects
chain length = 2: 1 object
chain length = 3: 1 object

3b1be60 tree'si to'liq saqlanmagan: u d9cddcf ga nisbatan farq — delta sifatida yozilgan. Demak Git baribir farq saqlaydimi?

Ikkalasi ham rost, chunki ular ikki xil qavatda. Mantiqan har commit — to'liq surat: cat-file doim butun obyektni beradi, siz hech qachon delta ko'rmaysiz. Diskda esa, to'plam ichida, Git joy tejash uchun o'xshash obyektlarni bir-biriga nisbatan siqadi. Bu — faqat saqlash usuli. Xuddi rasmlar arxivini ZIP qilganday: arxiv ichida siqilgan, ochsangiz — asl rasm.

Tekshirib ko'ring: git gc dan keyin .git/objects/4d/... kabi alohida fayllar yo'qoldi. git log va git show ishlashda davom etadimi?

Javob

Ha. Obyektlar o'chirilmadi, faqat bitta .pack fayliga ko'chirildi. Git obyektni avval alohida fayllardan, keyin to'plamlardan qidiradi. Buyruqlar uchun farqi yo'q.

9. SHA-1 va SHA-256'ga o'tish

SHA-1 1990-yillarda yaratilgan. Keyinchalik unga qarshi amaliy hujumlar topildi. 2017-yilda tadqiqotchilar bir xil SHA-1 hashli ikki xil PDF fayl yasashdi (SHAttered hujumi). Rasmiy Git hujjatlari bu haqda shunday deydi:

  • Git ma'lum hujumlarga qarshi himoyaga ega. Lekin kelajakda yangi hujumlar topilishi kutiladi.
  • Git allaqachon SHA-256 ni qo'llaydi — 64 belgili, xavfsizroq hash. Yangi repo yaratishda tanlash mumkin: git init --object-format=sha256.
  • Hozircha SHA-256'li va SHA-1'li repolar o'zaro ishlamaydi: biridagi commitlarni ikkinchisiga yuborib bo'lmaydi.
  • "BreakingChanges" hujjatiga ko'ra, kelajakdagi Git 3.0 da yangi repolar uchun standart hash SHA-256'ga almashadi. Uning chiqish sanasi hali belgilanmagan. SHA-1 formatidan voz kechish rejasi esa hozircha yo'q.

Sinab ko'ramiz — yana sinov-git/ ichida:

bash
cd ~/kurs/sinov-git
git init --object-format=sha256 sha256
cd sha256
echo 'salom' > salom.txt
git hash-object salom.txt
git rev-parse --show-object-format
text
Initialized empty Git repository in C:/Users/.../kurs/sinov-git/sha256/.git/
caa79a099664a4e0d29775d82115ac834c67a0d3e311cb34bdf34d0997c20149
sha256

O'sha salom — endi 64 belgili nom. «Bahor» reposida esa git rev-parse --show-object-format sha1 deydi.

Amalda nima qilish kerak? Hech narsa. Hozircha kursda va deyarli barcha real loyihalarda SHA-1 repolar ishlatiladi. Faqat bitta odatni oling: skriptlaringizda "hash doim 40 belgili" deb hisoblamang. Kelajakda u 64 bo'lishi mumkin.

Tajriba tugagach, bu papkani o'chirishingiz mumkin: cd ~/kurs/sinov-git qilib, keyin rm -rf sha256.

10. Ko'p uchraydigan xatolar

10.1 Mavjud bo'lmagan obyekt

bash
git cat-file -p abc1234
text
fatal: Not a valid object name abc1234

Tarjimasi: "abc1234 — yaroqli obyekt nomi emas". Bunday hashli obyekt reposida yo'q — ehtimol hash boshqa repodan ko'chirilgan yoki bir harf xato yozilgan. Juda qisqa nom (masalan 12) ham xuddi shu xatoni beradi: Git kamida 4 belgi talab qiladi.

10.2 .git/objects ni qo'lda o'zgartirish

Obyekt fayllarini o'chirish, nomini o'zgartirish yoki tahrirlash — repo buzilishining eng tez yo'li. Hash nomga mos kelmay qoladi, commitlar yo'qolgan obyektlarga ishora qiladi. Faqat o'qing, va faqat git cat-file orqali.

10.3 "Git farqlarni saqlaydi"

Intervyuda keng tarqalgan noto'g'ri javob. To'g'ri javob: har commit — butun loyihaning surati. O'zgarmagan fayllar hash tufayli qayta saqlanmaydi. Diskdagi delta — faqat to'plam ichidagi siqish usuli.

10.4 HEAD^{tree} shell'da buzilishi

{ } belgilari ba'zi shell'larda (masalan PowerShell'da) maxsus ma'noga ega. Natija kutilmagan bo'lsa — qo'shtirnoqqa oling: 'HEAD^{tree}'.

11. Mashqlar

1-mashq (oson): Obyekt turini toping

«Bahor» reposida quyidagilarning har biri qaysi turdagi obyekt? Avval taxmin qiling, keyin git cat-file -t bilan tekshiring:

  1. HEAD
  2. HEAD:menyu
  3. HEAD:menyu/index.html
  4. HEAD^{tree}

Ishora: «Tree'dan blob'gacha» bo'limidagi diagramma.

Yechim
bash
git cat-file -t HEAD
git cat-file -t HEAD:menyu
git cat-file -t HEAD:menyu/index.html
git cat-file -t 'HEAD^{tree}'
text
commit
tree
blob
tree

Commit — surat va muhr. Papka — tree. Fayl — blob. Commitning ildiz papkasi ham tree.

2-mashq (o'rta): Hashni qo'lda hisoblang

echo 'choy' | git hash-object --stdin qaysi hashni berishini sha1sum bilan tekshiring. Bo'sh joylarni to'ldiring: mazmun choy va yangi qator — jami bayt. Buyruq printf 'blob N\0choy\n' | sha1sum ko'rinishida bo'ladi, bu yerda N o'rniga o'sha son yoziladi.

Ishora: «Qo'lda tekshirish» bo'limi — salom 5 harf edi va hajmi 6 chiqdi.

Yechim
bash
echo 'choy' | git hash-object --stdin
printf 'blob 5\0choy\n' | sha1sum

Ikkala buyruq bir xil 40 belgili hash chiqaradi (oxirida faqat sha1sum ning *- belgisi farq qiladi). choy — 4 harf, plyus yangi qator belgisi — 5 bayt. Hajmni noto'g'ri yozsangiz, hash butunlay boshqa chiqadi.

3-mashq (qiyin): Nechta yangi obyekt?

«Bahor»da faqat bron/rahmat.html ga bitta qator qo'shib, commit qilsangiz, .git/objects ga nechta yangi obyekt yoziladi? Qaysilar?

Ishora: «Har commit — butun loyiha» bo'limi — o'zgargan faylning yo'li bo'ylab yuqoriga chiqing.

Yechim

To'rtta:

  1. rahmat.html ning yangi mazmuni uchun blob.
  2. bron papkasi uchun yangi tree — ichidagi bitta qator (rahmat.html hashi) o'zgardi.
  3. Ildiz uchun yangi tree — undagi bron qatorining hashi o'zgardi.
  4. Yangi commit.

aloqa, menyu, assets tree'lari va bron/index.html blobi eski commit bilan bo'lishiladi. git count-objects -v ni commitdan oldin va keyin ishga tushirib tekshiring: count to'rtga oshadi (git gc dan keyin bo'lsa, 0 dan 4 ga).

4-mashq: Portfolio qadami — o'z omboringizga qarang

  1. kurs/portfolio da birinchi commitingizning ichini oching: git cat-file -p HEAD. Qatorlardan qaysi biri parent emas, tree?
  2. Ildiz tree'ni oching: git cat-file -p 'HEAD^{tree}'. Qaysi fayllar bir xil blob hashga ega? Nega?
  3. git cat-file -p HEAD:sayt — qaysi ikki papka bir xil tree hashga ega?
  4. git count-objects -v ni yozing, keyin git gc va yana git count-objects -vH (-H — hajmni odam o'qiydigan ko'rinishda).
  5. Savol: asosiy-eski.css ni Yaxshi commit darsida git rm bilan o'chirgan edingiz, lekin birinchi commitda u bor. Birinchi commit SHA'sini git log --oneline ning eng pastki qatoridan oling (bizda 63190c1) va solishtiring: git rev-parse HEAD:sayt/assets/css/asosiy.css 63190c1:asosiy-eski.css. Nima chiqdi?

mashqlar repo: shu darsning 1–3-mashq javoblarini mashqlar/07/11-obyektlar/javoblar.md ga yozing va commit qiling: 07/11: obyektlar mashqlari javoblarini qo'sh.

Yechim
bash
cd ~/kurs/portfolio
git cat-file -p HEAD
git cat-file -p 'HEAD^{tree}'
git cat-file -p HEAD:sayt
git count-objects -v
git gc
git count-objects -vH
git rev-parse HEAD:sayt/assets/css/asosiy.css 63190c1:asosiy-eski.css

Biz Birinchi repo darsidagi qisqa portfolio'da sinadik — unda fayllar bir qatorli va asosiy-eski.css hali o'chirilmagan. Ildiz tree'dan parcha:

text
100644 blob c2658d7d1b31848c3b71960543cb0368e56cd4c7	.gitignore
100644 blob 0967ef424bce6791893e9a57bb952f80fd536e93	.htmlvalidate.json
100644 blob c2658d7d1b31848c3b71960543cb0368e56cd4c7	.htmlvalidateignore
100644 blob 0967ef424bce6791893e9a57bb952f80fd536e93	.prettierrc
...

Bizda .gitignore va .htmlvalidateignore bir xil (node_modules/), .prettierrc va boshqa ikki sozlama fayli ham bir xil ({}). sayt ichida aloqa va haqimda bir xil tree, chunki ichidagi index.html lar bir xil. Sizning haqiqiy fayllaringiz uzunroq va turli — ehtimol bunday moslik kamroq bo'ladi. Birinchi commitda parent qatori yo'q.

Oxirgi buyruq bizda ikki bir xil hash chiqardi: asosiy-eski.css va hozirgi CSS mazmuni bir xil edi — Git ularni bitta blob sifatida saqlagan. Sizda CSS o'shandan beri o'zgargan bo'lsa, ikki xil hash chiqadi. Qanday bo'lmasin, eski fayl o'chirilgan bo'lsa ham uning mazmuni birinchi commit orqali omborda turibdi.

12. Real ishda

  • Commit hashi — dasturchilar tilidagi aniq manzil. Xato hisobotlarida, Pull Request izohlarida, deploy jurnallarida "a1b2c3d commitdan keyin buzildi" deb yoziladi. Hash hech qachon boshqa commitni ko'rsatmaydi.
  • Deploy tizimlari ko'pincha versiyani commit hashi bilan belgilaydi — qaysi kod ishlayotgani aniq bo'ladi. Masalan, Docker — dasturni server uchun "qutiga" qadoqlaydigan vosita (keyingi qismlarda o'rganamiz, hozir bilish shart emas).
  • GitHub ham .git ombori bilan ishlaydi. Siz yuborganda, faqat serverda yo'q obyektlar yuboriladi — tarixning qolgani allaqachon bor. Remote darsida Counting objects, Compressing objects qatorlarini ko'rganda, bu darsni eslaysiz.
  • Intervyu savollari: "Git farqlarni saqlaydimi yoki snapshotlarnimi?", "Blob, tree, commit nima?", "Nega commit hashini o'zgartirmasdan tarixni tahrirlab bo'lmaydi?" O'rta darajadagi (middle) dasturchi suhbatlarida tez-tez uchraydi.

Xulosa

  • Git — mazmun bo'yicha manzillanadigan ombor: obyekt nomi — mazmunidan hisoblangan SHA hash.
  • To'rt obyekt: blob (mazmun), tree (nomlar), commit (tree + ota + muallif + xabar), annotated tag.
  • Har commit — to'liq surat; o'zgarmagan fayl va papkalar bir xil hash tufayli bo'lishiladi.
  • git cat-file -t/-p/-s — har qanday obyektni o'qish; .git/objects ni qo'lda o'zgartirmang.
  • git gc obyektlarni packfile'ga yig'adi; delta — faqat disk ichidagi siqish.
  • Hozir SHA-1; Git 3.0 da yangi repolar uchun SHA-256'ga o'tish rejalashtirilgan.

Keyingi dars: HEAD, refs va detached HEAD — commitlarga qanday qilib main va HEAD kabi nomlar yopishtirilishini va bu nomlar qayerda saqlanishini ko'ramiz.

Manbalar

  • Pro Git kitobi: "Git Internals — Git Objects", "Packfiles" — git-scm.com/book
  • Git hujjati: "git-cat-file", "git-hash-object", "git-gc", "git-init" (--object-format) — git-scm.com/docs
  • Git hujjati: "BreakingChanges" (Git 3.0 rejasi) — git-scm.com/docs/BreakingChanges
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Git ichkaridan: blob, tree, commit obyektlari va SHA — IlmHamroh