IlmHamroh
JavaScript Full-stack/32-qism. Professional Git va jamoada ishlash1/19-dars30 daqiqa
Mundarija (44)

Git bisect: xatoni kiritgan commitni ikkiga bo'lib topish

Qisqacha: git bisect — commitlar tarixida binar qidiruv. Siz bitta "sog'" va bitta "buzuq" commitni aytasiz, Git o'rtadagi commitni ochadi va "bu yerda xato bormi?" deb so'raydi. Har javob nomzodlarni ikki baravar kamaytiradi: 1 000 commit ichidan aybdorni 10 qadamda topadi. git bisect run <skript> bu savollarga o'zi javob beradi: chiqish kodi 0 — sog', 1 — buzuq, 125 — "bu commitni sinab bo'lmaydi". Kurs tajribasida vazifalar-api'ning 8 ta commiti ichidan breaker nuqsonini kiritgan commit 3 qadamda topildi.

Bu darsda

  • git bisect start, good, bad, reset bilan aybdor commitni qo'lda topasiz.
  • git bisect run uchun to'g'ri sinov skripti yozasiz va chiqish kodlarini (0, 1, 125, 128+) tushuntirasiz.
  • skip qachon yordam berishini va qachon "We cannot bisect more!" bilan to'xtashini ko'rasiz.
  • --term-old / --term-new, bisect log va replay bilan ishlaysiz.
  • vazifalar-api'dagi 31-qism nuqsonini bisect bilan topib, testi bilan yopasiz.

Oldin bilishingiz kerak: Tarixni o'qish: git log, Kod arxeologiyasi: blame, log -S, log -L, Binary search asoslari, Resilience naqshlari.

