Versiyalarni boshqarish va Git
Versiyalarni boshqarish tizimlari (VCS) manba kodidagi (yoki boshqa fayl va kataloglar to’plamidagi) o’zgarishlarni kuzatish uchun ishlatiladigan vositalardir. Nomi aytib turganidek, bu vositalar o’zgarishlar tarixini saqlashga yordam beradi; bundan tashqari, ular hamkorlikda ishlashni osonlashtiradi. Mantiqiy jihatdan, VCS’lar katalog va uning tarkibidagi o’zgarishlarni bir qator snapshot‘lar sifatida kuzatib boradi, bunda har bir snapshot eng yuqori darajadagi katalog ichidagi fayllar/kataloglarning butun holatini qamrab oladi. VCS’lar shuningdek, har bir snapshot’ni kim yaratganligi, har bir snapshot bilan bog’liq xabarlar va shunga o’xshash metama’lumotlarni saqlaydi.
Nima uchun versiyalarni boshqarish foydali? Hattoki o’zingiz yolg’iz ishlayotganingizda ham, u sizga loyihaning eski snapshot’larini ko’rishga, nima uchun ma’lum o’zgarishlar qilinganligi jurnalini yuritishga, parallel tarmoqlarda ishlashga va boshqa ko’plab ishlarni bajarishga imkon beradi. Boshqalar bilan ishlaganda esa, u boshqa odamlar nimalarni o’zgartirganini ko’rish, shuningdek, parallel dasturlashdagi ziddiyatlarni hal qilish uchun bebaho vositadir.
Zamonaviy VCS’lar quyidagi kabi savollarga osonlik bilan (va ko’pincha avtomatik ravishda) javob topishga imkon beradi:
- Bu modulni kim yozgan?
- Ushbu ma’lum faylning ushbu ma’lum qatori qachon tahrirlangan? Kim tomonidan? Nima uchun tahrirlangan?
- So’nggi 1000 ta reviziya davomida qachon/nima uchun ma’lum bir unit test ishlashdan to’xtadi?
Boshqa VCS’lar ham mavjud bo’lsa-da, Git versiyalarni boshqarish uchun amaldagi standart (de facto) hisoblanadi. Ushbu XKCD komiksi Git’ning obro’sini aks ettiradi:

