15 β Stash, tag va cherry-pick¶
β¬ οΈ Oldingi: 14 β Hamkorlik oqimlari: Git Flow, GitHub Flow Β· π README Β· Keyingi: 16 β Tarixni tozalash va reflog β‘οΈ
Bu bobda: kundalik ishda eng ko'p asqotadigan uchta "qutqaruvchi" buyruqni o'rganamiz. Birinchisi β
git stash: yarim qolgan ishni commit qilmasdan, vaqtincha "javonga" qo'yib turish (shoshilinch boshqa branchga o'tish kerak bo'lganda). Ikkinchisi βgit tag: reliz (chiqarilgan versiya) chiqqan commitniv1.0.0kabi yorliq bilan belgilash, lightweight va annotated (izohli) teg farqi, semantik versiyalash (major.minor.patch) va GitHub Releases. Uchinchisi βgit cherry-pick: butun branchni emas, bittagina kerakli commitni boshqa branchga ko'chirish (xato joyga commit qilib qo'yganda yoki shoshilinch tuzatishni tezda yetkazganda). Har birini sinab ko'rilgan buyruqlar va konflikt holatlari bilan ko'rib chiqamiz.
Muammo¶
Tasavvur qiling: siz do'kon sayti ustida ishlayotgan jamoada savat-sahifa degan branch'da ishlayapsiz. Savat sahifasi yarmiga keldi β ikkita fayl o'zgargan, lekin hali ishlamaydi, commit qilsa uyat bo'ladi. Shu payt boshliq qo'ng'iroq qiladi: "saytda narxlar noto'g'ri ko'rsatilyapti, mijozlar shikoyat qilyapti, hoziroq tuzat!".
Vaziyat tig'iz:
- Yarim ishingizni commit qila olmaysiz β hali tugamagan, buzuq holatda.
- Lekin boshqa branchga o'tish uchun ish daraxtingiz (working directory) toza bo'lishi kerak β aks holda Git o'zgarishlaringizni yangi branchga sudrab boradi yoki o'tkazmaydi.
- Tuzatishni qilgach, GitHub'da uni
v2.3.1deb belgilash va e'lon qilish kerak. - Tuzatishni ehtimol bir nechta branchga (
main'ga ham,relizga-tayyorbranch'ga ham) yetkazish kerak β lekin butun branchni emas, faqat o'sha bitta tuzatishni.
Bu uchta ehtiyoj β aynan stash, tag va cherry-pick hal qiladigan uchta klassik muammo. Birma-bir ko'ramiz.
git stash β yarim ishni vaqtincha "javonga" qo'yish¶
git stash (stash β "yashirib qo'yish, zaxiraga olish") ish daraxtidagi barcha o'zgarishlarni oladi, ularni alohida bir joyga (xuddi javonga qo'ygandek) saqlaydi va ish daraxtingizni oxirgi commit holatiga qaytaradi β ya'ni tozalaydi. O'zgarishlar yo'qolmaydi, ular "javon"da turadi; xohlagan paytda qaytarib olasiz.
Sinab ko'ramiz. Bu buyruqlarni loyihangizga emas, alohida bo'sh papkada bajaring (har doimgidek):
Endi ishni "yarim" qilamiz β faylni o'zgartiramiz va yangi (untracked β Git hali bilmaydigan) fayl qo'shamiz:
M β o'zgargan (modified), ?? β Git bilmaydigan (untracked) fayl. Mana shu holatda boshqa branchga o'tib bo'lmaydi. Ishni javonga qo'yamiz:
π -m "..." β stashga izoh beradi. Bir nechta stash bo'lganda qaysi biri nima ekanini izohsiz eslab bo'lmaydi β izohni doim yozish odat tusiga kirsin. push so'zini tushirib git stash deb ham yozsa bo'ladi (eski qisqa shakl), lekin -m izoh berish uchun push kerak.
Endi ish daraxti holatini ko'ring:
Natija β bo'sh. Ish daraxti toza, xuddi hech narsa qilmagandek. Endi xotirjam boshqa branchga o'tib, shoshilinch tuzatishni qila olasiz.
-u nima uchun kerak? (untracked fayllar tuzog'i)¶
π Oddiy git stash faqat Git biladigan (tracked) fayllarni javonga oladi. Yuqoridagi yangi.txt β untracked, demak oddiy stash uni qoldirib ketadi β u ish daraxtida osilib qoladi va daraxt toza bo'lmaydi. -u (yoki to'liq --include-untracked) β untracked fayllarni ham javonga oladi. Shuning uchun "hammasini tozalab tashla" desangiz, deyarli doim -u bilan ishlating.
π‘ .gitignore'da yashiringan fayllarni ham olish uchun -a (--all) bor, lekin u kamdan-kam kerak va ehtiyot bo'lib ishlatiladi β odatda -u yetarli.
Stashni qaytarib olish: pop va apply¶
Tuzatishni tugatib, savat ishingizga qaytdingiz. Endi javondagi ishni tiklaymiz:
On branch main
Changes not staged for commit:
modified: narx.js
Untracked files:
yangi.txt
Dropped refs/stash@{0} (a1b2c3d4e5f6...)
narx.js o'zgargani ham, yangi.txt ham qaytib keldi. pop ikki ishni qiladi: o'zgarishlarni qaytaradi va javondan o'sha stashni o'chiradi (oxirida "Dropped..." β o'chirildi).
π pop va apply farqi:
| Buyruq | O'zgarishni qaytaradimi? | Javondan o'chiradimi? |
|---|---|---|
git stash pop |
β ha | β ha (javon tozalanadi) |
git stash apply |
β ha | β yo'q (javonda qoladi) |
π‘ apply qachon kerak? Bitta stashni bir nechta branchga qo'llamoqchi bo'lsangiz: apply bilan tiklang, javonda saqlanadi, keyin boshqa branchda yana apply qilasiz. Ishingiz bitgach git stash drop bilan qo'lda o'chirasiz.
Bir nechta stash bilan ishlash¶
Stash β bu "stack" (uyum): yangi qo'ygan tepada (stash@{0}), oldingilari pastga suriladi (stash@{1}, stash@{2} ...). Royxatni ko'rish:
Aniq bir stashni tanlab ishlash uchun nomini yozasiz:
git stash apply stash@{1} # 1-raqamlini qaytar (javonda qoldirib)
git stash pop stash@{1} # 1-raqamlini qaytar va o'chir
git stash drop stash@{1} # qaytarmasdan shunchaki o'chir
git stash show -p stash@{0} # ichida nima o'zgarganini ko'rish (diff)
π git stash clear β butun javonni birdan tozalaydi (hamma stash o'chadi, qaytarib bo'lmaydi). Ehtiyot bo'ling: bu buyruqni adashib ishlatib, kerakli ishni yo'qotish oson. Aniq bittasini o'chirish kerak bo'lsa, clear emas, drop ishlating.
π‘ Stash branchga bog'lanmagan β main'da stash qilib, savat-sahifa'da pop qilsangiz ham ishlaydi. Lekin o'zgargan fayllar boshqa branchda boshqacha bo'lsa, konflikt chiqishi mumkin (konfliktni hal qilishni 8-bobda ko'rgan edik β bu yerda ham xuddi shunday markerlar bilan ishlanadi).
git tag β relizni belgilash¶
Tuzatish tayyor, sayt yana ishlayapti. Endi bu holatni "rasman e'lon qilingan versiya" deb belgilash kerak β ertaga "qaysi commit o'sha tuzatilgan versiya edi?" degan savol tug'ilmasin. Teg (tag) β muayyan commitga osilgan o'zgarmas yorliq. Branch'dan farqi: branch yangi commit qo'shilganda oldinga suriladi, teg esa bir marta osilsa, o'sha joyda abadiy qoladi.
Lightweight vs annotated teg¶
Tegning ikki turi bor:
1. Lightweight (yengil) teg β shunchaki commitga osilgan nom, qo'shimcha ma'lumotsiz:
2. Annotated (izohli) teg β -a (annotated) va -m (xabar) bilan. Bu to'laqonli obyekt: kim qo'ygani, qachon va izoh bilan saqlanadi:
Farqni o'z ko'zingiz bilan ko'rasiz:
Lightweight teg shunchaki commitga ishora qiladi (commit), annotated teg esa alohida tag obyekti bo'lib, ichida izoh va sana saqlanadi.
π Qoida: relizlar uchun doim annotated (-a -m) ishlating. Sababi: kim, qachon, nima uchun bu versiyani chiqarganini teg ichida saqlaydi β bu reliz tarixi uchun muhim. Lightweight teg β vaqtinchalik, shaxsiy belgilar uchun (masalan "shu yerga keyin qaytaman").
Annotated teg ma'lumotini ko'rish:
Avvalgi commitga teg qo'yish¶
Teg qo'yishni unutib, ustiga yana commit qilib qo'ygan bo'lsangiz ham mayli β teg orqaga qarab ham qo'yiladi. Commit hashini yoki nisbiy ishorani (HEAD~2 β ikki orqadagi commit) yozasiz:
Teglar royxatini izohlari bilan ko'rish:
Semantik versiyalash (semver)¶
Versiya raqamlarini boshi berk ko'chada o'ylab topmaslik uchun butun dunyo semantic versioning (semver) qoidasiga amal qiladi: vMAJOR.MINOR.PATCH, masalan v2.1.4.
| Qism | Qachon oshadi | Misol |
|---|---|---|
| MAJOR | eski kod bilan moslik buzilsa (katta o'zgarish) | 1.x.x β 2.0.0 |
| MINOR | yangi funksiya qo'shilsa, lekin eskisi ishlayversa | 2.1.x β 2.2.0 |
| PATCH | faqat xato tuzatilsa | 2.2.3 β 2.2.4 |
π‘ Qoida: yuqoriroq qism oshganda, undan past qismlar nolga qaytadi. 1.4.2'da katta o'zgarish bo'lsa β 2.0.0 (minor ham, patch ham nolga tushadi). Bizning hikoyamizdagi shoshilinch narx tuzatishi β bu PATCH: v2.3.0 β v2.3.1.
Teglarni GitHub'ga jo'natish (push)¶
π Eng ko'p uchraydigan tuzoq: oddiy git push teglarni jo'natmaydi! Teglar maxsus jo'natiladi. Bitta tegni nomi bilan:
Yoki barcha lokal teglarni birdan:
π‘ --tags qulay, lekin u hamma lokal tegingizni jo'natadi β shaxsiy/vaqtinchalik lightweight teglaringiz ham ketib qoladi. Faqat reliz teglarini jo'natayotganingizga ishonch hosil qilish uchun ko'pincha har bir relizni nomi bilan alohida push qilish toza yo'l.
Teg jo'natilgach, GitHub avtomatik ravishda Releases bo'limida uni ko'rsatadi. GitHub'da reliz sahifasiga o'tib, "Draft a new release" β tegni tanlab (yoki yangi yaratib), reliz izohi (release notes β nima o'zgargani) va kerak bo'lsa yuklab olinadigan fayllar (masalan tayyor .zip, .exe) qo'shasiz. Foydalanuvchilar aynan shu sahifadan versiyalarni ko'radi va yuklab oladi.
π Tegni xato qo'ygan bo'lsangiz: lokalda git tag -d v2.3.1 bilan o'chiriladi. GitHub'dan ham o'chirish uchun: git push origin --delete v2.3.1. Lekin boshqalar allaqachon yuklab olgan tegni o'zgartirmang β yangi versiya chiqaring (masalan v2.3.2).
git cherry-pick β bitta commitni ko'chirish¶
Oxirgi muammo: tuzatishingiz savat-sahifa branch'ida turibdi (shoshilinch tuzatishni o'sha yerda qilib yuborgan bo'lsangiz), lekin u main'ga ham kerak β hozir, butun yarim savat ishini kutmasdan. Yoki teskari: adashib main'ga commit qilib, keyin "voy, bu boshqa branchga tegishli edi" deb tushundingiz.
git cherry-pick (so'zma-so'z "gilosni terib olish") aynan shu uchun: butun branchni merge qilmasdan, bittagina kerakli commitni joriy branchga ko'chiradi.
Sinab ko'ramiz. Bo'sh papkada:
git init -b main
echo "1" > a.txt && git add a.txt && git commit -m "1-commit"
echo "2" > b.txt && git add b.txt && git commit -m "2-commit"
git switch -c hotfix # yangi branch ochib, unga o'tamiz
echo "tuzatildi" > fix.txt
git add fix.txt
git commit -m "muhim tuzatish"
Endi hotfix'dagi tuzatishni main'ga ko'chiramiz. Avval ko'chiriladigan commit hashini eslab qolamiz, keyin main'ga o'tamiz:
git log --oneline -1 # tuzatish commiti hashi, masalan a1b2c3d
git switch main
git cherry-pick a1b2c3d
Endi main'da fix.txt paydo bo'ldi β hotfix'dagi boshqa o'zgarishlarsiz, faqat o'sha bitta commit ko'chdi.
π Muhim nuqta: cherry-pick commitni ko'chirmaydi, nusxalaydi. Asl commit hotfix'da o'z joyida qoladi; main'da esa yangi hash bilan yangi commit paydo bo'ladi (mazmun bir xil, lekin bu boshqa commit). Shuning uchun rasmda asl X va nusxa X' deb belgilangan.
π‘ Bir nechta commitni ketma-ket ko'chirish ham mumkin:
git cherry-pick a1b2c3d e4f5g6h # ikkita aniq commit
git cherry-pick a1b2c3d^..e4f5g6h # oraliq (diapazon)
Cherry-pick konflikti¶
Agar ko'chirilayotgan commit o'zgartirgan joyni joriy branch ham o'zgartirgan bo'lsa β konflikt chiqadi (xuddi merge'dagidek). Buni ko'rib chiqaylik:
git init -b main
echo "narx 0" > narx.txt && git add narx.txt && git commit -m "boshlang'ich"
git switch -c branch-a
echo "narx 100" > narx.txt && git add narx.txt && git commit -m "narx 100"
git switch main
echo "narx 200" > narx.txt && git add narx.txt && git commit -m "narx 200"
Ikki branch ham narx.txtning o'sha satrini boshqacha o'zgartirdi. Endi branch-a'dagi commitni main'ga olib o'tishga urinamiz:
Auto-merging narx.txt
CONFLICT (content): Merge conflict in narx.txt
error: could not apply b7051db... narx 100
hint: ... "git add", then run "git cherry-pick --continue".
hint: ... skip this commit with "git cherry-pick --skip".
hint: ... abort with "git cherry-pick --abort".
narx.txtni ochsangiz, 8-bobdan tanish konflikt markerlarini ko'rasiz:
Hal qilish β xuddi merge konfliktidagidek: faylni qo'lda kerakli holatga tahrirlab (markerlarni olib tashlab), add qilib, keyin --continue bilan davom ettirasiz:
# narx.txt ni tahrirlab, kerakli variantni qoldiring, markerlarni o'chiring
git add narx.txt
git cherry-pick --continue
Cherry-pick'da uchta chiqish yo'li bor:
| Buyruq | Nima qiladi |
|---|---|
git cherry-pick --continue |
konfliktni hal qilgach davom etadi |
git cherry-pick --abort |
hammasini bekor qilib, boshlang'ich holatga qaytaradi |
git cherry-pick --skip |
shu commitni tashlab, keyingisiga o'tadi (bir nechta pick'da) |
π Konflikt chiqsa vahima qilmang β git status doim qaysi faylda konflikt borligini va keyingi qadamni aytib turadi. Chalkashib ketsangiz, --abort hech narsani buzmasdan boshlang'ich holatga qaytaradi.
π‘ Cherry-pick'ni qachon ishlatmaslik kerak? Agar butun branchni olib o'tmoqchi bo'lsangiz β bu merge yoki rebase ishi, cherry-pick emas. Cherry-pick β aynan bir-ikki tanlangan commit uchun. Ko'p commitni cherry-pick qilsangiz, asl va nusxa orasida chalkashlik (ikki xil hashda bir xil o'zgarish) kelib chiqadi.
15-bob mashqlari¶
Quyidagi mashqlarni alohida sinov papkasida bajaring (loyihangizga emas). Har biri uchun avval kichik repozitoriy yarating va sinab ko'ring.
- Bo'sh papkada repo yarating, bitta fayl commit qiling. Faylni o'zgartiring va
git stashbilan javonga qo'ying.git statustoza bo'lganini tasdiqlang, keyingit stash popbilan qaytaring. - Bitta tracked faylni o'zgartiring va yangi (untracked) fayl qo'shing. Oddiy
git stashqilib ko'ring βgit status'da untracked fayl qolib ketganiga e'tibor bering. - 2-mashqdagi holatni qaytadan yarating, lekin endi
git stash -ubilan stash qiling. Bu safar ish daraxti to'liq tozalanganini tasdiqlang. - Stash qilgan ishni
git stash popo'rnigagit stash applybilan qaytaring.git stash list'da stash hali javonda turganini ko'ring, so'ng unigit stash dropbilan o'chiring. - Ketma-ket uchta turli o'zgarishni uchta alohida stashga qo'ying.
git stash listbilan uchalasini ko'ring va izohlari (-m) qaysi qaysiligini farqlang. - 5-mashqdagi stashlardan
stash@{1}nigit stash show -p stash@{1}bilan ko'ring (ichida nima o'zgargani), keyin aynan o'sha stashni nomi bilanpopqiling. main'da yarim ishni stash qiling, yangi branch (git switch -c sinov) oching va o'sha branchdagit stash popqiling. Stash branchga bog'lanmaganini, ishingiz yangi branchga tushganini ko'ring.- Repoda 2-3 commit yarating. Oxirgi commitga lightweight teg qo'ying (
git tag v0.1.0).git cat-file -t v0.1.0natijasicommitekanini tasdiqlang. - Xuddi shu commitga annotated teg qo'ying (
git tag -a v1.0.0 -m "...").git cat-file -t v1.0.0natijasitagekanini vagit show v1.0.0'da izoh ko'rinishini tekshiring. git tag -n1bilan barcha teglarni izohlari bilan birga royxat qilib chiqing.- Teg qo'yishni unutgan eski commitga (
HEAD~2) keyinchalik annotated teg qo'ying.git log --onelinebilan teg to'g'ri commitga osilganini tasdiqlang. - Versiyalarni semver bo'yicha belgilang: bitta xato tuzatib
v1.0.1(patch), keyin yangi funksiya qo'shibv1.1.0(minor), keyin katta o'zgarish kiritibv2.0.0(major) teglarini ketma-ket qo'ying. - Lokalda xato qo'yilgan tegni
git tag -d <nom>bilan o'chiring va to'g'ri nom bilan qayta qo'ying. - GitHub'da test repo yarating, uni clone qiling. Bir nechta commit va teg qo'ying.
git push origin --tagsbilan teglarni jo'nating va GitHub'ning Releases bo'limida paydo bo'lganini ko'ring. - 14-mashqdagi teglardan birini GitHub'da to'liq Release'ga aylantiring: "Draft a new release", tegni tanlang, reliz izohi (release notes) yozing va e'lon qiling.
- Ikki branch (
mainvahotfix) yarating.hotfix'da bitta tuzatish commit qiling.main'ga o'tib, o'sha commitnigit cherry-pickbilan ko'chiring.main'da fayl paydo bo'lganini tasdiqlang. - Cherry-pick qilgan commitning hashini asl
hotfix'dagi hash bilan solishtiring β ular boshqacha ekaniga e'tibor bering (nusxa, ko'chirma emas). - Bitta branchda ikkita commit qiling.
main'ga o'tib, ikkalasini birdan cherry-pick qiling (git cherry-pick <h1> <h2>). - Ataylab konflikt yarating: ikki branchda
narx.txtning bir satrini har xil o'zgartiring. Bir branchdagi commitni ikkinchisiga cherry-pick qilib, konflikt markerlarini ko'ring, faylni hal qiling,git add+git cherry-pick --continuebilan yakunlang. - 19-mashqdagi konfliktni qaytadan yarating, lekin bu safar hal qilish o'rniga
git cherry-pick --abortbilan bekor qiling. Repo boshlang'ich holatiga, hech bir o'zgarishsiz qaytganini tasdiqlang.