32-qism boshlandi. vazifalar-api prodakshnga tayyor, lekin uni bitta odam yozdi — endi u jamoaviy repoga aylanadi. Avval Git chuqur (bisect, reliz, workflow'lar, imzo), keyin jamoa ishi (PR, code review, README, Scrum, open source). Har darsda: muammo → kanon tajribasi → yechim → narxi.

Kursning sinov reposi ochiq va arxivda: github.com/Sarvarbek0704/ilmhamroh-git-sinov — darslardagi PR'lar shu yerda. Buyruqlarda esa o'z repo'ngiz turadi: login/vazifalar-api.

1. Nega bu kerak?

Resilience naqshlari darsida biz bitta nuqson topgan edik. Bitta umumiy circuit breaker ikki tomonlama xato qiladi. Kam yukda o'lik modelni o'chirmaydi. Ko'p yukda esa sog' zaxira modelni ham to'sib qo'yadi: 16 foydalanuvchining hammasi 503 oladi. Nuqson ataylab qoldirilgan edi. Endi uni tuzatish navbati.

Lekin tuzatishdan oldin jamoada albatta bitta savol beriladi: "Bu qachondan beri shunday?" Javob nuqson qaysi relizga tushganini va qaysi o'zgarish uni olib kirganini bildiradi. Aybdor commitning diff'ida sabab ham turadi.

git log da 31-qismning 8 ta commiti bor, bittasining o'zida 25 ta fayl. Birma-bir o'qish — soatlar. git blame ham yordam bermaydi: xato bitta qatorda emas, ikki klass qanday ulanganida.

Hayotiy o'xshatish: archa chirog'idagi 100 ta lampochka ketma-ket ulangan, bittasi kuygan. Zanjirni o'rtasidan tekshirasiz: tok birinchi yarmida bor bo'lsa, kuygani ikkinchi yarmida. Keyin o'sha yarmini yana ikkiga bo'lasiz. 7 qadamda kuygan lampochka qo'lingizda.

git bisect aynan shunday ishlaydi, faqat lampochka o'rnida — commitlar.

2. Binar qidiruv — commitlar ustida

2.1 G'oya

Bisect (bisect — "ikkiga bo'lish") — Git buyrug'i: sog' va buzuq commit orasidagi tarixni har qadamda ikkiga bo'lib, xatoni birinchi bo'lib kiritgan commitni topadi. Unga ikki narsa kerak:

  • sog' (good) commit — xato hali yo'q edi (masalan, o'tgan oydagi reliz);
  • buzuq (bad) commit — xato bor (odatda hozirgi HEAD).

Git o'rtadagi commitni ochadi. Siz uni sinab, "sog'" yoki "buzuq" deysiz. Javobga qarab Git yarmini tashlaydi va qolganining o'rtasini ochadi.

gitGraph
  commit id: "v1.0.0 sog'"
  commit id: "2"
  commit id: "3"
  commit id: "4"
  commit id: "5: sog'"
  commit id: "6"
  commit id: "7: buzuq"
  commit id: "8"
  commit id: "HEAD buzuq"

Diagrammaga qarang. Git avval o'rtadagi 5-commitni ochadi — u sog'. Demak, aybdor 6–HEAD oralig'ida. Keyin 7-commit — buzuq: aybdor 6 yoki 7. Oxirgi savol 6-commit haqida. U sog' bo'lsa — aybdor 7. Sakkizta nomzoddan uch savolda.

Bitta shart bor: xato bir marta paydo bo'lib, keyin yo'qolmagan bo'lishi kerak — sog'lar chapda, buzuqlar o'ngda. Aks holda binar qidiruv noto'g'ri javob berishi mumkin.

2.2 Necha qadam?

Har qadam nomzodlarni ikki baravar kamaytiradi. Demak, n ta commit uchun taxminan log₂ n qadam kerak. Binary search asoslari darsidagi formulaning o'zi:

js
function qadamlarSoni(commitlar) {
  return Math.ceil(Math.log2(commitlar));
}

for (const n of [8, 15, 200, 1000, 100000]) {
  console.log(`${n} commit → ${qadamlarSoni(n)} qadam`);
}

Konsolda:

text
8 commit → 3 qadam
15 commit → 4 qadam
200 commit → 8 qadam
1000 commit → 10 qadam
100000 commit → 17 qadam

Debugging metodologiyasi darsida "200 commit ichidan taxminan 8 qadamda" degan edik — mana o'sha hisob. Yuz ming commitli tarixda ham 17 ta tekshiruv yetadi. Qadamlar soni tarixning uzunligiga juda sekin bog'liq.

Tekshirib ko'ring: Bitta tekshiruv (build + test) 3 daqiqa oladi. 500 commit ichidan aybdorni qo'lda birma-bir qidirish va bisect bilan qidirish qancha vaqt oladi?

Javob

Birma-bir: eng yomon holatda 500 × 3 = 1 500 daqiqa, ya'ni 25 soat. Bisect: log₂ 500 = 9 qadam, 9 × 3 = 27 daqiqa. Farq 50 baravardan ko'p — va tarix uzaygan sari o'sadi.

3. Qo'lda bisect

3.1 Tayyorgarlik: kichik repo

Avval bisect'ni kichik misolda ko'ramiz. «Bahor» choyxonasining buyurtma hisobi: hisob.js dagi jami() funksiyasi savat summasini hisoblaydi. 100 000 so'mdan boshlab 10% chegirma beriladi. test.js ikki holatni tekshiradi:

js
import assert from "node:assert/strict";
import { jami } from "./hisob.js";

assert.equal(jami([{ narx: 38000, soni: 2 }]), 76000);
assert.equal(jami([{ narx: 50000, soni: 2 }]), 90000);
console.log("test: ok");

Ikkinchi qatorga e'tibor bering: savat roppa-rosa 100 000 so'm. Chegirma "100 000 dan boshlab" bo'lgani uchun u ham chegirma olishi kerak — 90 000.

Repo'ni kurs/mashqlar/32/01-bisect/bahor-hisob.sh skripti yaratadi: bash bahor-hisob.sh hisob, keyin cd hisob. Ichida 16 ta commit bor, muallif va sanalar qotirilgan — SHA'lar sizda ham darsdagidek chiqadi. Tarixga qaraymiz:

bash
git log --oneline

Terminalda:

text
a209877 (HEAD -> main) docs: to'lov usullari
ad4ff60 feat: choy narxi yangilandi
e60eea9 docs: bron qoidasi
a661c3f feat: osh narxi yangilandi
8fefd8f docs: manzil
5d77071 refactor(hisob): chegarani konstantaga chiqar
f0faa27 feat: somsa qo'shildi
412f9a3 docs: yetkazib berish vaqti
20b94d8 fix(test): import yo'lini tuzat
0cc9fad refactor: hisob.js ni src/ ga ko'chir
f72fd87 docs: telefon raqami
4aabc85 feat: choy qo'shildi
94e284c feat: manti qo'shildi
61700b7 feat: menyu narxlari
0b406af docs: ish vaqtini yoz
37d8fbd (tag: v1.0.0) feat: buyurtma jami va 10% chegirma

Hozir test yiqiladi:

bash
node test.js

Terminalda (Node izining boshi qisqartirildi):

text
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

100000 !== 90000

100 000 so'mlik savat chegirma olmayapti. v1.0.0 da esa test o'tardi. Orada 15 ta commit bor.

Commit xabarlari sizga Conventional Commits darsidan tanish: feat, fix, docs, refactor. Ko'zingiz ehtimol refactor(hisob) ga tushdi. Lekin taxmin — dalil emas. Haqiqiy loyihada 15 emas, 300 commit bo'ladi va xabarlar bunchalik aniq bo'lmaydi.

3.2 Boshlash: start, bad, good

bash
git bisect start
git bisect bad
git bisect good v1.0.0

Terminalda:

text
status: waiting for both good and bad commits
status: waiting for good commit(s), bad commit known
Bisecting: 7 revisions left to test after this (roughly 3 steps)
[20b94d84cdd0039e6b038d33d8bcc7de0912c47d] fix(test): import yo'lini tuzat

Qatorma-qator:

  • git bisect start — qidiruv boshlandi, Git ikkala chegarani kutyapti.
  • git bisect bad — argumentsiz: joriy commit (HEAD) buzuq.
  • git bisect good v1.0.0 — tag yoki SHA. Git darhol o'rtani ochdi: "yana 7 ta commit, taxminan 3 qadam".

Uchta buyruqni bittasiga qisqartirish mumkin: git bisect start HEAD v1.0.0 — avval buzuq, keyin sog'.

Endi siz detached HEAD holatidasiz — branch emas, aniq commit ochiq. git status buni aytadi:

text
HEAD detached at 20b94d8
You are currently bisecting, started from branch 'main'.
  (use "git bisect reset" to get back to the original branch)

nothing to commit, working tree clean

"Siz hozir bisect qilyapsiz, main branch'idan boshlagansiz. Qaytish uchun git bisect reset".

3.3 Bitta qadam

Git ochgan commitda testni ishga tushiramiz:

bash
node test.js

Terminalda:

text
test: ok

Bu commitda xato yo'q. Git'ga aytamiz:

bash
git bisect good

Git keyingi o'rtani ochadi, siz yana sinaysiz — to Git ... is the first bad commit deguncha. Sinov avtomatlashmagan bo'lsa ("brauzerda tugmani bosib ko'raman") qo'lda bisect qulay. Test bor bo'lsa — savollarga Git'ning o'zi javob beradi.

3.4 Tugatish: reset

Qidiruv tugagach (yoki uni to'xtatmoqchi bo'lsangiz):

bash
git bisect reset

Terminalda:

text
Previous HEAD position was 20b94d8 fix(test): import yo'lini tuzat
Switched to branch 'main'

Git sizni qidiruv boshlangan branch'ga qaytardi. reset ni unutmang: aks holda keyingi commit'ingiz detached HEAD'da qolib ketadi.

Tekshirib ko'ring: git bisect start HEAD v1.0.0 buyrug'ida argumentlar tartibi teskari bo'lsa (v1.0.0 HEAD), nima bo'ladi?

Javob

Git v1.0.0 ni buzuq, HEAD ni sog' deb tushunadi. Lekin sog' commit buzuqning ajdodi bo'lishi kerak, bu yerda esa teskari. Biz sinab ko'rdik — Git qidiruvni boshlamadi:

text
Some good revs are not ancestors of the bad rev.
git bisect cannot work properly in this case.
Maybe you mistook good and bad revs?

"Ba'zi sog' commitlar buzuqning ajdodi emas. Bu holda bisect to'g'ri ishlay olmaydi. Sog' va buzuqni adashtirmadingizmi?" Qoida: start dan keyin avval buzuq, keyin sog'.

4. git bisect run — avtomatik qidiruv

4.1 Chiqish kodlari

git bisect run <buyruq> har qadamda buyruqni ishga tushiradi va uning chiqish kodiga (exit code) qarab javob beradi. Chiqish kodini terminal darslaridan bilasiz: 0 — muvaffaqiyat, boshqa son — xato. Bisect uchun qoida shunday (git-scm.com hujjati, kanonda sinalgan):

Chiqish kodi Bisect nima deb tushunadi
0 sog' (good)
1–127, 125 dan tashqari buzuq (bad)
125 bu commitni sinab bo'lmaydi — o'tkazib yubor (skip)
128 va undan katta qidiruvni to'xtat (abort)

node test.js bu qoidaga tayyor mos keladi: test o'tsa — 0, assert yiqilsa — 1. Shuning uchun eng qisqa yo'l:

bash
git bisect start HEAD v1.0.0
git bisect run node test.js

Lekin bu tarixda bitta tuzoq bor. 0cc9fad commitida hisob.js src/ papkasiga ko'chgan, test.js esa eski yo'lni import qiladi. Bu commitda test umuman ishlamaydi: ERR_MODULE_NOT_FOUND, chiqish kodi 1. Bisect buni "buzuq" deb tushunadi. Ammo bu bizning xatomiz emas — boshqa sabab! Shunday commit qidiruv yo'liga tushsa, bisect noto'g'ri commitni aybdor qiladi. Buni 3-mashqda o'z ko'zingiz bilan ko'rasiz.

4.2 To'g'ri sinov skripti

Yechim — kichik skript. U uch holatni ajratadi: test o'tdi, test yiqildi, test ishlamadi. Skriptni repo tashqarisiga qo'yamiz. Bisect har qadamda boshqa commitni ochadi, repo ichidagi fayl esa commit bilan birga o'zgarib ketishi mumkin.

bash
#!/bin/bash
# sinov.sh — git bisect run uchun:
#   0 — yaxshi, 1 — yomon, 125 — sinab bo'lmaydi
chiqish=$(node test.js 2>&1)
k=$?
if [ $k -ne 0 ] && echo "$chiqish" | grep -q ERR_MODULE_NOT_FOUND
then
  echo "test ishlamadi (import topilmadi) — skip"
  exit 125
fi
echo "$chiqish" | grep -E "^(test: ok|AssertionError)" | head -2
exit $k

Test yiqilgan va sabab — topilmagan modul bo'lsa, skript 125 qaytaradi. Aks holda testning o'z kodini (0 yoki 1) uzatadi. grep qatori faqat chiqishni qisqa ko'rsatadi. Skript mashq papkasida ham bor (sinov.sh).

4.3 Ishga tushiramiz

bash
git bisect start HEAD v1.0.0
git bisect run bash ../sinov.sh

Har qadamda uchta qatorga qarang: qaysi commit ochildi, skript nima dedi, nechta nomzod qoldi.

text
Bisecting: 7 revisions left to test after this (roughly 3 steps)
[20b94d84cdd0039e6b038d33d8bcc7de0912c47d] fix(test): import yo'lini tuzat
running 'bash' '../sinov.sh'
test: ok
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[8fefd8f1f31ff267172c0641a097c68ee84e5808] docs: manzil
running 'bash' '../sinov.sh'
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
Bisecting: 1 revision left to test after this (roughly 1 step)
[f0faa270445ced68da8d227abd60a70284f2cdeb] feat: somsa qo'shildi
running 'bash' '../sinov.sh'
test: ok
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[5d77071d5a128c0c4d9e4d6b2aea76f44c1333fb] refactor(hisob): chegarani konstantaga chiqar
running 'bash' '../sinov.sh'
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
5d77071d5a128c0c4d9e4d6b2aea76f44c1333fb is the first bad commit
commit 5d77071d5a128c0c4d9e4d6b2aea76f44c1333fb (HEAD)
Author: Aziz Karimov <aziz@example.com>
Date:   Thu Oct 1 19:00:00 2026 +0500

    refactor(hisob): chegarani konstantaga chiqar

 src/hisob.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
bisect found first bad commit

15 nomzoddan 4 qadam — log₂ 15 = 4, formula bilan aynan. Git aybdorni topdi va uning diff statistikasini ham ko'rsatdi. Endi farqni ochamiz:

bash
git show 5d77071 -- src/hisob.js

Terminalda (commit sarlavhasidan keyingi qism):

text
@@ -1,6 +1,7 @@
-// hisob.js — buyurtma jami: 100 000 so'mdan boshlab 10% chegirma
+// hisob.js — buyurtma jami: CHEGARA so'mdan boshlab 10% chegirma
+const CHEGARA = 100000;
 export function jami(savat) {
   let summa = 0;
   for (const t of savat) summa += t.narx * t.soni;
-  return summa >= 100000 ? Math.round(summa * 0.9) : summa;
+  return summa > CHEGARA ? Math.round(summa * 0.9) : summa;
 }

Mana sabab. "Faqat konstantaga chiqardim" degan refactor jimgina >= ni > ga almashtirgan. 100 000 so'mlik savat chegirmadan chiqib qoldi. Commit xabari "refactor" deydi — xulq o'zgarmasligi kerak edi. Bisect xabarga emas, xulqqa qaraydi.

Qidiruv yo'lida buzilgan 0cc9fad commiti uchramadi, shuning uchun 125 bu safar kerak bo'lmadi. Boshqa chegaralar bilan u uchraydi — 3-mashq aynan shu haqda.

Bu bo'limdagi chiqishlar haqiqiy (Git 2.54.0, Node 24.21.0). Git versiyangiz boshqa bo'lsa, xabar matnlari biroz farq qilishi mumkin.

4.4 bisect log va replay

Qidiruv davomida Git har javobni yozib boradi. Bu jurnalni reset dan oldin saqlab qo'yish mumkin:

bash
git bisect log

Terminalda (boshi; keyingi ikki javob xuddi shu shaklda):

text
# bad: [a209877f2b30f78ecef90184afbbb475a964b124] docs: to'lov usullari
# good: [37d8fbda50c74bba80f76ef1029d69b7bef61344] feat: buyurtma jami va 10% chegirma
git bisect start 'HEAD' 'v1.0.0'
# good: [20b94d84cdd0039e6b038d33d8bcc7de0912c47d] fix(test): import yo'lini tuzat
git bisect good 20b94d84cdd0039e6b038d33d8bcc7de0912c47d
# bad: [8fefd8f1f31ff267172c0641a097c68ee84e5808] docs: manzil
git bisect bad 8fefd8f1f31ff267172c0641a097c68ee84e5808
...
# first bad commit: [5d77071d5a128c0c4d9e4d6b2aea76f44c1333fb] refactor(hisob): chegarani konstantaga chiqar

Jurnal — oddiy bash buyruqlari va # izohlar. U ikki joyda kerak bo'ladi:

  • PR yoki issue'ga isbot — hamkasb qidiruvingizni takrorlay oladi.
  • Qo'lda adashganda. Bitta noto'g'ri "good" butun qidiruvni buzadi. Jurnalni faylga saqlab, noto'g'ri qatorni o'chirasiz va git bisect replay jurnal.txt — Git to'g'ri javoblarni qayta bajaradi. Biz sinadik, ikki javobli jurnal:
text
Bisecting: 7 revisions left to test after this (roughly 3 steps)
[20b94d84cdd0039e6b038d33d8bcc7de0912c47d] fix(test): import yo'lini tuzat
Bisecting: 1 revision left to test after this (roughly 1 step)
[f0faa270445ced68da8d227abd60a70284f2cdeb] feat: somsa qo'shildi

4.5 Boshqa so'zlar: --term-old va --term-new

"good" va "bad" regressiya uchun qulay. Lekin "sahifa qachon tezlashdi?" degan savolda yangi holat yaxshiroq — "bad" chalkashtiradi. Shuning uchun o'z so'zlaringizni berish mumkin:

bash
git bisect start --term-old=sog --term-new=buzuq
git bisect buzuq v4.0.0
git bisect sog v3.0.0
git bisect terms

git bisect terms oxirgi qatorda tanlangan so'zlarni eslatadi:

text
Your current terms are sog for the old state
and buzuq for the new state.

"Eski holat uchun so'zingiz — sog', yangi holat uchun — buzuq." Endi git bisect sog va git bisect buzuq ishlaydi. Git'da tayyor juftlik ham bor: old / new. Kanon tajribasi sog / buzuq bilan yurdi.

4.6 Qidiruvni toraytirish

  • Yo'l bo'yicha. git bisect start HEAD v1.0.0 -- src — faqat src/ ga tekkan commitlar nomzod. Bizning repoda ular ikkita, Git darhol 0 revisions left dedi. Lekin xato boshqa papkadan kelgan bo'lsa (masalan, package.json), uni o'tkazib yuborasiz.
  • Faqat asosiy chiziq. --first-parent — merge'li tarixda faqat main dagi commitlar bo'ylab yuradi, PR ichiga kirmaydi (git-scm.com hujjati; kanon tarixi chiziqli edi — sinalmagan).
  • git bisect view --oneline — hali qolgan nomzodlar ro'yxati.

5. skip va uning chegarasi

Ba'zan commitni sinab bo'lmaydi: build buzilgan, commit "yarim ish". Qo'lda bisect'da git bisect skip yozasiz va Git yonidagi boshqa commitni ochadi. Kanon tarixida skip'dan keyin c39a485 o'rniga b120fec ochildi (xabar qisqartirildi):

text
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[b120fec30dafb8c7434c02300071982e2e4097d4] feat: saqlash muddati…

git bisect run da shu ishni chiqish kodi 125 bajaradi. Lekin skip bepul emas: o'tkazilgan commit haqida Git hech narsa bilmaydi. Aybdor skip'lar orasida bo'lsa, Git sizga bir nechta nomzod beradi. Buni keyingi bo'limda haqiqiy tarixda ko'ramiz.

6. Kanon tajribasi: vazifalar-api'dagi breaker nuqsoni

6.1 Tarix

Endi haqiqiy loyiha. Kanon vazifalar-api tarixini qayta qurdi: 30-qism oxiri (v3.0.0) va 31-qismning sakkizta bosqichi, har biri bitta commit, xabarlar — darslardagidek. Siz ham shu commitlarni qilgansiz, demak qidiruvni o'z tarixingizda takrorlay olasiz.

text
489d9c9 (HEAD -> main, tag: v4.0.0) test: asinxron (soxta…
93a6b16 feat: graceful drain (readiness 503 + kechikish),…
89abe02 feat: audit jurnali (faqat qo'shiladi, hash zanjir…
ddeead9 feat(ai): kunlik kvota va javob keshi (foydalanuvc…
c39a485 feat(ai): semantik qidiruv (pgvector HNSW, gibrid…
34346db feat(ai): vazifani qadamlarga bo'lish — LLM provay…
b120fec feat: saqlash muddati va hisobni o'chirish — tozal…
36b8d8f feat(db): vazifa hodisalari — oyma-oy bo'limli vaq…
13eff7f (tag: v3.0.0) feat(api): 29-qism integratsiyalari…

Qatorlar «…» bilan kesilgan. Sizning tarixingizda tag bo'lmasligi mumkin — v3.0.0 o'rniga 30-qismning oxirgi commit SHA'sini yozing.

6.2 Nega npm test emas?

Test arxitekturasi va CI darsida git bisect run npm test ni eslatgan edik. Bu loyihada u yomon tanlov, uch sababi bor:

  • Sekin. 339 ta test, Postgres, Redis, MinIO, Mailpit; bitta yugurish ~2 daqiqa.
  • Beqaror. auth.test dagi vaqtga bog'liq test kanon yakuniy tekshiruvida bir yiqilib, bir o'tdi. Beqaror (flaky) test bitta tasodifiy "buzuq" bilan boshqa commitni aybdor qiladi.
  • Keng. Har commit o'z testlarini yuritadi — eski commitda bugungi nuqsonni tekshiradigan test yo'q.

Bisect uchun bitta savolga javob beradigan, tez, barqaror va eski commitlarda ham ishlaydigan test kerak — regressiya testi: bir marta topilgan xato qaytib kelmasligini kuzatadigan test.

6.3 Regressiya testi — qora quti

Kanon testi kodning ichiga tayanmaydi: config beradi, provayderYarat() ni chaqiradi va javoblarni sanaydi. Tarmoq o'rniga soxta fetch: asosiy model doim 503, zaxira sog'. Bunday testni qora quti (black box) testi deyishadi — faqat kirish va chiqishga qaraymiz. Ichki klass nomlariga tayangan test eski commitlarda kompilyatsiya bo'lmasdi; qora quti testi esa zaxira bor har commitda bir xil ishlaydi.

Faylning ikki asosiy qismi — soxta tarmoq va parallel stsenariy (ikkinchisi describe ichida turadi; to'liq fayl, ~100 qator, mashq papkasida: breaker-regressiya.test.ts):

ts
// tarmoq o'rniga soxta fetch: asosiy model doim 503, zaxira sog'
beforeEach(() => {
  chaqiruv = { asosiy: 0, zaxira: 0 };
  globalThis.fetch = async (kirish: string | URL | Request) => {
    const url = typeof kirish === "string" ? kirish
      : kirish instanceof URL ? kirish.href : kirish.url;
    // tarmoq kechikishi: parallel so'rovlar ustma-ust tushsin
    await new Promise((ok) => setTimeout(ok, 5));
    if (url.includes(`models/${ASOSIY}:`)) {
      chaqiruv.asosiy++;
      return new Response(JSON.stringify(OLIK),
        { status: 503, headers: json });
    }
    if (url.includes(`models/${ZAXIRA}:`)) {
      chaqiruv.zaxira++;
      return new Response(GEMINI_JAVOB,
        { status: 200, headers: json });
    }
    return new Response("{}", { status: 404 });
  };
});

  // 2-stsenariy: 8 tadan parallel, ikki aylana — hammasi javob olsin
  test.runIf(ikkinchi)("8 × 2: zaxira to'silmaydi", async () => {
    const p = provayder();
    const natija: Record<string, number> = {};
    for (let aylana = 0; aylana < 2; aylana++) {
      const r = await Promise.allSettled(
        Array.from({ length: 8 }, () => p.yarat(sorov)));
      for (const x of r) {
        const k = x.status === "fulfilled" ? "ok"
          : (x.reason as { tur?: string }).tur ?? "xato";
        natija[k] = (natija[k] ?? 0) + 1;
      }
    }
    console.log(
      `8×2: javob ${matn(natija)}, chaqiruv ${matn(chaqiruv)}`);
    assert.equal(natija["ok"], 16,
      `zaxira to'sildi: ${matn(natija)}`);
  }, 30_000);

Testda ikki stsenariy bor — nuqsonning ikki yuzi:

  • 1 × 10 — so'rovlar ketma-ket. O'lik asosiy modelga ko'pi bilan 5 chaqiruv ketishi kerak (breaker chegarasi), keyin breaker ochiladi.
  • 8 × 2 — 8 tadan parallel, ikki aylana. 16 so'rovning hammasi javob olishi kerak — sog' zaxira to'silmasin.

BISECT_STSENARIY bittasini tanlaydi: bisect bitta savol bilan ishlaydi.

Kanon fayli telefon uchun formatlandi: uzun qatorlar bo'lindi, takrorlanadigan ifodalar (OLIK, matn) nomga olindi, test nomlari va bosh izoh qisqardi — mantiq aynan. Bu nusxani biz loyihaning shu bosqichi nusxasida tsc --noEmit, ESLint va Vitest bilan tekshirdik: 2 ta test o'tdi, chiqish kanon bilan bir xil.

6.4 Sinov skripti

Test fayli ham, skript ham repo tashqarisida turadi. Har qadamda skript testni test/ ga nusxalaydi, ishga tushiradi va o'chiradi — ishchi daraxt toza qolishi kerak.

bash
#!/bin/bash
# bisect-sinov.sh — git bisect run uchun sinov (repo TASHQARISIDA)
#   0 — sog', 1 — buzuq, 125 — bu commitni sinab bo'lmaydi (skip)
TEST=../breaker-regressiya.test.ts
NUSXA=test/zz-breaker-regressiya.test.ts
QISQA=$(git rev-parse --short HEAD)

# zaxira model hali yo'q commit — savol ma'nosiz
if [ ! -f src/ai/zaxira.ts ]; then
  echo "[$QISQA] zaxira.ts yo'q — skip (125)"
  exit 125
fi

cp "$TEST" "$NUSXA"
npx vitest run "$NUSXA" --project asosiy > ../bisect.txt 2>&1
k=$?
rm -f "$NUSXA"   # ishchi daraxt toza qolsin

echo "[$QISQA] $(grep -E '^(1×10|8×2):' ../bisect.txt) → exit $k"
[ $k -eq 0 ] && exit 0
grep -qE "Tests .*failed" ../bisect.txt && exit 1
echo "kutilmagan xato (test ishlamadi) — skip"
exit 125

Skript ham mashq papkasida (bisect-sinov.sh). Uchta qaror skriptga yozilgan:

  1. Zaxira model yo'q commit — 125. "Sog' zaxira to'silyaptimi?" degan savol zaxira yo'q joyda ma'nosiz.
  2. Test yiqildi — 1. Faqat Vitest "Tests … failed" desa. Bu — haqiqiy javob.
  3. Boshqa har qanday xato — 125. Import topilmadi, kompilyatsiya buzildi — bu "buzuq" emas, "bilmayman".

Uchinchi qaror kanonda o'zini oqladi. Birinchi yugurishda node_modules ulanmagan edi va hamma commitda ERR_MODULE_NOT_FOUND chiqdi. Skript hammasini skip qildi, bisect "We cannot bisect more!" bilan to'xtadi. 1 qaytarganida edi, bisect ishonch bilan noto'g'ri commitni ko'rsatardi. Noaniq javob — noto'g'ri javobdan yaxshi. (Kanonning ichki skripti uzunroq; bu — uning qisqa varianti, mantiq bir xil.)

6.5 Natija: 8 × 2 stsenariysi

bash
git bisect start --term-old=sog --term-new=buzuq
git bisect buzuq v4.0.0
git bisect sog v3.0.0
BISECT_STSENARIY=2 git bisect run bash ../bisect-sinov.sh

Biz kanon tarixining klonida o'quvchi skripti bilan ishga tushirdik (Git 2.54.0, Node 24.21.0). Uzun commit xabarlari «…» bilan qisqartirildi, git show --stat qismidan faqat uch qator qoldi (...):

text
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[c39a48564986babec36e405fce6580c05f2ee9c6] feat(ai): semantik qidiruv…
running 'bash' '../bisect-sinov.sh'
[c39a485] 8×2: javob {"ok":16}, chaqiruv {"asosiy":16,"zaxira":16} → exit 0
Bisecting: 1 revision left to test after this (roughly 1 step)
[89abe029da3542fd9738493313c22963d504b46d] feat: audit jurnali…
running 'bash' '../bisect-sinov.sh'
[89abe02] 8×2: javob {"ok":16}, chaqiruv {"asosiy":16,"zaxira":16} → exit 0
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[93a6b161ea1a8c8bb043b0219016aaf8e74a2a87] feat: graceful drain…
running 'bash' '../bisect-sinov.sh'
[93a6b16] 8×2: javob {"ochiq":16}, chaqiruv {"asosiy":12,"zaxira":0} → exit 1
93a6b161ea1a8c8bb043b0219016aaf8e74a2a87 is the first buzuq commit
...
 src/ai/ai.module.ts                 |   8 ++-
 src/ai/chidamli.ts                  | 108 ++++++++++++++++++++++++++++++++++++
...
 25 files changed, 448 insertions(+), 23 deletions(-)
bisect found first bad commit
--- vaqt: 27 s

Uch qadam, 27 soniya (kanon yugurishida — 11 soniya). Aybdor — 93a6b16, ya'ni Resilience naqshlari darsidagi commit: "AI chidamliligi (retry, circuit breaker, bulkhead)". 25 ta fayl ichida yangi src/ai/chidamli.ts (108 qator) — umumiy breaker aynan shu yerda tug'ilgan. Raqamlar ham 31-qism darsidagi bilan aynan: 16 ta ochiq, zaxiraga 0 chaqiruv.

E'tibor bering: oxirgi qator "first bad commit" deydi, garchi biz "buzuq" so'zini berganmiz. Bu Git'ning umumiy xabari — terms faqat buyruq nomlarini o'zgartiradi.

6.6 Boshqa savol — boshqa javob: 1 × 10

Endi xuddi shu tarixda birinchi stsenariy bilan qidiramiz: "o'lik asosiy model breaker'ni ochadimi?"

bash
BISECT_STSENARIY=1 git bisect run bash ../bisect-sinov.sh

Biz ishga tushirdik (commit xabarlari kesildi; ... — xuddi shunday Bisecting va running qatorlari):

text
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[c39a48564986babec36e405fce6580c05f2ee9c6] feat(ai): semantik qidiruv…
running 'bash' '../bisect-sinov.sh'
[c39a485] 1×10: ok 10, chaqiruv {"asosiy":10,"zaxira":10} → exit 1
Bisecting: 1 revision left to test after this (roughly 1 step)
[b120fec30dafb8c7434c02300071982e2e4097d4] feat: saqlash muddati…
running 'bash' '../bisect-sinov.sh'
[b120fec] zaxira.ts yo'q — skip (125)
...
[34346db] zaxira.ts yo'q — skip (125)
...
[36b8d8f] zaxira.ts yo'q — skip (125)
There are only 'skip'ped commits left to test.
The first buzuq commit could be any of:
b120fec30dafb8c7434c02300071982e2e4097d4
34346db7242d43807b66259cbc962331697ce513
36b8d8ff54e0a735bde1f898faef0a430a962bff
c39a48564986babec36e405fce6580c05f2ee9c6
We cannot bisect more!
error: bisect run cannot continue any more

Butunlay boshqa manzara. Zaxira model paydo bo'lgan c39a485 commitida test allaqachon yiqiladi: o'lik model 10 marta chaqirilgan. Undan oldingi uchta commitda zaxira yo'q — skip. Git to'rtta nomzod qoldirdi va to'xtadi: "Faqat skip qilingan commitlar qoldi. Birinchi buzuq commit ulardan istalgani bo'lishi mumkin. Boshqa ikkiga bo'la olmaymiz!"

Bu xato emas — bu ma'lumot. Har commit uchun ikkala stsenariy jadvali (kanon o'lchovi):

Commit 1 × 10: asosiyga chaqiruv 8 × 2: javob
c39a485, ddeead9, 89abe02 (zaxira bor, breaker yo'q) 10 — yiqiladi ok 16 — o'tadi
93a6b16, 489d9c9 (umumiy breaker) 30 — yiqiladi ochiq 16 — yiqiladi
tuzatilgan kod (har modelga breaker) 5 — o'tadi ok 16 — o'tadi

Birinchi stsenariy hech qachon o'tmagan. Breaker bo'lmagan paytda ham o'lik model har so'rovda chaqirilgan (10 marta). Umumiy breaker qo'shilgach — 30 marta. Demak, bu regressiya emas, "hech qachon ishlamagan" xulq. Bisect uni topa olmaydi, chunki tarixda sog' nuqta yo'q.

Ikkinchi stsenariy esa haqiqiy regressiya: 89abe02 gacha 16/16 javob bor edi, 93a6b16 da 0 ga tushdi.

Asosiy saboq: test nimani o'lchasa, bisect o'shani topadi. Bir xil tarix, ikki xil savol — ikki xil javob. Bisect'dan oldin aniq bitta savolni tuzing: "nima o'zgardi?", emas — "qaysi xulq qachon buzildi?".

Tekshirib ko'ring: 1 × 10 stsenariysida skip'larni olib tashlab, zaxira yo'q commitda skript exit 0 qaytarsa ("zaxira yo'q — demak muammo ham yo'q"), bisect nimani topardi? Bu javob to'g'rimi?

Javob

Bisect c39a485 ni "first buzuq commit" deb ko'rsatardi: undan oldingi commitlar "sog'", u "buzuq". Javob ishonchli ko'rinadi, lekin noto'g'ri talqin qilinadi. c39a485 nuqsonni kiritmagan — u zaxira modelni qo'shgan, savol shundan keyingina ma'noga ega bo'lgan. "Savol ma'nosiz" holatini "sog'" deb aytish — bisect'ni aldash. To'g'ri javob — 125.

7. Loyiha qadami: nuqsonlarni yopish va commit

7.1 Breaker — har modelga alohida

Aybdor topildi, sabab ma'lum: breaker provayderga bitta edi, modelga emas. Tuzatish — src/ai/chidamli.ts da breaker holati alohida Breaker klassiga chiqdi va modellar bo'yicha Map da saqlanadi. Asosiy qism (kanon kodidan parcha, telefon uchun bo'lingan):

ts
  readonly #breakerlar = new Map<string, Breaker>();

  #breaker(model: string | undefined): Breaker {
    const k = model ?? this.model;
    let b = this.#breakerlar.get(k);
    if (b === undefined) {
      b = new Breaker(this.#s);
      this.#breakerlar.set(k, b);
    }
    return b;
  }

Kalit — so'rovdagi model nomi, berilmasa provayder sukuti. Endi zaxiraning muvaffaqiyati asosiy model hisoblagichini nolga qaytarmaydi, asosiy breaker ochilganda esa zaxira o'z eshigidan o'tadi. zaxira.ts da ham bitta o'zgarish: asosiy breaker ochiq bo'lsa ham zaxiraga o'tiladi. Bulkhead umumiy qoldi — u modelni emas, ilova resursini himoya qiladi.

Natija — jadvalning oxirgi qatori. Regressiya testi test/breaker-regressiya.test.ts sifatida loyihada qoladi: kimdir breaker'ni yana umumiy qilsa, CI darhol qizaradi.

7.2 Qolgan nuqsonlar

31-qism oxirida yana oltita ochiq xato qolgan edi. Ular ham shu commitda, har biri testi bilan yopildi:

Nuqson Tuzatish
AI so'rovida umumiy muddat yo'q (eng yomon holat 121.8 s) AI_DEADLINE_MS (25 000): retry va zaxira bitta muddatda, tugasa 504
DEFAULT bo'limdagi qator yangi bo'limni to'sadi, xato yutiladi qatorlar bitta tranzaksiyada ko'chiriladi; xato — log va metrika
gemini-embedding-2 ga taskType yuboriladi matn prefiksi; sifat ta'siri qayta o'lchanmagan
Uzilgan oqim kvotani qaytaradi faqat provayder hech narsa bermay yiqilsa qaytadi
Audit api_kalit.yaratildi da obyekt_id bo'sh javobdagi id yoziladi
MCP vositasida readOnlyHint noto'g'ri readOnlyHint: false — vosita kvota sarflaydi

Embedding qatoriga e'tibor bering: qidiruv sifatiga ta'siri o'lchanmagan — bu qismda AI API'ga so'rov yuborilmadi. Buni ochiq aytish ham PR tavsifining bir qismi. Yana bir topilma: 31-qismning audit testi nuqsonni kutgan edi — obyekt_id: null ni to'g'ri deb tekshirardi. Test noto'g'ri xulqni "qotirib" qo'ygan; kutuv String(kalit.id) ga o'zgardi.

7.3 Tekshiruv

Kanon bosqich tekshiruvi (npm run check), kanon chiqishidan — Vitest qismidan faqat regressiya testi qatorlari va yakun qoldirildi:

text
--- npm run tip
exit 0 (11940 ms)
--- npm run lint
exit 0 (33340 ms)
--- npm run test
1×10: ok 10, chaqiruv {"asosiy":5,"zaxira":10}
8×2: javob {"ok":16}, chaqiruv {"asosiy":12,"zaxira":16}
 Test Files  69 passed | 1 skipped (70)
      Tests  346 passed | 1 skipped (347)
exit 0 (120210 ms)
--- npm run build
exit 0 (14138 ms)

Testlar 339 dan 346 ga oshdi: 2 ta regressiya va 5 ta nuqsonlar testi. Lint birinchi urinishda 9 ta xato bergan edi — tuzatildi.

7.4 Commit

bash
git add -A
git commit

Muharrirda xabar (sarlavha va tanasi):

text
fix(ai): har modelga alohida circuit breaker va umumiy deadline; 31-qism nuqsonlari

- breaker: umumiy edi — o'lik asosiy model ochilmasdi, parallel yukda sog' zaxira to'silardi
- AI_DEADLINE_MS (25 s): retry + zaxira bitta muddat ichida
- DEFAULT bo'limdagi qatorlar yangi bo'limga ko'chiriladi; fon xatosi — log + metrika
- gemini-embedding-2: taskType o'rniga prefiks; uzilgan oqim kvotani qaytarmaydi
- audit: api_kalit.yaratildi da obyekt_id; MCP readOnlyHint to'g'rilandi

Sarlavhadagi fix keyingi darsda muhim: reliz vositalari versiyani shu so'zdan hisoblaydi.

Kanon bu commitni sinov reposida PR #9 orqali main ga qo'shdi, tavsifning "Nega" bo'limida — bisect natijasi: github.com/Sarvarbek0704/ilmhamroh-git-sinov/pull/9. PR oqimini Jamoa workflow'lari darsida ko'ramiz.

8. Ko'p uchraydigan xatolar

8.1 reset ni unutish

Bisect tugadi, siz kod yozib commit qildingiz — commit detached HEAD'da qoldi. Belgisi: git status da "You are currently bisecting". Davosi: git bisect reset, keyin commitni reflog va git cherry-pick bilan qaytaring.

8.2 start siz buyruq

text
You need to start by "git bisect start"

"Avval git bisect start qilishingiz kerak." git bisect good yoki bad qidiruv boshlanmagan holda yozildi. Chiqish kodi 1 — skriptda bu buyruq jim o'tib ketmaydi.

8.3 Skript topilmadi: 127

git bisect run ./yoq.sh — fayl yo'q yoki yo'l noto'g'ri. Shell 127 qaytaradi, bu 1–127 oralig'ida, ya'ni "buzuq". Lekin biz sinagan Git 2.54 bunga ishonmadi: buyruqni sog' commitda ham sinab ko'rdi. Biz ishga tushirdik:

text
running './yoq.sh'
'./yoq.sh': line 1: ./yoq.sh: No such file or directory
[37d8fbda50c74bba80f76ef1029d69b7bef61344] feat: buyurtma jami va 10% chegirma
running './yoq.sh'
'./yoq.sh': line 1: ./yoq.sh: No such file or directory
error: bogus exit code 127 for good revision

"Sog' commit uchun soxta chiqish kodi 127" — Git sizni to'xtatdi: sog' deb aytilgan commitda ham "buzuq" chiqsa, aybdor commit emas, buyruqning o'zi. Skript yo'lini tekshiring; repo tashqarisidagi skriptga bash ../sinov.sh deb murojaat qiling.

8.4 128 va undan katta kod

text
error: bisect run failed: exit code 130 from 'sh' '-c' 'exit 130' is < 0 or >= 128

"Bisect run muvaffaqiyatsiz: chiqish kodi 130 — 0 dan kichik yoki 128 dan katta." Ctrl + C bilan to'xtatilgan jarayon aynan 130 qaytaradi. Bu xato emas, xavfsizlik: Git "nimadir butunlay buzildi" deb qidiruvni to'xtatadi. Sinov muhiti buzilsa (baza yo'q, disk to'ldi), skriptingiz ataylab exit 128 qaytarishi mumkin.

8.5 O'zgargan fayl bilan bisect

Bisect commit almashtirganda Git ishchi daraxtdagi o'zgarishlaringizni olib yuradi — agar ular to'qnashmasa. Biz test.js ga bitta qator qo'shib, git bisect start HEAD v1.0.0 qildik. Qidiruv boshlandi va git status --short shuni ko'rsatdi:

text
 M test.js

Endi har commit sizning tahriringiz bilan sinaladi — natija ifloslanadi. Bisect'dan oldin ishchi daraxt toza bo'lsin: git stash (Stash) yoki commit.

8.6 Xato goh bor, goh yo'q

«G'oya» bo'limidagi shart buzilgan: xato yo'qolib, yana qaytgan yoki test beqaror. Sinov skriptida testni 2–3 marta yurgizing yoki aniq regressiya testini yozing.

9. Mashqlar

Mashqlar kurs/mashqlar/32/01-bisect/ da. 2-mashq yechimi Node 24.21.0 da, 3-mashq yechimi Git 2.54.0 da tekshirilgan (biz ishga tushirdik).

1-mashq (oson): bo'shliqlarni to'ldiring

  • Bisect'ga "bu commitni sinab bo'lmaydi" deydigan chiqish kodi —
  • 1 000 commitli tarix uchun taxminan nechta qadam kerak? —
  • Qidiruvni tugatib, boshlang'ich branch'ga qaytaradigan buyruq — git bisect
  • Joriy so'zlarni (masalan sog / buzuq) ko'rsatadigan buyruq — git bisect
  • Qo'lda qilingan javoblarni fayldan qayta bajaradigan buyruq — git bisect
Yechim
  • 125 — skip; 0 — sog', 1–127 — buzuq, 128+ — to'xtatish.
  • 10 — log₂ 1000.
  • reset — unutsangiz, detached HEAD'da qolasiz.
  • terms — --term-old / --term-new so'zlari.
  • replay — git bisect log > jurnal.txt, xato qatorni o'chirish, git bisect replay jurnal.txt.

2-mashq (o'rta): bisect'ni JavaScript'da

bisectQil(natijalar) funksiyasini yozing. natijalar — commitlar bo'yicha test natijalari, eskidan yangiga: false — sog', true — buzuq. Birinchi element doim sog', oxirgisi doim buzuq. Funksiya birinchi buzuq commit indeksini va nechta tekshiruv qilinganini qaytarsin: { birinchiBuzuq, tekshiruv }. Faqat binar qidiruv: har tekshiruvda oraliqning o'rtasini oling. Ishora: ikki chegara — sog va buzuq indekslari; while sikli ular yonma-yon bo'lguncha (Binary search asoslari). Test uchun massivni Array.from({ length: n }, (_, i) => i >= aybdor) bilan yasang.

Yechim
js
function bisectQil(natijalar) {
  let sog = 0;
  let buzuq = natijalar.length - 1;
  let tekshiruv = 0;
  while (buzuq - sog > 1) {
    const orta = Math.floor((sog + buzuq) / 2);
    tekshiruv++;
    if (natijalar[orta]) buzuq = orta;
    else sog = orta;
  }
  return { birinchiBuzuq: buzuq, tekshiruv };
}

const tarix = (n, aybdor) =>
  Array.from({ length: n }, (_, i) => i >= aybdor);

console.log(bisectQil(tarix(16, 11)));
console.log(bisectQil(tarix(1001, 637)));
console.log(bisectQil([false, true]));

Konsolda:

text
{ birinchiBuzuq: 11, tekshiruv: 4 }
{ birinchiBuzuq: 637, tekshiruv: 10 }
{ birinchiBuzuq: 1, tekshiruv: 0 }

Birinchi qator — «Bahor» repo'sining nusxasi: 16 commit, aybdor 11-indeksda (5d77071), 4 tekshiruv — git bisect run bilan aynan. Ikkinchisi — 1 000 nomzod, 10 tekshiruv. Uchinchisi — chegara holat: sog' va buzuq yonma-yon, hech narsa sinash shart emas. Git haqiqatda biroz aqlliroq: tarix chiziqli bo'lmasa (merge'lar), u "o'rta" ni commitlar grafi bo'yicha tanlaydi.

3-mashq (qiyin): 125 nega kerak

kurs/mashqlar/32/01-bisect/hisob-yarat.sh skripti 9 commitli «Bahor» hisob repo'sini yaratadi (muallif va sanalar qotirilgan — SHA'lar sizda ham bir xil chiqadi). Ichida ikki muammo bor: bitta commitda xato kiritilgan, boshqasida hisob.js ko'chgan, lekin test import yo'li yangilanmagan.

  1. bash hisob-yarat.sh hisob bilan repo yarating va cd hisob.
  2. git bisect start HEAD v1.0.0 va git bisect run node test.js — qaysi commit aybdor chiqdi?
  3. git bisect reset. Endi repo tashqarisida sinov.sh yozing: ERR_MODULE_NOT_FOUND bo'lsa 125, aks holda testning kodi (yoki mashq papkasidagi tayyorini oching va o'qing). Qidiruvni qayta yurgizing.
  4. Ikki javobni solishtiring. Qaysi biri to'g'ri va nega?

Ishora: «To'g'ri sinov skripti» bo'limiga qarang. Natijani qisqa ko'rish uchun git log -1 --format=%s refs/bisect/bad — bisect "buzuq" deb belgilagan oxirgi commit xabari.

Yechim
bash
bash hisob-yarat.sh hisob && cd hisob

git bisect start HEAD v1.0.0
git bisect run node test.js 2>&1 | grep "is the first bad commit"
git log -1 --format=%s refs/bisect/bad
git bisect reset

# ../sinov.sh — «To'g'ri sinov skripti» (mashq papkasida bor)
git bisect start HEAD v1.0.0
git bisect run bash ../sinov.sh 2>&1 | grep "is the first bad commit"
git log -1 --format=%s refs/bisect/bad
git bisect log | grep "^# skip"
git bisect reset

Biz ishga tushirdik (hisob-yarat.sh, git bisect start va reset chiqishlari yozilmagan):

text
e4cd19602342e275e63c99d4a51980a51618cd36 is the first bad commit
refactor: hisob.js ni src/ ga ko'chir
a4e3fe2b1dd0e13b028a1306652c47943fbee022 is the first bad commit
refactor(hisob): chegarani konstantaga chiqar
# skip: [e4cd19602342e275e63c99d4a51980a51618cd36] refactor: hisob.js ni src/ ga ko'chir

Birinchi javob noto'g'ri. Bisect birinchi qadamda aynan ko'chirilgan commitni ochdi. U yerda test ishlamadi (ERR_MODULE_NOT_FOUND, kod 1) va bisect buni "buzuq" deb oldi. Keyingi qadamlar oldingi commitlarni sog' deb topdi — va "aybdor — ko'chirish" degan xulosa chiqdi. Chegirma xatosiga aloqasi yo'q.

Ikkinchi javob to'g'ri: 125 bilan ko'chirilgan commit o'tkazib yuborildi (jurnalda # skip), bisect yonidagi commitlarni sinadi va haqiqiy aybdorni — >= ni > ga almashtirgan refactor'ni topdi. Saboq: "test ishlamadi" va "test yiqildi" — ikki xil javob. Skriptingiz ularni ajratmasa, bisect sizga ishonch bilan yolg'on gapiradi.

10. Real ishda

  • Qayerda uchraydi: "o'tgan relizda ishlardi, bu relizda buzildi" — har jamoada. Linux yadrosi hujjatida regressiyani tekshirish va bisect qilish bo'yicha alohida yo'riqnoma bor.
  • Regressiya testi bilan juftlik: bisect aybdorni topadi, test xato qaytmasligini kafolatlaydi. Kod ko'rib chiqishda (code review) "bu tuzatishga test qani?" — eng ko'p beriladigan savollardan biri.
  • Kichik commitlar foydasi: 25 faylli commit ichida yana qidirishga to'g'ri keladi. Yaxshi commit darsidagi "atomar commit" qoidasi shu yerda o'zini oqlaydi.
  • Intervyu savollari: "Qaysi commit xato kiritganini qanday topasiz?", "git bisect run qanday ishlaydi, 125 nima?", "Beqaror test bisect'ga qanday ta'sir qiladi?"

Xulosa

  • git bisect — tarixda binar qidiruv: n ta commit uchun log₂ n qadam, 1 000 commitda 10 ta.
  • Qo'lda: start → bad → good → har qadamda sinab javob berish → reset.
  • git bisect run: 0 — sog', 1–127 — buzuq, 125 — skip, 128+ — to'xtatish. "Test ishlamadi"ni 125 bilan ajrating.
  • Skip ko'p bo'lsa, Git aniq javob bermaydi — "We cannot bisect more!" va nomzodlar ro'yxati.
  • Test nimani o'lchasa, bisect o'shani topadi: bisect uchun tez, barqaror, qora quti regressiya testi yozing.
  • vazifalar-api'da breaker nuqsoni 93a6b16 da kiritilgani 3 qadamda topildi va testi bilan yopildi.

Keyingi dars: CHANGELOG va reliz avtomatikasi — bugungi fix(ai): … commitidan versiya raqami va o'zgarishlar tarixini avtomatik hosil qilamiz: release-please, semantic-release va changesets.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Git bisect: xatoni kiritgan commitni ikkiga bo'lib topish — IlmHamroh