Git’ning interfeysi yetarlicha mukammal bo’lmagan abstraksiya (leaky abstraction) bo’lgani uchun, Git’ni yuqoridan pastga (interfeysidan / buyruqlar satridan boshlab) o’rganish ko’p chalkashliklarga olib kelishi mumkin. Bir nechta buyruqlarni yodlab olish va ularni sehrli afsun sifatida tasavvur qilish, hamda biror narsa noto’g’ri ketganda yuqoridagi komiksdagi yondashuvga amal qilish oson.
Garchi Git interfeysi xunuk bo’lsa-da, uning asosiy dizayni va g’oyalari chiroyli. Xunuk interfeysni yodlash kerak bo’lsa, chiroyli dizaynni tushunish mumkin. Shu sababli, biz Git’ni uning ma’lumotlar modelidan boshlab va keyinroq buyruqlar satri interfeysini qamrab olgan holda pastdan yuqoriga qarab tushuntiramiz. Ma’lumotlar modeli tushunilgandan so’ng, buyruqlarni ular asosiy ma’lumotlar modelini qanday o’zgartirishi orqali yaxshiroq tushunish mumkin.
Git’ning ma’lumotlar modeli
Git’ning ixtirosi uning yaxshi o’ylangan ma’lumotlar modelida bo’lib, u versiyalarni boshqarishning tarixni saqlash, tarmoqlarni qo’llab-quvvatlash va hamkorlik qilishga imkon berish kabi barcha ajoyib xususiyatlarini ta’minlaydi.
Snapshot’lar
Git biror yuqori darajadagi katalog ichidagi fayllar va kataloglar to’plami tarixini snapshot’lar seriyasi sifatida modellashtiradi. Git terminologiyasida fayl “blob” deb ataladi va u shunchaki baytlar to’plamidir. Katalog esa “daraxt” deb ataladi va u nomlarni blob’lar yoki daraxtlarga xaritalaydi (shuning uchun kataloglar ichida boshqa kataloglar bo’lishi mumkin). Snapshot esa kuzatilayotgan eng yuqori darajadagi daraxtdir. Masalan, bizda quyidagicha daraxt bo’lishi mumkin:
<root> (tree)
|
+- foo (tree)
| |
| + bar.txt (blob, contents = "hello world")
|
+- baz.txt (blob, contents = "git is wonderful")
Eng yuqori darajadagi daraxt ikkita elementni o’z ichiga oladi: “foo” daraxti (u o’z ichida bitta “bar.txt” blob’ini saqlaydi) va “baz.txt” blob’i.
Tarixni modellashtirish: snapshot’larni bog’lash
Versiyalarni boshqarish tizimi snapshot’larni qanday bog’lashi kerak? Bitta oddiy model tarixni chiziqli qilib yaratish bo’lishi mumkin. Tarix vaqt tartibida joylashgan snapshot’lar ro’yxati bo’ladi. Ko’pgina sabablarga ko’ra, Git bunday oddiy modeldan foydalanmaydi.
Git’da tarix snapshot’larning yo’naltirilgan asiklik grafidir (DAG). Bu qandaydir murakkab matematik atama bo’lib tuyulishi mumkin, ammo qo’rqmang. Bu shunchaki Git’dagi har bir snapshot o’zidan oldingi snapshot’lar to’plamiga, ya’ni “ota-ona” (parents) larga ishora qilishini anglatadi. Bu yagona ota-ona emas (chiziqli tarixda bo’lgani kabi), balki ota-onalar to’plamidir, chunki bitta snapshot bir nechta ota-onadan kelib chiqqan bo’lishi mumkin, masalan, ikkita parallel dasturlash tarmog’ini birlashtirish (merge) natijasida.
Git bu snapshot’larni “commit” deb ataydi. Commit’lar tarixini vizualizatsiya qilish taxminan shunday ko’rinishi mumkin:
o <-- o <-- o <-- o
^
\
--- o <-- o
Yuqoridagi ASCII tasvirlarda o lar alohida commit’larga (snapshot’larga) mos keladi. O’qlar har bir commit’ning ota-onasiga ishora qiladi (bu “keyin keladi” emas, balki “oldin keladi” munosabatidir). Uchinchi commit’dan so’ng tarix ikkita alohida tarmoqqa bo’linadi. Bu, masalan, bir-biridan mustaqil ravishda parallel ishlab chiqilayotgan ikkita alohida xususiyatga mos kelishi mumkin. Kelajakda bu tarmoqlar har ikkala xususiyatni o’z ichiga olgan yangi snapshot yaratish uchun birlashtirilishi mumkin, va buning natijasida quyidagicha yangi tarix hosil bo’ladi, bu yerda yangi yaratilgan birlashtirilgan commit qalin qilib ko’rsatilgan:
o <-- o <-- o <-- o <---- o
^ /
\ v
--- o <-- o
Git’da commit’lar o’zgarmasdir (immutable). Biroq, bu xatolarni to’g’irlab bo’lmaydi degani emas; shunchaki commit tarixidagi “tahrirlar” aslida mutlaqo yangi commit’larni yaratadi va havolalar (quyiga qarang) yangilariga ishora qilish uchun yangilanadi.
Ma’lumotlar modeli, psevdokod sifatida
Git’ning ma’lumotlar modelini psevdokodda yozib ko’rish foydali bo’lishi mumkin:
// a file is a bunch of bytes
type blob = array<byte>
// a directory contains named files and directories
type tree = map<string, tree | blob>
// a commit has parents, metadata, and the top-level tree
type commit = struct {
parents: array<commit>
author: string
message: string
snapshot: tree
}
Bu tarixning toza va oddiy modelidir.
Obyektlar va kontentni manzillash
“Obyekt” bu blob, daraxt yoki commit’dir:
type object = blob | tree | commit
Git’ning ma’lumotlar omborida barcha obyektlar SHA-1 xeshi orqali kontent bilan manzillanadi.
objects = map<string, object>
def store(object):
id = sha1(object)
objects[id] = object
def load(id):
return objects[id]
Blob’lar, daraxtlar va commit’lar shu tarzda birlashtiriladi: ularning barchasi obyektlardir. Ular boshqa obyektlarga havola berganda, ularni haqiqatda diskdagi ko’rinishida o’z ichiga olmaydi, balki ularga xeshlari orqali havola qiladi.
Masalan, yuqoridagi misol katalog tuzilmasi uchun daraxt (git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d yordamida vizualizatsiya qilingan) quyidagicha ko’rinadi:
100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85 baz.txt
040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87 foo
Daraxt o’z ichiga olgan elementlariga, baz.txt (blob) va foo (daraxt) ga ko’rsatkichlarni saqlaydi. Agar biz baz.txt ga mos keluvchi xesh bilan manzillangan kontentga git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85 bilan qarasak, quyidagini ko’ramiz:
git is wonderful
Havolalar
Endi, barcha snapshot’lar o’zlarining SHA-1 xeshlari orqali aniqlanishi mumkin. Ammo bu noqulay, chunki odamlar 40 ta o’n oltilik tizimdagi belgilardan iborat qatorlarni eslab qolishga usta emas.
Git’ning bu muammoga yechimi SHA-1 xeshlari uchun “havolalar” deb ataluvchi inson o’qiy oladigan nomlarni taqdim etishdir. Havolalar commit’larga ko’rsatkichlardir. O’zgarmas (immutable) bo’lgan obyektlardan farqli o’laroq, havolalar o’zgaruvchandir (yangi commit’ga ishora qilish uchun yangilanishi mumkin). Masalan, master havolasi odatda asosiy ish tarmog’idagi oxirgi commit’ga ishora qiladi.
references = map<string, string>
def update_reference(name, id):
references[name] = id
def read_reference(name):
return references[name]
def load_reference(name_or_id):
if name_or_id in references:
return load(references[name_or_id])
else:
return load(name_or_id)
Buning yordamida, Git tarixning qaysidir bir qismiga uzundan-uzoq o’n oltilik satr bilan emas, balki “master” kabi odam o’qiy oladigan nomlardan foydalanib havola qilishi mumkin.
Yana bir detal shundaki, biz yangi snapshot yaratganimizda, u nimaga nisbatan olinganligini (commit’ning parents maydonini qanday o’rnatishimizni) bilishimiz uchun ko’pincha tarixda “hozir qayerdaligimiz” haqida tushunchaga ega bo’lishni xohlaymiz. Git’da o’sha “hozir qayerdamiz” degan tushuncha “HEAD” deb ataluvchi maxsus havoladir.
Repozitoriylar
Nihoyat, biz Git repozitoriysi (taxminan) nima ekanligini ta’riflashimiz mumkin: bu objects (obyektlar) va references (havolalar) ma’lumotlaridir.
Diskda Git butunlay faqat obyektlar va havolalarni saqlaydi: Git’ning ma’lumotlar modeli faqat shulardan iborat. Barcha git buyruqlari obyektlar qo’shish va havolalarni qo’shish/yangilash orqali commit DAG’ini (yo’naltirilgan asiklik grafini) qandaydir tahrirlashga mos keladi.
Har qanday buyruqni kiritganingizda, o’ylab ko’ringki bu buyruq ostidagi graf ma’lumotlar tuzilmasida qanday o’zgartirishlar qilmoqda. Va aksincha, agar siz commit DAG’ida biror o’zgartirish kiritmoqchi bo’lsangiz, masalan, “commit qilinmagan o’zgarishlarni bekor qilish va ‘master’ havolasini 5d83f9e commit’ga o’tkazish”, bu uchun ham, ehtimol, buyruq mavjud (masalan, ushbu holatda, git checkout master; git reset --hard 5d83f9e).
Staging area
Bu ma’lumotlar modeliga bevosita bog’liq bo’lmagan yana bir tushuncha, lekin u commit’larni yaratish uchun mo’ljallangan interfeysning bir qismidir.
Siz yuqorida ta’riflanganidek, snapshot yaratishni ishchi katalogning joriy holatiga asoslangan yangi snapshot yaratadigan “snapshot yaratish” buyrug’i bo’lishi deb o’ylashingiz mumkin. Ba’zi versiyalarni boshqarish vositalari shunday ishlaydi, ammo Git emas. Bizga toza snapshot’lar kerak, va doim ham mavjud holatdan snapshot yaratish eng to’g’ri tanlov bo’lmasligi mumkin. Masalan, ikkita turli xususiyat qo’shganingizni va bittasi faqat birinchi xususiyatni, ikkinchisi esa faqat ikkinchisini qo’shadigan ikkita alohida commit yaratmoqchi ekanligingizni tasavvur qiling. Yoki butun kodingiz bo’ylab xatoliklarni topish (debugging) uchun print ko’rsatmalari va qo’shimcha ravishda xatolik tuzatmasi (bugfix) bor deylik; barcha print’larni chetga surib faqat bugfix’ni commit qilmoqchi bo’lishingiz mumkin.
Git bunday ssenariylarga “staging area” deb ataladigan mexanizm orqali navbatdagi snapshot’ga aynan qaysi o’zgarishlar kiritilishini aniq ko’rsatishga ruxsat berish orqali moslashadi.
Git buyruqlar satri interfeysi
Ma’lumotlarni takrorlamaslik uchun, quyidagi buyruqlarni ushbu ma’ruza matnlarida batafsil tushuntirmaymiz. Ko’proq ma’lumot olish uchun tavsiya etilgan Pro Git kitobini o’qing yoki ma’ruza videosini ko’ring.
Asoslar
git help <command>: git buyrug’i uchun yordam olinggit init:.gitkatalogida saqlanadigan ma’lumotlarga ega yangi git repozitoriysini yaratadigit status: nima bo’layotganini ko’rsatadigit add <filename>: fayllarni staging area’ga qo’shadigit commit: yangi commit yaratadi- Yaxshi commit xabarlarini yozing!
- Yaxshi commit xabarlarini yozish uchun yana ko’proq sabablar!
git log: tarixning tekislangan jurnalini ko’rsatadigit log --all --graph --decorate: tarixni DAG sifatida tasvirlaydigit diff <filename>: siz kiritgan o’zgarishlarni staging area’ga nisbatan ko’rsatadigit diff <revision> <filename>: snapshot’lar orasidagi fayldagi farqlarni ko’rsatadigit checkout <revision>: HEAD’ni (va agar tarmoqqa o’tilayotgan bo’lsa, joriy tarmoqni) yangilaydi
Tarmoqlash va birlashtirish
git branch: tarmoqlarni ko’rsatadigit branch <name>: tarmoq yaratadigit switch <name>: tarmoqqa o’tadigit checkout -b <name>: tarmoq yaratadi va unga o’tadigit branch <name>; git switch <name>bilan bir xil
git merge <revision>: joriy tarmoqqa birlashtiradigit mergetool: ziddiyatlarni hal qilishga yordam beruvchi maxsus vositadan foydalaninggit rebase: yamoqlar (patches) to’plamini yangi bazaga o’tkazish (rebase)
Masofaviy repozitoriylar
git remote: masofaviy repozitoriylarni ro’yxatlaydigit remote add <name> <url>: masofaviy repozitoriy qo’shishgit push <remote> <local branch>:<remote branch>: obyektlarni masofaviy repozitoriyga yuborish va masofaviy havolani yangilashgit branch --set-upstream-to=<remote>/<remote branch>: mahalliy va masofaviy tarmoqlar o’rtasidagi bog’liqlikni o’rnatishgit fetch: masofaviy repozitoriydan obyektlar/havolalarni yuklab olishgit pull:git fetch; git mergebilan bir xilgit clone: masofaviy repozitoriyni yuklab olish
Bekor qilish (Undo)
git commit --amend: commit’ning tarkibini/xabarini tahrirlashgit reset <file>: faylni staging area’dan chiqarib tashlashgit restore: o’zgarishlarni bekor qilish
Murakkab Git
git config: Git yuqori darajada moslashtiriladigan (customizable) tizimdirgit clone --depth=1: barcha versiyalar tarixini olmaydigan yuzaki (shallow) klonlashgit add -p: interaktiv tarzda staging area’ga qo’shishgit rebase -i: interaktiv rebasegit blame: qaysi qatorni oxirgi marta kim tahrirlaganini ko’rsatadigit stash: ishchi katalogga kiritilgan o’zgartirishlarni vaqtinchalik olib tashlashgit bisect: tarix bo’yicha binar qidiruv (masalan, regressiyalarni topish uchun)git revert: avvalgi commit ta’sirini teskarisiga o’zgartiradigan yangi commit yaratishgit worktree: bir vaqtning o’zida bir nechta tarmoqlarni ochib ishlash.gitignore: ataylab kuzatilmaydigan fayllarni e’tiborsiz qoldirishni belgilash
Turli xil narsalar
- GUI’lar: Git uchun juda ko’p GUI (grafik interfeys) mijozlari mavjud. Shaxsan biz ulardan foydalanmaymiz va uning o’rniga buyruqlar satri interfeysidan foydalanamiz.
- Shell integratsiyasi: shell so’rovida Git holati ko’rinib turishi juda qulay (zsh, bash). Ko’pincha Oh My Zsh kabi freymvorklar tarkibiga kiradi.
- Tahrirlovchi (Editor) integratsiyasi: xuddi yuqoridagiga o’xshab, juda ko’p imkoniyatlarga ega qulay integratsiyalar mavjud. fugitive.vim Vim uchun standart hisoblanadi.
- Ish oqimlari (Workflows): biz sizga ma’lumotlar modelini, hamda ba’zi asosiy buyruqlarni o’rgatdik; yirik loyihalarda ishlaganda qanday amaliyotlarga rioya qilish kerakligini aytmadik (buning ko’plab turli xil yondashuvlari mavjud).
- GitHub: Git bu GitHub emas. GitHub boshqa loyihalarga kod qo’shishning o’ziga xos usuliga ega bo’lib, u pull request deb ataladi.
- Boshqa Git provayderlari: GitHub yagona emas: GitLab va BitBucket kabi ko’plab boshqa Git repozitoriylari xostlari ham mavjud.
Manbalar
- Pro Git ni o’qish jiddiy tavsiya etiladi. Ma’lumotlar modelini tushunib olganingizdan so’ng, 1-5 boblarni o’qib chiqish sizga Git’dan qanday professional tarzda foydalanish kerakligi bo’yicha ko’p narsani o’rgatadi. Keyingi boblar qiziqarli, murakkab materiallarni o’z ichiga oladi.
- Oh Shit, Git!?! bu ba’zi umumiy Git xatolarini qanday to’g’irlash bo’yicha qisqacha qo’llanma.
- Kompyuter mutaxassislari uchun Git Git’ning ma’lumotlar modeli bo’yicha qisqacha tushuntirish bo’lib, ushbu ma’ruza matnlaridan ko’ra kamroq psevdokod va chiroyliroq diagrammalardan iborat.
- Pastdan yuqoriga Git (Git from the Bottom Up) Git qanday amalga oshirilgani haqida qiziquvchilar uchun faqat ma’lumotlar modelidan tashqari batafsil tushuntirishdir.
- Git’ni oddiy so’zlar bilan qanday tushuntirish mumkin
- Learn Git Branching brauzer orqali Git’ni o’rgatuvchi o’yin.
Mashqlar
- Agar siz Git bilan oldin ishlash tajribasiga ega bo’lmasangiz, Pro Git ning dastlabki bir necha bobini o’qishga yoki Learn Git Branching kabi qo’llanmadan o’tishga harakat qiling. O’qish jarayonida Git buyruqlarini ma’lumotlar modeliga bog’lab o’rganing.
- Kurs veb-sayti repozitoriysini klonlang.
- Versiyalar tarixini graf sifatida tasvirlab o’rganing.
README.mdfaylini oxirgi marta kim o’zgartirgan? (Maslahat: argument bilangit logdan foydalaning)._config.ymlfaylidagicollections:qatoriga kiritilgan oxirgi o’zgartirish bilan bog’liq commit xabari nima edi? (Maslahat:git blamevagit showdan foydalaning).
- Git’ni o’rganayotgandagi eng keng tarqalgan xatolardan biri bu Git tomonidan boshqarilmasligi kerak bo’lgan katta hajmdagi fayllarni yoki maxfiy ma’lumotlarni commit qilishdir. Repozitoriyga bitta fayl qo’shishga urinib ko’ring, bir nechta commit’lar yarating va so’ngra ushbu faylni tarixdan butunlay o’chirib tashlang (nafaqat so’nggi commit’dan). Buni qanday qilishni bilish uchun buni o’qib chiqsangiz bo’ladi.
- GitHub’dan biron bir repozitoriyni klonlang va undagi mavjud fayllardan birini o’zgartiring. Agar siz
git stashni bajarsangiz nima sodir bo’ladi?git log --all --onelineni bajarganda nimani ko’ryapsiz?git stashorqali qilgan amalingizni orqaga qaytarish uchungit stash popbuyrug’ini bajaring. Bu qaysi ssenariyda foydali bo’lishi mumkin? - Ko’pgina buyruqlar satri vositalari singari, Git ham
~/.gitconfignomli konfiguratsiya fayli (yoki nuqtali fayl) ni taqdim etadi.~/.gitconfigichida qisqartma (alias) yarating, shu tufayli qachonkigit graphbuyrug’ini bersangiz, sizgagit log --all --graph --decorate --onelinenatijasi chiqadigan bo’lsin. Buni~/.gitconfigfaylini bevosita tahrirlash orqali yoki alias qo’shish uchungit configbuyrug’ini ishlatish bilan amalga oshirishingiz mumkin. Git alias’lari bo’yicha ma’lumotlarni bu yerdan topsangiz bo’ladi. git config --global core.excludesfile ~/.gitignore_globalbuyrug’ini berib,~/.gitignore_globalfaylida global e’tiborsiz qoldirish naqshlari (ignore patterns) ni aniqlashingiz mumkin. Bu Git foydalanadigan global ignore fayli manzilini o’rnatadi, biroq siz baribir o’sha manzilda faylni qo’lda yaratishingiz kerak. O’zingizning operatsion tizimingiz yoki tahrirlovchingizga xos vaqtinchalik fayllarni, masalan,.DS_Storeni e’tiborsiz qoldirish (ignore qilish) uchun global gitignore faylingizni sozlang.- Kurs veb-sayti repozitoriysini fork qiling, imlo xatosini (typo) yoki o’zingiz kiritishingiz mumkin bo’lgan boshqa yaxshilanishni topib, GitHub’da pull request jo’nating (Buning uchun buni ko’rishingiz mumkin). Iltimos, faqatgina foydali bo’lgan PR’larni yuboring (iltimos, bizga spam yubormang!). Agar hech qanday yaxshilanish topa olmasangiz, bu mashqni o’tkazib yuborishingiz mumkin.
- Hamkorlikdagi stsenariyni simulyatsiya qilib, ziddiyatlarni (merge conflicts) hal qilishni amalda sinab ko’ring:
git initorqali yangi repozitoriy yarating va ichida bir nechta qator (masalan, retsept) dan iboratrecipe.txtdegan fayl yarating.- Uni commit qiling, so’ngra ikkita tarmoq (branch) yarating:
git branch saltyvagit branch sweet. saltytarmog’ida bir qatorni o’zgartiring (masalan, “1 piyola shakar” ni “1 piyola tuz” ga o’zgartiring) va commit qiling.sweettarmog’ida ham xuddi shu qatorni boshqacha qilib o’zgartiring (masalan, “1 piyola shakar” ni “2 piyola shakar” ga o’zgartiring) va commit qiling.- Endi
mastertarmog’iga qayting va avvalgit merge salty, so’nggit merge sweetni bajarib ko’ring. Nima sodir bo’ladi?recipe.txtfaylining tarkibini ko’ring - undagi<<<<<<<,=======va>>>>>>>belgilari nimani anglatadi? - O’zingiz qoldirmoqchi bo’lgan tarkibni saqlab faylni tahrirlash orqali ziddiyatni hal qiling, ziddiyat belgilarini olib tashlang va birlashtirishni
git addvagit commit(yokigit merge --continue) orqali yakunlang. Muqobil variant sifatida, grafik yoki terminal asosidagi merge vositalaridan foydalanib ziddiyatni hal qilish uchungit mergetoolni ishlating. - O’zingiz hozirgina yaratgan birlashish tarixini (merge history) ko’rish uchun
git log --graph --onelinedan foydalaning.
CC BY-NC-SA asosida litsenziyalangan.