18 β Submodule, Git LFS va monorepo¶
β¬ οΈ Oldingi: 17 β Xatoni qidirish: bisect, blame, grep Β· π README Β· Keyingi: 19 β GitHub vositalari: Issues, Projects β‘οΈ
Bu bobda: loyiha o'sgani sayin uchraydigan uchta haqiqiy muammoni hal qilamiz. Birinchisi β loyiha ichida boshqa Git repoga bog'liqlik: submodule (boshqa reponi o'z loyihangizga "ko'rsatkich" sifatida ulash),
.gitmodulesfayli,git submodule add,--recurse-submodulesbilan clone, submodule'ni yangilash va uning chigalliklari. Ikkinchisi β katta binar fayllar (video, dizayn, dataset) reponi shishirib yuborishi: Git LFS (Large File Storage) nima, u qanday qilib katta faylni kichik ko'rsatkichga aylantiradi,git lfs install/trackva.gitattributes. Uchinchisi β bitta repoda ko'p loyiha: monorepo va polyrepo nima, qaysi biri qachon to'g'ri keladi, hamdagit subtree(submodule'ga muqobil). Yo'l-yo'lakay repo hajmini qanday boshqarishni ham ko'ramiz. Hamma buyruq alohida sinov papkasida haqiqatan ishlatib tekshirilgan.
Muammo¶
Tasavvur qiling: jamoa bilan do'kon sayti yozyapsiz. Sayt uchun chiroyli tugmalar, formalar, modal oynalar kerak β buni "UI kutubxona" deb ataylik. Aynan shu UI kutubxonani sizning boshqa loyihangizda β masalan admin panelida β ham ishlatmoqchisiz. Nusxa-ko'chir qilsangiz, kutubxonaga tuzatish kiritganda uni ikki joyda qo'lda yangilashga to'g'ri keladi. Bir kun unutasiz β ikki loyihada UI bir-biridan farq qila boshlaydi. Kerak bo'lgani: UI kutubxonasi bitta joyda (o'z reposida) yashasin, qolgan loyihalar esa undan aniq versiyani olib tursin.
Ikkinchi og'riq: shu saytga reklama roliklari, yuqori sifatli rasmlar, Photoshop fayllari (.psd) qo'shyapsiz. Bitta .psd β 500 MB. Siz uni har kuni o'zgartirib commit qilyapsiz. Bir oydan keyin .git papkangiz 20 GBga shishadi, git clone esa yarim soat davom etadi β chunki Git har bir versiyani to'liq saqlaydi. Kod 5 MB, lekin repo gigabaytlar.
Uchinchi og'riq: kompaniyangizda web-sayt, mobil ilova va backend API bor. Ularning hammasi bitta umumiy koddan (validatsiya qoidalari, modellar) foydalanadi. Uch alohida repomi yoki bitta katta repomi? Bu β monorepo va polyrepo munozarasi.
Uchala muammoning ham yechimlari shu bobda. Boshlaymiz.
1-qism. Submodule β repo ichida repo¶
Submodule (kichik modul) β bu sizning repongiz ichiga boshqa bir Git reposini ulash usuli. Eng muhim g'oya: ota-repo bola-reponing fayllarini nusxalamaydi. U faqat bitta narsani saqlaydi β bola-reponing aniq qaysi commitida turishini ko'rsatadigan ko'rsatkich (pointer). Bu ko'rsatkichni Git tilida gitlink deyiladi.
Diqqat qiling: ota-repo ichida ikki yangi narsa paydo bo'ladi β .gitmodules fayli (bola-repo qayerdan kelishini eslab qoladi) va vendor/ui "papkasi" (aslida papka emas, gitlink β faqat bitta commit hashini saqlaydigan maxsus yozuv).
Submodule qo'shish: git submodule add¶
Aytaylik, asosiy loyihangizga kutubxona-ui deb nomlangan boshqa reponi vendor/ui papkasi ostida ulamoqchisiz:
# Asosiy loyiha papkasi ichida turib:
git submodule add https://github.com/foydalanuvchi/kutubxona-ui vendor/ui
Bu buyruq nima qiladi:
kutubxona-uireposinivendor/uipapkasiga clone qiladi.- Loyiha ildizida
.gitmodulesfaylini yaratadi (yoki yangilaydi). vendor/uini gitlink sifatidagit addqiladi.
Endi git status ga qaraymiz:
π vendor/ui "fayl" sifatida ko'rinyapti β bu g'alati tuyulishi mumkin, lekin shunday: gitlink Git uchun oddiy fayl ham, papka ham emas, uchinchi tur β submodule yozuvi.
.gitmodules faylining ichi shunday bo'ladi:
Endi commit qilamiz va ota-repoda nima saqlanganini ko'ramiz:
Natija:
π Mana eng muhim joy. 160000 β bu maxsus "fayl rejimi" (mode), u faqat gitlink uchun ishlatiladi (oddiy fayl 100644, papka 040000 bo'ladi). commit a1b2c3d... esa β bola-reponing aynan shu commitiga qotirilganini bildiradi. Ota-repo bola fayllarini emas, atigi shu bitta hashni saqlaydi.
Submodule holatini ko'rish¶
Bu β submodule hozir qaysi commitda turganini ko'rsatadi.
π‘ .gitmodules faylini hech qachon qo'lda tahrirlamang β buyruqlar uni avtomatik boshqaradi. Faqat o'qish uchun ochishingiz mumkin.
--recurse-submodules bilan clone qilish¶
Mana eng ko'p sirpanadigan joy. Boshqa odam (yoki kelajakdagi siz) loyihani oddiy git clone bilan oladi:
Natijada vendor/ui papkasi bo'sh chiqadi! Chunki oddiy clone ota-reponi oladi, lekin submodule'lar ichini avtomatik to'ldirmaydi. Ikki yechim bor:
Yechim 1 β clone paytida birga olish (eng qulay):
--recurse-submodules bayrog'i ota bilan birga hamma submodule'larni ham gitlink ko'rsatgan commitda to'ldiradi.
Yechim 2 β allaqachon clone qilib bo'lgan bo'lsangiz:
--initβ.gitmodulesdagi submodule'larni ishga tushiradi (registratsiya qiladi);--recursiveβ submodule ichidagi submodule'lar bo'lsa, ularni ham (chuqurlikka) oladi.
π Eng tez-tez uchraydigan "submodule bo'sh" muammosi aynan shu: oddiy clone qilingan, keyin esa update --init unutilgan. Agar vendor/ui papkasi bo'sh bo'lsa β birinchi navbatda shu buyruqni eslang.
Submodule'ni yangilash¶
Vaqt o'tib, kutubxona-ui repoda yangi versiyalar chiqadi. Sizning ota-repongiz hamon eski commitga qotirilgan β bu ataylab shunday: versiya o'z-o'zidan o'zgarib ketmasligi uchun. Yangi versiyaga o'tish uchun siz buni ataylab qilishingiz kerak:
# vendor/ui ni o'z masofaviy (remote) shoxidagi eng so'nggi commitga olib chiqish:
git submodule update --remote vendor/ui
Endi git status ga qarang β ota-repo submodule o'zgarganini ko'radi:
Bu o'zgarishni git diff ham ko'rsatadi va u juda o'qiluvchan:
"Subproject commit" β gitlink eski commitdan yangisiga ko'chganini bildiradi. Buni saqlash uchun ota-repoda commit qilasiz:
π‘ Mana submodule'ning asosiy foydasi: jamoangizdagi har bir kishi aniq bir xil UI versiyasini oladi. Kimdir submodule'ni yangilab, ota-repoda commit qilmaguncha β boshqalar uchun versiya o'zgarmaydi. "Mende ishlaydi, sende ishlamaydi" muammosi kamayadi.
Har bir submodule'da buyruq bajarish¶
Ko'p submodule bo'lsa, hammasida bitta buyruqni ishlatish kerak bo'ladi:
Submodule'ni o'chirish¶
Submodule kerak bo'lmay qolsa, zamonaviy Git'da bitta buyruq yetadi:
git rm .gitmodules ichidagi yozuvni ham, gitlink'ni ham bir vaqtda olib tashlaydi.
π Submodule chigalliklari (oldindan ogohlantiramiz):
- Detached HEAD. Submodule papkasiga kirsangiz, ko'pincha HEAD biror shoxga emas, to'g'ridan-to'g'ri commitga ulangan ("detached HEAD") holatda bo'ladi (7-bobni eslang). Submodule ichida o'zgarish qilmoqchi bo'lsangiz, avval
git switch mainqilib shoxga o'ting. - Ikki bosqichli commit. Submodule ichida o'zgartirib commit qildingizmi β ota-repoda yana bir commit (gitlink yangilanishi) kerak bo'ladi. Buni unutish β eng keng tarqalgan xato.
- Push tartibi. Avval submodule (bola-repo) push qilinishi, keyin ota-repo push qilinishi kerak. Aks holda boshqalar ota-repodagi gitlink ko'rsatgan, lekin hali yuklanmagan commitni topa olmaydi.
git push --recurse-submodules=on-demandbuni avtomatlashtira oladi.
2-qism. Git LFS β katta fayllarni jilovlash¶
Endi ikkinchi muammoga o'tamiz: katta binar fayllar reponi shishiradi. Avval nega shunday bo'lishini tushunaylik.
Git matnli fayllarni juda yaxshi siqadi va versiyalaydi: ikki commit orasidagi farqni (diff) saqlash arzon. Lekin binar fayl (video, .psd, .zip, mashina o'rganish dataseti) uchun "farq" tushunchasi yo'q β har o'zgarishda Git butun faylni qayta saqlaydi. 500 MB fayl 10 marta o'zgarsa, repo tarixida 5 GB joy egallaydi. Bu fayl .git tarixiga butun umrga o'rnashib qoladi β uni hozir o'chirsangiz ham, tarixdan ketmaydi.
Git LFS (Large File Storage β katta fayllar saqlovi) aynan shu muammoni hal qiladi. G'oyasi sodda: katta faylni Git tarixiga emas, alohida serverga saqlaydi, repoda esa uning o'rniga kichik matnli ko'rsatkich qoldiradi.
LFS'ni sozlash: install va track¶
LFS β Git'ga qo'shimcha vosita, uni alohida o'rnatish kerak (ko'p tizimlarda allaqachon mavjud; tekshirish uchun git lfs version). Keyin har bir kompyuterda bir marta ishga tushiriladi:
Bu buyruq Git'ga "filter" o'rnatadi β endi Git ma'lum fayllarni avtomatik ko'rsatkichga aylantiradigan bo'ladi. Endi qaysi fayllar LFS orqali ketishini aytamiz:
Bu buyruqlar loyiha ildizida .gitattributes faylini yaratadi/to'ldiradi:
*.psd filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
π .gitattributes faylini albatta commit qiling va repoga qo'shing. Aks holda jamoangizdagi boshqalar uchun LFS ishlamaydi β ular katta fayllarni oddiy holda, to'g'ridan-to'g'ri tarixga qo'shib yuboradi. Bu fayl .gitignore kabi β loyihaning bir qismi.
Endi katta fayl qo'shamiz¶
Tashqaridan oddiy add va commitga o'xshaydi. Lekin sahna ortida sehr sodir bo'ldi. Repoda aslida nima saqlanganini ko'raylik:
version https://git-lfs.github.com/spec/v1
oid sha256:33737e694841e7b280ba42e477f0c45a0bda7b97022cb1f7c36d38c490dfb12c
size 524288000
500 MB fayl o'rniga β atigi uch qatorlik matn! Bu ko'rsatkich:
oid sha256:...β asl faylning "barmoq izi" (hash). Bu orqali LFS serveridan kerakli faylni topadi.sizeβ asl faylning baytlardagi hajmi.
Asl 500 MB binar esa LFS serverida turadi va siz git clone yoki git checkout qilganda avtomatik yuklab olinadi (kerakli versiyada).
LFS holatini tekshirish¶
Qaysi fayllar LFS orqali kuzatilayotganini ko'rish:
Hozir qaysi shablonlar (pattern) kuzatilayotganini ko'rish:
Allaqachon shishib ketgan repo: git lfs migrate¶
Eng yomon holat β siz LFS haqida kech bildingiz, katta fayllar allaqachon tarixga o'rnashib bo'lgan. git lfs track faqat kelajakdagi commitlarga ta'sir qiladi, tarixni o'zgartirmaydi. Tarixni qayta yozish kerak bo'ladi.
Avval qaysi fayl turlari ko'p joy egallayotganini ko'rib chiqing:
Keyin tarixni qayta yozib, mavjud .psd fayllarini LFS'ga ko'chirish:
π Ogohlantirish: migrate import β tarixni qayta yozadi (16-bobdagi filter-repo kabi), ya'ni hamma commit hashlari o'zgaradi. Buni faqat hammani ogohlantirib, jamoa kelishuvi bilan qiling. Boshqalar keyin git pull o'rniga reponi qayta clone qilishlari kerak bo'lishi mumkin.
π‘ LFS ham bepul emas: GitHub'da LFS uchun saqlash hajmi va trafik (bandwidth) bo'yicha cheklov bor (bepul rejada oyiga ma'lum GB). Katta video arxivlar uchun ba'zan maxsus xizmatlar yoki o'z LFS serveringiz arzonroq tushadi.
3-qism. Monorepo va polyrepo β bitta repomi, ko'pmi?¶
Uchinchi muammo: kompaniyada bir nechta loyiha bor. Ularni bitta repoda saqlaymizmi yoki har birini alohida repoda?
- Polyrepo ("ko'p repo") β har loyiha o'z reposida. Web-sayt, mobil ilova, API β uchta alohida
.git. - Monorepo ("yagona repo") β hamma loyiha bitta katta repoda, papkalarga bo'lingan holda.
Monorepo papka tuzilishi odatda shunday ko'rinadi:
kompaniya-repo/
βββ apps/
β βββ web/ (sayt)
β βββ mobil/ (ilova)
β βββ api/ (backend)
βββ libs/
βββ ulashilgan/ (uch loyiha ham foydalanadigan umumiy kod)
Solishtirish jadvali¶
| Mezon | Monorepo | Polyrepo |
|---|---|---|
| Umumiy kodni ulashish | Oson β bitta papka, hammaga ko'rinadi | Qiyin β submodule yoki paket (package) kerak |
| Atom o'zgarish | Bitta commit hamma loyihani bir vaqtda o'zgartiradi | Bir necha repoda alohida commit/PR kerak |
| Yagona versiya | Hamma kod doim bir xil versiyada | Har repo o'z versiyasida, sinxronlash boshog'rig'i |
| Repo hajmi va tezligi | Kattalashadi, CI sekinroq, maxsus tooling kerak | Har repo kichik va tez |
| Ruxsatlar | Hammaga butun repo ko'rinadi | Har repoga alohida ruxsat berish oson |
| Jamoa mustaqilligi | Jamolalar bir-biriga to'sqinlik qilishi mumkin | Har jamoa o'z reposida mustaqil ishlaydi |
Qachon qaysi biri?¶
π Universal "to'g'ri javob" yo'q β vaziyatga bog'liq. Lekin amaliy qoidalar bor:
β Monorepo quyidagi holatlarda yaxshi: - Loyihalar zich bog'liq va ko'p umumiy kod ulashadi; - Bitta o'zgarishni bir vaqtda bir necha loyihaga qo'llash kerak (masalan, API o'zgarsa, web va mobil ham birga yangilanishi shart); - Jamoa bir butun bo'lib ishlaydi, umumiy standartlar muhim.
β Polyrepo quyidagi holatlarda yaxshi: - Loyihalar mustaqil, kam bog'liq; - Har loyihaning alohida jamoasi, alohida reliz jadvali bor; - Ruxsatlarni qattiq ajratish kerak (masalan, tashqi pudratchiga faqat bitta repo ochasiz).
π‘ Kichik jamoa va mustaqil loyihalar uchun odatda polyrepo soddaroq β chunki monorepo katta repoda CI'ni tezlashtirish uchun maxsus vositalarni (Nx, Turborepo, Bazel kabi) talab qiladi. Monorepo'ning kuchi katta, zich bog'liq tizimlarda ochiladi (Google, Meta kabi kompaniyalar shu yo'lni tanlagan).
git subtree β submodule'ga muqobil (qisqacha)¶
Boshqa reponi o'z loyihangizga ulashning yana bir usuli β subtree. Submodule'dan farqi: subtree bola-reponing fayllarini bevosita sizning repongizga ko'chiradi (gitlink emas, haqiqiy fayllar).
# kutubxona-ui ni libs/ui ostiga, tarixini siqib (squash) qo'shish:
git subtree add --prefix=libs/ui https://github.com/foydalanuvchi/kutubxona-ui main --squash
Endi libs/ui/button.js β repongizdagi haqiqiy fayl, gitlink emas. Keyin yangilash:
| Jihat | Submodule | Subtree |
|---|---|---|
| Repoda nima saqlanadi | Faqat ko'rsatkich (gitlink) | Haqiqiy fayllar |
| Clone qiluvchi uchun | --recurse-submodules kerak |
Hech narsa kerak emas, fayllar shu yerda |
| Repo hajmi | Kichik (faqat hash) | Kattaroq (fayllar nusxasi) |
| O'rganish qiyinligi | Chigalroq (detached HEAD, ikki commit) | Soddaroq ishlatish, lekin buyruqlari uzun |
π‘ Maslahat: agar jamoangiz Git'da hali tajribasiz bo'lsa, submodule chigalliklaridan ko'ra subtree (yoki oddiy paket menejeri orqali bog'liqlik) ko'pincha tinchroq yo'l. Submodule kuchli, lekin uni noto'g'ri ishlatish oson.
Repo hajmini boshqarish¶
Repongiz nega katta ekanini bilish uchun:
size-pack β siqilgan tarixning umumiy hajmi. Agar u shubhali katta bo'lsa, kimdir katta binar faylni LFS'siz commit qilgan bo'lishi mumkin β buni git lfs migrate info bilan tekshiring.
π Repo shishishining oldini olishning eng arzon yo'li β profilaktika:
- Katta binar fayllarni LFS bilan kuzating (*.psd, *.mp4, *.zip);
- Yaratiladigan (build) papkalarni .gitignore ga qo'shing (node_modules/, dist/, *.log) β bularni hech qachon commit qilmang;
- Tasodifan kirib qolgan katta faylni darhol (keyingi commit qilishdan oldin) olib tashlash, tarixga o'rnashishidan ko'ra ming marta oson.
18-bob mashqlari¶
Quyidagi mashqlarni alohida sinov papkasida bajaring (asosiy ish loyihangizga tegmang). Har bir mashq uchun yangi yoki oldingisidan davom etadigan vaqtinchalik papka oching. Yechimlar yozilmagan β har birini o'zingiz qilib ko'ring.
- Ikki bo'sh papka yarating:
asosiy-loyihavakutubxona-ui. Ikkalasida hamgit initqiling, har biriga bittadan fayl qo'shib commit qiling. asosiy-loyihaichida turib,kutubxona-uinivendor/uipapkasiga submodule sifatida qo'shing (git submodule addβ local yo'l orqali).git statusnatijasini kuzating.- Hosil bo'lgan
.gitmodulesfaylini oching va ichidagipathhamdaurlqatorlarini tushuntiring. - Submodule'ni commit qiling. So'ng
git ls-tree HEAD vendor/uini ishlatib, gitlink rejimi (160000) va saqlangan commit hashini toping. git submodule statusni ishlating va submodule hozir qaysi commitda turganini aniqlang.asosiy-loyihani boshqa papkaga oddiygit clonebilan klon qiling.vendor/uipapkasi bo'sh ekanini tasdiqlang.- Klon ichida
git submodule update --init --recursiveni ishlatib,vendor/uini to'ldiring va fayllar paydo bo'lganini tekshiring. - Yangi joyga
asosiy-loyihanigit clone --recurse-submodulesbilan klon qiling. Endi submodule darhol to'lganiga ishonch hosil qiling. kutubxona-uireposiga (asl bola-repo) yangi fayl qo'shib commit qiling. So'ngasosiy-loyihadagit submodule update --remote vendor/uini ishlatib, gitlink yangi commitga ko'chganinigit difforqali kuzating ("Subproject commit" qatorini toping).- 9-mashqdagi o'zgarishni
asosiy-loyihada commit qiling. Bu nega ota-repoda alohida commit talab qilishini tushuntiring. - Submodule papkasiga (
vendor/ui) kiring vagit statusni ishlating. HEAD "detached" holatda ekanini ko'ring, so'nggit switch mainbilan shoxga o'ting. git submodule foreach 'git log --oneline -1'ni ishlatib, har submodule'ning oxirgi commitini chiqaring.git rm vendor/uibilan submodule'ni o'chiring va.gitmoduleshamda gitlink birga olib tashlanganini tekshiring.- Yangi papkada
git lfs installni ishlating. Natijada nima o'zgarganini (Git hooks) o'qing. git lfs track "*.psd"vagit lfs track "*.mp4"ni ishlatib, hosil bo'lgan.gitattributesfaylini ko'ring. Har bir qatordagifilter=lfsnimani anglatishini izohlang.- Bitta katta soxta binar fayl yarating (masalan, ~500 KB tasodifiy ma'lumot bilan
dizayn.psd). Uni.gitattributesbilan birgaaddvacommitqiling. git show HEAD:dizayn.psdni ishlating va repoda asl fayl emas, ko'rsatkich (version,oid,size) saqlanganini tasdiqlang.git lfs ls-filesvagit lfs trackbuyruqlari natijasini solishtiring: biri kuzatilayotgan fayllarni, ikkinchisi kuzatilayotgan shablonlarni ko'rsatadi β farqini ayting.- Yangi papkada
git subtree add --prefix=libs/ui <yo'l> main --squashbilan boshqa reponi subtree sifatida qo'shing. So'nggit ls-filesorqali fayllar (gitlink emas) bevosita repoda ekanini tekshiring. - O'z loyihalaringizdan birini tasavvur qiling (yoki real loyiha oling) va u uchun monorepo yoki polyrepo qaysi biri to'g'ri kelishini yozma asoslang: umumiy kod bormi, jamoa nechta, reliz jadvallari qanday β har bir mezon bo'yicha bir jumladan dalil keltiring.