IlmHamroh
JavaScript Full-stack/32-qism. Professional Git va jamoada ishlash5/19-dars25 daqiqa
Mundarija (36)

Tarixni qayta yozish va sirlarni tozalash: git filter-repo, majburiy push va push protection

Qisqacha: Sir bir marta commit qilinsa, keyingi commitda o'chirilsa ham tarixda qoladi — git log -S uni bir soniyada topadi. Birinchi qadam doim rotatsiya: eski kalitni bekor qilib, yangisini berish. Tarixni tozalash — ikkinchi, ixtiyoriy qadam: git filter-repo faylni (--invert-paths --path .env) yoki matnni (--replace-text) butun tarixdan olib tashlaydi, lekin sirga tegmagan commitlardan keyingi hamma hash'ni o'zgartiradi. Keyin majburiy push va har bir hamkasbning klonini tozalash kerak. Kurs tajribasida bitta eski klondan qilingan oddiy git pull va git push tozalangan sirni markaziy repo'ga qaytarib qo'ydi.

Bu darsda

  • Sir tarixda qanday "yashashini" o'z repo'ngizda ko'rasiz: git grep va git log -S farqi.
  • git filter-repo bilan faylni va matnni butun tarixdan olib tashlaysiz.
  • Nega filter-repo "yangi klon" talab qilishini va origin ni o'chirishini tushunasiz.
  • Majburiy push (force push) dan keyin hamkasblar nima qilishi kerakligini ko'rasiz — to'g'ri va noto'g'ri yo'l bilan.
  • GitHub tomonidagi himoyani bilasiz: push protection, secret scanning va GitHub Support.

Oldin bilishingiz kerak: Sirlarni boshqarish, Kod arxeologiyasi: blame, log -S, log -L, Interaktiv rebase, Ajralgan tarix: pull --rebase va force-with-lease, Reflog.

1. Nega bu kerak?

Sirlarni boshqarish darsida qoidani yozgan edik: sir sizib chiqsa, avval rotatsiya, keyin (ixtiyoriy) tozalash. O'sha darsda birinchi qismni bajardik. Bugun ikkinchisi — tozalash.

Nega "ixtiyoriy"? Chunki bekor qilingan kalit endi hech narsani ochmaydi. Uni tarixda qoldirish xavfsiz, faqat chiroyli emas. Lekin tozalash shart bo'ladigan holatlar ham bor:

  • Almashtirib bo'lmaydigan ma'lumot. Mijozlar ro'yxati, shaxsiy ma'lumot, pasport skani. Uni "rotatsiya" qilib bo'lmaydi.
  • Hujjat yoki qonun talabi. Ma'lumot repo'dan butunlay yo'qolishi kerak.
  • Katta fayl. 200 MB video tasodifan commitga tushgan. Har klonda u yuklanadi (bu haqda keyingi darsda).
  • Ochiq repo. Kalit bekor qilingan bo'lsa ham, uning formati va joylashuvi hujumchiga "bu jamoa sirni qayerda saqlaydi" degan ma'lumot beradi.

Hayotiy o'xshatish: kitobning bitta sahifasiga telefon raqamingizni yozib qo'ydingiz. Kitob 50 nusxada chop etilgan. Siz o'z nusxangizdagi sahifani yirtasiz — lekin qolgan 49 nusxa allaqachon tarqalgan. Eng to'g'ri ish — raqamni almashtirish. Sahifani yirtish esa qo'shimcha chora: yangi chop etiladigan nusxalarda raqam bo'lmaydi.

Git'da ham shunday. Tarixni "qayta chop etish" mumkin, lekin eski nusxalar — hamkasblarning klonlari, fork'lar, CI keshlari — sizning nazoratingizda emas.

2. Muammo: sir tarixda

2.1 O'quv repo

«Bahor» choyxonasining Telegram boti. Repo'ni kurs/mashqlar/32/05-tozalash/sir-repo-yarat.sh skripti yaratadi: bash sir-repo-yarat.sh bot, keyin cd bot. Muallif va sanalar qotirilgan — SHA'lar sizda ham darsdagidek chiqadi. Sir — ataylab yasalgan qiymat: SOXTA-BAHOR-SIRI-0000-faqat-lokal. U hech qaysi xizmatning kalit formatiga o'xshamaydi.

Tarix:

bash
git log --oneline

Terminalda:

text
8d0b325 (HEAD -> main) docs: ish vaqti
b536195 fix: token env'dan o'qiladi
b51df01 feat: konfig moduli
112cb66 fix: .env ni git'dan chiqarish
f4ca674 feat: sozlamalar
09785db feat: bot boshlang'ich

Ikki xato bor. Birinchisi: f4ca674 da .env fayli commit qilingan, keyin 112cb66 da kuzatuvdan chiqarilgan. Ikkinchisi: b51df01 da kalit to'g'ridan konfig.js ga yozilgan, b536195 da process.env ga almashtirilgan. Ikkala holatda ham "tuzatish" commiti bor.

2.2 Joriy fayllarda — toza

bash
git grep -c "SOXTA-BAHOR-SIRI-0000-faqat-lokal"
echo "exit $?"
text
exit 1

git grep faqat joriy holatni qidiradi. Hech narsa topilmadi, chiqish kodi 1. Shu yerda ko'pchilik "tuzatildi" deb to'xtaydi.

2.3 Tarixda — to'rt commit

Kod arxeologiyasi darsidagi git log -S — "shu matn qo'shilgan yoki olib tashlangan commitlar":

bash
git log --all -S"SOXTA-BAHOR-SIRI-0000-faqat-lokal" --oneline
git show HEAD~4:.env

Terminalda:

text
b536195 fix: token env'dan o'qiladi
b51df01 feat: konfig moduli
112cb66 fix: .env ni git'dan chiqarish
f4ca674 feat: sozlamalar
PORT=3000
BOT_TOKEN=SOXTA-BAHOR-SIRI-0000-faqat-lokal

To'rt commit: ikkitasi sirni qo'shgan, ikkitasi olib tashlagan. git show HEAD~4:.env — to'rt commit oldingi .env ning to'liq mazmuni. Repo'ni klonlagan har kishi shu ikki buyruqni yozishi yetarli.

Tekshirib ko'ring: Nega "tuzatish" commitlari (112cb66, b536195) ham log -S ro'yxatida bor, axir ular sirni olib tashlagan-ku?

Javob

-S matnning soni o'zgargan commitlarni topadi: qo'shilgan ham, olib tashlangan ham. Olib tashlagan commitning diff'ida sir - qatorida to'liq turibdi. Ya'ni "tuzatish" commiti sirni o'zi ham ko'rsatadi.

3. Tartib: avval kalit, keyin tarix

Tozalashni boshlashdan oldin to'liq rejaga qaraymiz. Bu ketma-ketlik GitHub hujjatidagi "Removing sensitive data from a repository" sahifasiga mos:

flowchart TD
  A["Sir sizdi"] --> B["1. Rotatsiya:<br/>eski kalit bekor"]
  B --> C{"Tarixni<br/>tozalash shartmi?"}
  C -- "yo'q" --> Z["Tugadi:<br/>kalit endi ishlamaydi"]
  C -- "ha" --> D["2. Yangi klonda<br/>git filter-repo"]
  D --> E["3. Tekshirish:<br/>git log -S bo'sh"]
  E --> F["4. Majburiy push<br/>--force --mirror"]
  F --> G["5. Hamkasblar:<br/>qayta klon yoki rebase"]
  G --> H["6. GitHub Support:<br/>PR havolalari, kesh"]

Ikki narsaga e'tibor bering. Birinchi: rotatsiya tozalashdan oldin. Tozalash soatlab, hamkasblarni kutish bilan kunlab davom etadi. Shu vaqt ichida kalit ishlab turmasin. Ikkinchi: tozalash bitta buyruq emas, olti qadamlik jarayon. Eng qiyini 5-qadam — boshqa odamlar.

4. git filter-repo

4.1 O'rnatish

git filter-repo — Git loyihasi tavsiya qiladigan tarixni qayta yozish vositasi. Eski git filter-branch ning hujjatida ham "uning o'rniga filter-repo ishlating" deb yozilgan. U Git bilan birga kelmaydi — Python 3 da yozilgan alohida skript:

bash
pip install git-filter-repo
git filter-repo --version

O'rnatilmagan bo'lsa, Git uni tanimaydi:

text
git: 'filter-repo' is not a git command. See 'git --help'.

"filter-repo — Git buyrug'i emas". Git git X ni ko'rsa, PATH'dan git-X dasturini qidiradi. pip aynan shunday nomli faylni o'rnatadi. Kurs tajribasida versiya — 2.47.0 (pip), Git 2.54.0.

4.2 Birinchi urinish: ishchi repo'da

Avval markaziy repo kerak — GitHub o'rnida lokal "bare" repo (Remote darsidagidek). Unga push qilamiz va ikki hamkasb — Nodira va Jasur — klon oladi:

bash
git init -q --bare -b main ../markaz.git
git remote add origin ../markaz.git
git push -q -u origin main
git clone -q ../markaz.git ../nodira
git clone -q ../markaz.git ../jasur

Endi o'z ishchi repo'mizda .env ni butun tarixdan olib tashlashga urinamiz:

bash
git filter-repo --invert-paths --path .env

Biz ishga tushirdik:

text
Aborting: Refusing to destructively overwrite repo history since
this does not look like a fresh clone.
  (expected at most one entry in the reflog for HEAD)
Note: when cloning local repositories, you need to pass
      --no-local to git clone to avoid this issue.
Please operate on a fresh clone instead.  If you want to proceed
anyway, use --force.

"To'xtatildi: bu yangi klonga o'xshamagani uchun tarixni buzib qayta yozishni rad etaman (HEAD reflog'ida ko'pi bilan bitta yozuv kutilgan edi). Iltimos, yangi klonda ishlang." Chiqish kodi 1.

Bu — xato emas, himoya. filter-repo qaytarib bo'lmaydigan ish qiladi. U "foydalanuvchida zaxira bormi?" degan savolga bilvosita javob izlaydi: yangi klon bo'lsa, asl nusxa serverda turibdi. Ishchi repo'da esa push qilinmagan commit, stash, branch bo'lishi mumkin — ular yo'qolsa, qaytarib bo'lmaydi. Reflog'dagi bir nechta yozuv (Reflog darsi) — "bu repo'da ish qilingan" degan belgi.

--force bu tekshiruvni o'chiradi. filter-repo hujjati ochiq yozadi: har doim --force yozishga o'rganib qolish — "juda yomon g'oya". Kurs tajribasida biz --force ni faqat bir martalik sinov repo'sida ishlatdik.

4.3 To'g'ri yo'l: yangi --mirror klon

bash
cd ..
git clone -q --mirror markaz.git tozalash.git
cd tozalash.git

--mirror — "ko'zgu" klon: ishchi papkasiz (bare), hamma ref'lar bilan — branch'lar, tag'lar, boshqa ref'lar. Tozalash uchun aynan shu kerak: sir faqat main da emas, boshqa branch yoki tag'da ham bo'lishi mumkin.

Endi ikki vazifa bir buyruqda. Fayl — --invert-paths --path .env: ".env dan boshqa hamma narsani qoldir". Matn — --replace-text <fayl>: har qatorda bitta almashtirish qoidasi.

bash
echo "SOXTA-BAHOR-SIRI-0000-faqat-lokal==>***OLIB-TASHLANDI***" \
  > ../almashtir.txt
git filter-repo --invert-paths --path .env \
  --replace-text ../almashtir.txt

Biz ishga tushirdik (yo'l qisqartirildi):

text
NOTICE: Removing 'origin' remote; see 'Why is my origin removed?'
        in the manual if you want to push back there.
        (was …/markaz.git)
Parsed 6 commits
New history written in 0.45 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
Completely finished after 1.26 seconds.

Oltita commit 1.3 soniyada qayta yozildi. Chiqishning birinchi qatoriga keyinroq qaytamiz.

Almashtirish fayli formati (filter-repo hujjatidan):

Qator Ma'nosi
matn aniq matn, o'rniga ***REMOVED***
matn==>yangi aniq matn, o'rniga yangi
regex:naqsh==>yangi muntazam ifoda
glob:naqsh==>yangi * va ? shabloni

4.4 Natija: yangi tarix

bash
git log --oneline main
git log --all -S"SOXTA-BAHOR-SIRI-0000-faqat-lokal" --oneline
git show main~2:konfig.js

Terminalda:

text
84aecd1 (HEAD -> main) docs: ish vaqti
0d20272 fix: token env'dan o'qiladi
586fae7 feat: konfig moduli
235b65f fix: .env ni git'dan chiqarish
ae30c80 feat: sozlamalar
09785db feat: bot boshlang'ich
export const token = '***OLIB-TASHLANDI***';

log -S bo'sh qaytdi — sir tarixda yo'q. konfig.js ning eski versiyasida sir o'rnida belgi turibdi. Commit xabarlari, mualliflar, sanalar — o'zgarmadi.

Lekin hash'larga qarang. filter-repo eski va yangi hash'lar xaritasini filter-repo/commit-map fayliga yozadi (qisqartirib, tartiblab ko'rsatdik):

text
old    new
09785db09785db
112cb66235b65f
8d0b32584aecd1
b51df01586fae7
b5361950d20272
f4ca674ae30c80

Faqat birinchi commit — 09785db — o'zgarmadi. Qolgan beshtasi yangi hash oldi. Nega?

Git ichkaridan darsini eslang: commit hash'i uning butun mazmunidan hisoblanadi — daraxt (fayllar), ota-commit hash'i, muallif, xabar. f4ca674 ning daraxtidan .env olib tashlandi — daraxt o'zgardi, demak hash o'zgardi. Undan keyingi 112cb66 ning daraxti o'zgarmagan bo'lishi mumkin edi, lekin uning ota-commit hash'i o'zgardi — demak uning hash'i ham o'zgaradi. Zanjir oxirigacha shunday ketadi. Birinchi commitda sir yo'q edi va uning otasi yo'q — u tegilmadi.

Qoida: sir tarixning qayerida birinchi marta paydo bo'lsa, undan keyingi hamma commit yangi bo'ladi. Sir uch yil oldin commit qilingan bo'lsa — uch yillik tarix qayta yoziladi. Bu qoida imzosiz commitlar uchun; imzolangan repoda butun tarix yangilanadi («Boshqa yon ta'sirlar» bo'limi).

4.5 Nega origin o'chirildi

Chiqishning birinchi qatori: "'origin' remote olib tashlanmoqda". Tekshiramiz — git remote bo'sh.

filter-repo hujjatida "Why is my origin removed?" bo'limi bor. Sabab: yangi tarixni eski tarix ustiga tasodifan push qilib yubormaslik. Oddiy git push rad etiladi, lekin chalg'igan dasturchi git pull qilib, eski va yangi tarixni birlashtirib yuborishi mumkin — keyingi bo'limda aynan shuni ko'ramiz. Hujjat yangi tarixni yangi repo'ga push qilishni tavsiya qiladi. Eski joyga qaytarish kerak bo'lsa, remote'ni qo'lda qo'shasiz.

4.6 --sensitive-data-removal rejimi

filter-repo 2.47 da sir tozalash uchun maxsus bayroq qo'shildi. GitHub hujjati hozir aynan uni tavsiya qiladi. Xuddi shu repo'ning yangi --mirror klonida:

bash
git filter-repo --sensitive-data-removal --invert-paths \
  --path .env --replace-text ../almashtir.txt

Biz ishga tushirdik (boshi va oxiri):

text
You rewrote 5 (of 6) commits.
NOTE: First Changed Commit(s) is/are:
  f4ca674c58524823aeac0664aa95ba8045df32bc
NOTE: LFS object orphaning not checked (LFS not in use)
...
NEXT STEPS FOR YOUR SENSITIVE DATA REMOVAL:
...
  * Force push the rewritten history to the server:
      git push --force --mirror origin
  * Contact the server admins for additional steps they need to take; the
    First Changed Commit(s) may come in handy here.
  * Have other colleagues with a clone either discard their clone and reclone
    OR follow the detailed steps in the manual to repeatedly rebase and
    purge the sensitive data from their copy.  Again, the First Changed
    Commit(s) may come in handy.

Uch farq:

  1. "First Changed Commit" — birinchi o'zgargan commit: f4ca674, sir paydo bo'lgan joy. Server administratoriga va hamkasblarga aynan shu hash kerak: "shu commitdan keyingi hamma narsa yangi".
  2. origin o'chirilmaydi — bu rejimda maqsad aynan eski joyga qaytarish. git remote — origin.
  3. Keyingi qadamlar ro'yxati — «Tartib: avval kalit, keyin tarix» bo'limidagi diagrammaning o'zi, vositaning tilida.

Sir tozalashda shu rejimni ishlating. Oddiy rejim — tarixni boshqa maqsadda qayta yozish uchun (papkani alohida repo'ga ajratish, katta faylni olib tashlash).

5. Majburiy push va uning oqibatlari

5.1 Markazga yuborish

Oddiy rejimdagi tozalash.git ga qaytamiz va origin ni qo'lda qo'shamiz:

bash
git remote add origin ../markaz.git
git push origin main
text
To ../markaz.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '../markaz.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.

"Rad etildi: remote'da sizda yo'q ish bor." Ajralgan tarix darsidagi holat: markazdagi 8d0b325 yangi tarixda yo'q. Git git pull ni maslahat beradi — va aynan shu maslahat bu yerda halokatli: pull eski tarixni (sir bilan) qaytarib olib keladi. Bizga almashtirish kerak:

bash
git push --force origin main
text
To ../markaz.git
 + 8d0b325...84aecd1 main -> main (forced update)

+ va "forced update" — eski 8d0b325 o'rniga yangi 84aecd1. Haqiqiy GitHub repo'da bunga ruleset to'sqinlik qiladi (Branch himoyasi darsidagi "Block force pushes"). Tozalash paytida uni vaqtincha o'chirish kerak bo'ladi — GitHub hujjati ham buni yon ta'sirlar ro'yxatida aytadi. Sir tozalashda butun repo uchun git push --force --mirror origin ishlatiladi: barcha branch va tag'lar bir martada. GitHub'da bu buyruq refs/pull/... ref'larida rad oladi — hujjatga ko'ra ular faqat o'qish uchun. Bu kutilgan holat: ularni GitHub Support hal qiladi («GitHub tomoni» bo'limi).

Diqqat: Odatiy ishda --force emas, --force-with-lease ishlating (Ajralgan tarix darsi). Tozalash — kamdan-kam holat: siz markazdagi tarixni ataylab butunlay almashtiryapsiz va bundan oldin jamoani ogohlantirgansiz.

5.2 Jasur: to'g'ri yo'l

Jasur tozalashdan oldin klon olgan va ustida bitta commit qilgan (feat: narx fayli), hali push qilmagan. Jamoa chatida "tarix qayta yozildi, birinchi o'zgargan commit f4ca674" degan xabarni o'qidi. U merge qilmaydi — o'z commitlarini yangi tarix ustiga ko'chiradi:

bash
git fetch -q
git rebase --onto origin/main 'origin/main@{1}' main
git log --oneline -3

Terminalda:

text
Successfully rebased and updated refs/heads/main.
b225906 (HEAD -> main) feat: narx fayli
84aecd1 (origin/main, origin/HEAD) docs: ish vaqti
0d20272 fix: token env'dan o'qiladi

rebase --onto A B main — "B dan keyingi main commitlarini A ustiga qayta ter". origin/main@{1} — fetch'dan oldingi origin/main holati, ya'ni eski 8d0b325 (Reflog darsidagi @{1} sintaksisi). Demak, faqat Jasurning o'z commiti ko'chdi. Eski tarix uning main idan chiqib ketdi: git log -S endi 0 ta commit ko'rsatadi. Push oddiy, --force siz o'tdi:

text
To ../markaz.git
   84aecd1..b225906  main -> main

O'z commiti bo'lmasa, yo'l yanada oddiy: eski klonni o'chirib, qayta git clone.

5.3 Nodira: odatdagi git pull

Nodira chatni o'qimadi. U ham bitta commit qildi (feat: menyu moduli) va odatdagidek git pull yozdi:

text
From ../markaz
 + 8d0b325...b225906 main       -> origin/main  (forced update)
hint: You have divergent branches and need to specify how to reconcile them.
...
fatal: Need to specify how to reconcile divergent branches.

Git 2.54 sozlanmagan pull da ajralgan tarixni o'zi hal qilmaydi — "qanday birlashtirishni ko'rsating" deb to'xtaydi (chiqish kodi 128). forced update so'zi — birinchi ogohlantirish: kimdir tarixni almashtirgan. Lekin Nodira hint'dagi variantlardan birini tanlaydi:

bash
git pull --no-rebase
git push origin main
text
Merge made by the 'ort' strategy.
To ../markaz.git
   b225906..b098b2c  main -> main

Merge muvaffaqiyatli, push ham — chunki merge commiti yangi tarixni o'z ichiga oladi, ya'ni bu oddiy fast-forward. Hech qanday himoya ishlamadi. Markazda nima bo'ldi?

bash
SIR="SOXTA-BAHOR-SIRI-0000-faqat-lokal"
git -C ../markaz.git log --all -S"$SIR" --oneline
git -C ../markaz.git log --oneline --graph main

Biz ishga tushirdik:

text
b536195 fix: token env'dan o'qiladi
b51df01 feat: konfig moduli
112cb66 fix: .env ni git'dan chiqarish
f4ca674 feat: sozlamalar
*   b098b2c (HEAD -> main) Merge branch 'main' of ../markaz
|\  
| * b225906 feat: narx fayli
| * 84aecd1 docs: ish vaqti
| * 0d20272 fix: token env'dan o'qiladi
| * 586fae7 feat: konfig moduli
| * 235b65f fix: .env ni git'dan chiqarish
| * ae30c80 feat: sozlamalar
* | 1e69225 feat: menyu moduli
* | 8d0b325 docs: ish vaqti
* | b536195 fix: token env'dan o'qiladi
* | b51df01 feat: konfig moduli
* | 112cb66 fix: .env ni git'dan chiqarish
* | f4ca674 feat: sozlamalar
|/  
* 09785db feat: bot boshlang'ich

Sir qaytdi. Butun tozalash bekor bo'ldi. Bundan ham yomoni: tarixda har commit endi ikki nusxada — eski va yangi, bir xil xabar bilan. Rasmda:

gitGraph
  commit id: "09785db"
  branch eski
  commit id: "f4ca674 (sir)"
  commit id: "b51df01 (sir)"
  commit id: "8d0b325"
  commit id: "1e69225 menyu"
  checkout main
  commit id: "ae30c80"
  commit id: "586fae7"
  commit id: "84aecd1"
  commit id: "b225906 narx"
  merge eski id: "b098b2c merge"

GitHub hujjati bu xavfni "recontamination" (qayta ifloslanish) deb ataydi: eski klonlardan biri push qilsa, "sir qaytadi". Shuning uchun tozalash — texnik emas, jamoaviy jarayon: hamma ogohlantiriladi, hamma qayta klon yoki rebase qiladi, va shundan keyingina ish davom etadi.

Tekshirib ko'ring: Nodira git pull --rebase ni tanlaganida nima bo'lardi?

Javob

Ko'pincha to'g'ri ishlaydi, lekin kafolat emas. Biz ikkala holatni sinadik. Odatdagi klonda Git reflog'dan eski origin/main ni topdi ("fork-point") va faqat Nodiraning commitini ko'chirdi:

text
Successfully rebased and updated refs/heads/main.
commitlar: 7, sir: 0

Keyin eski origin/main ref'i yo'q klonda sinadik (masalan, boshqa remote nomi bilan ishlagan yoki ref'ni o'chirgan hamkasb). Git eski commitlarni ham "Nodiraniki" deb terishga urindi va birinchisidayoq to'xtadi:

text
Could not apply f4ca674... # feat: sozlamalar

f4ca674 — sir paydo bo'lgan commit. Konfliktni "yechib" davom etsa, sir qaytadi. Shuning uchun Jasurdagi aniq rebase --onto yoki qayta klon — ishonchli yo'l. Push'dan oldin git log -S"<sir>" bilan tekshirish — majburiy.

5.4 Boshqa yon ta'sirlar

GitHub hujjatidagi "Side effects of rewriting history" ro'yxatidan (2026-11-04 da o'qildi):

  • Hash'lar o'zgaradi — commit havolalari (issue'larda, CHANGELOG'da, chatda) endi mavjud bo'lmagan commitga olib boradi.
  • Imzolar yo'qoladi — filter-repo commit va tag imzolarini olib tashlaydi, sir paydo bo'lishidan oldingi commitlarnikini ham. Commit imzolash darsidagi G belgisi N ga aylanadi.
  • PR'lar — yopilgan PR'larning diff'i ko'rinmay qoladi, ochiq PR'larda izohlar yo'qolishi mumkin; hujjat avval ochiq PR'larni yopish yoki merge qilishni tavsiya qiladi.
  • Hamkasblarning ishi — tozalashdan oldin push qilinmagan ishlar rebase talab qiladi.

Imzo bandini o'zimiz tekshirdik. Uch commitli repo, hammasi SSH kaliti bilan imzolangan, ikkinchisida sir fayli bor (sir.txt). Yangi klonda git filter-repo --invert-paths --path sir.txt, keyin git log --format='%h %G? %s'. Qarang: belgi va hash'lar qanday o'zgardi.

text
=== oldin
f4bf238 G docs: ish vaqti
561b497 G feat: sozlamalar
d17e7a0 G feat: boshlang'ich
=== filter-repo'dan keyin
c3d26d9 N docs: ish vaqti
1c0a1ed N feat: boshlang'ich
tag ichida imzo: 0

Nima bo'ldi? Hamma imzo ketdi (G → N), tagdagi imzo ham. Muhimi — sirga tegmagan birinchi commit ham yangi hash oldi (d17e7a0 → 1c0a1ed). Imzo commit matnining bir qismi edi; u olib tashlangach, matn o'zgardi. Demak, imzolangan repoda «Natija: yangi tarix» bo'limidagi "birinchi commit tegilmadi" qoidasi ishlamaydi — butun tarix yangi bo'ladi. (Hash'lar sizda boshqa chiqadi: sinovda kalit har safar yangi yaratiladi.) Tozalashdan keyin yangi commitlarni odatdagidek imzolaysiz; eski tarix imzosiz qoladi.

6. GitHub tomoni

Lokal tarixni tozaladingiz va majburiy push qildingiz. GitHub'da sir hali ham qolishi mumkin (docs.github.com, sinalmagan, 2026-11):

  • PR havolalari. Har PR uchun GitHub refs/pull/N/head ni saqlaydi. Ularni siz o'chira olmaysiz.
  • Kesh ko'rinishlar. Commit sahifasi URL bo'yicha ochilib turishi mumkin.
  • Fork'lar. Ular boshqa odamlarniki — GitHub ularni tozalamaydi va egalarining kontaktini bermaydi.

Birinchi ikkisi uchun GitHub Support ga murojaat qilinadi — --sensitive-data-removal chiqishidagi "First Changed Commit" aynan shu murojaatda kerak. Support faqat rotatsiya bilan hal bo'lmaydigan sezgir ma'lumot uchun yordam beradi.

Eng yaxshisi — sir umuman push qilinmasin. GitHub'ning ikki himoyasi bor:

  • Secret scanning — repo'dagi ma'lum formatdagi kalitlarni (GitHub tokenlari, bulut kalitlari va h.k.) qidiradi va ogohlantiradi.
  • Push protection — push paytida ma'lum formatdagi sirni ko'rsa, push'ni rad etadi. Shaxsiy akkauntlar uchun ochiq repo'larga push'da sukut bo'yicha yoqilgan. Rad etilganda batafsil xabar chiqadi. Yozish huquqi bor odam uni sabab ko'rsatib chetlab o'tishi mumkin: "It's used in tests", "It's a false positive", "I'll fix it later". Repo darajasida push protection yoqilgan bo'lsa, chetlab o'tish ogohlantirish (alert) yaratadi, audit jurnaliga yoziladi va repo egalariga xat boradi. Faqat foydalanuvchi darajasidagi himoyada bunday ogohlantirish yaratilmaydi (hujjat, 2026-11-04).

Biz push protection'ni sinamadik: kursning sinov reposiga hech qanday sir yuborilmadi. Bizning SOXTA-… qiymatimiz hech qaysi ma'lum formatga mos emas — push protection uni tanimagan bo'lardi. Bu muhim saboq: himoya faqat taniladigan formatlarni ushlaydi. O'zingiz o'ylab topgan parol yoki baza manzili .env bilan ketsa — hech kim to'xtatmaydi. Sirlarni boshqarish darsidagi pre-commit skaner va .gitignore — birinchi qatordagi himoya.

7. Kichikroq qayta yozishlar

Hamma "tarixni qayta yozish" filter-repo emas. Push qilinmagan commitlar uchun Interaktiv rebase darsidagi vositalar yetarli: commit --amend, rebase -i, commit --fixup + --autosquash. Ular ham hash'larni o'zgartiradi — lekin hech kimda eski nusxa yo'q, shuning uchun xavfsiz.

Kanon tajribasida (IlmHamroh, 2026-11) bir foydali usul sinaldi: --autosquash ni muharrir ochmasdan ishlatish. GIT_SEQUENCE_EDITOR=: — Git rebase ro'yxatini ochadigan "muharrir" o'rniga hech narsa qilmaydigan : buyrug'ini ishlatadi, ya'ni ro'yxat o'zgarishsiz qabul qilinadi:

bash
git commit --fixup HEAD~1
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash HEAD~4

Kanon chiqishi — oldin va keyin:

text
027f720 fixup! feat: f fayl
6e76fdd feat: g fayl
04ed8c8 feat: f fayl
19b1bc2 feat: konfig
--- autosquash'dan keyin:
67bd6d0 feat: g fayl
972f05e feat: f fayl
19b1bc2 feat: konfig

fixup! commiti feat: f fayl ga qo'shildi. Skriptlarda va CI'da muharrir yo'q — bu usul shunday joylar uchun.

BFG Repo-Cleaner — filter-repo'ga muqobil vosita. U Scala'da yozilgan va Java (JRE 11+) ostida java -jar bilan ishlaydi: bfg --delete-files .env, bfg --replace-text. U ko'p eski qo'llanmalarda uchraydi. Kanon filter-repo'ni ishlatdi, BFG sinalmagan. GitHub hujjatining joriy versiyasi BFG'ni tilga olmaydi — faqat filter-repo'ni tavsiya qiladi.

8. Loyiha qadami: vazifalar-api tarixini tekshirish

Bu dars — tajriba darsi: vazifalar-api kodi o'zgarmaydi va commit yo'q. Bizning loyihada tozalash kerak emas — va buni isbotlash kerak.

Kanon tajribasida (IlmHamroh, 2026-11) xuddi shu jarayon vazifalar-api bilan bir xil tuzilishdagi alohida sinov repo'sida bajarildi: .env commiti, git filter-repo --invert-paths --path .env — 1.06 s, sirga tegmagan birinchi commit hash'i o'zgarmadi, origin o'chirildi. Eski klonda esa sir 2 joyda qoldi. Xulosa o'sha: tarixni tozalash kalitni almashtirmaydi.

Kursning sinov reposiga har push'dan oldin kanon tarixni ma'lum sir naqshlariga tekshirdi. O'z repo'ngizda ham qiling:

bash
git log --all -p | grep -c -E \
  'ghp_|github_pat_|sk-|AIza|AKIA|PRIVATE KEY|xox[bpa]-'

Naqshlar: GitHub tokenlari (ghp_, github_pat_), ko'p AI xizmatlari kaliti (sk-), Google (AIza), AWS (AKIA), maxfiy kalit fayli, Slack (xox…). Kanon shu naqshlar bilan har push'dan oldin tekshirdi — haqiqiy moslik 0. Sizda 0 dan katta bo'lsa — har mos qatorni ko'rib chiqing: ba'zan bu naqshning o'zi yozilgan skript bo'ladi. Haqiqiy kalit bo'lsa — avval rotatsiya, keyin shu dars.

Va bitta odat: .env.example — repo'da, .env — .gitignore da. Bu loyihada Sirlarni boshqarish darsidan beri shunday.

9. Ko'p uchraydigan xatolar

9.1 "does not look like a fresh clone"

filter-repo ishchi repo'da ishga tushirildi. Davosi: git clone --mirror (yoki lokal yo'l uchun git clone --no-local) bilan yangi klon va o'sha yerda ishlash. --force ni odat qilmang.

9.2 'filter-repo' is not a git command

Vosita o'rnatilmagan yoki pip papkasi PATH'da yo'q. pip install git-filter-repo, keyin terminalni qayta oching. Windows'da py -m pip install git-filter-repo ham ishlaydi.

9.3 Tozalandi, lekin sir yana paydo bo'ldi

Eski klondan pull + push («Nodira: odatdagi git pull» bo'limi). Davosi: tozalashdan oldin jamoani ogohlantiring, keyin har klonni qayta oling. Push'dan oldin git log --all -S"<sir>" — 0.

9.4 fatal: Need to specify how to reconcile divergent branches

pull paytida forced update ko'rdingizmi? Hint'dagi variantni o'ylamay tanlamang — avval jamoadan so'rang, tarix qayta yozilganmi.

9.5 Faqat main tozalandi

Oddiy klonda filter-repo faqat lokal branch'larni ko'radi. Sir eski feature branch yoki tag'da qoladi. Davosi: --mirror klon va push --force --mirror.

9.6 Tozaladim, endi kalit xavfsiz

Yo'q. Klonlar, fork'lar, CI loglari, botlar allaqachon nusxa olgan bo'lishi mumkin. Rotatsiya qilinmagan kalit — sizib chiqqan kalit.

10. Mashqlar

Mashqlar kurs/mashqlar/32/05-tozalash/ da. 2-mashq yechimi Node 24.21.0 da, 3-mashq yechimi Git 2.54.0 va git-filter-repo 2.47.0 da tekshirilgan (biz ishga tushirdik).

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

  • Sir sizib chiqqanda birinchi qadam —
  • Faqat joriy fayllarda qidiradigan buyruq — git
  • filter-repo'da faylni tarixdan olib tashlash: --path .env va
  • --replace-text da ==> yozilmasa, sir o'rniga qo'yiladigan matn —
  • Barcha branch va tag'larni bir martada almashtiradigan push — git push --force
Yechim
  • rotatsiya — eski kalit bekor qilinadi; tozalash — keyin.
  • grep — tarix uchun git log -S.
  • --invert-paths — "shu yo'ldan boshqasini qoldir".
  • ***REMOVED*** — filter-repo sukuti.
  • --mirror — --mirror klon bilan juft.

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

filter-repo almashtirish faylini o'qiydigan qoidalarniOqi(fayl) va qoidalarni matnga qo'llaydigan tozala(matn, qoidalar) funksiyalarini yozing. Format — «To'g'ri yo'l: yangi --mirror klon» bo'limidagi jadval: oddiy matn, ==> bilan almashtirish, regex: prefiksi (glob: shart emas). ==> bo'lmasa — ***REMOVED***. Bo'sh qatorlarni tashlab keting. Ishora: indexOf("==>") va slice (Satr metodlari); hamma moslikni almashtirish uchun replaceAll (replaceAll) va g bayroqli RegExp.

Yechim
js
function qoidalarniOqi(fayl) {
  return fayl
    .split("\n")
    .filter((q) => q.trim() !== "")
    .map((q) => {
      const i = q.indexOf("==>");
      const naqsh = i === -1 ? q : q.slice(0, i);
      const orniga = i === -1 ? "***REMOVED***" : q.slice(i + 3);
      if (naqsh.startsWith("regex:")) {
        return { re: new RegExp(naqsh.slice(6), "g"), orniga };
      }
      return { matn: naqsh, orniga };
    });
}

function tozala(matn, qoidalar) {
  let natija = matn;
  for (const q of qoidalar) {
    natija = q.re
      ? natija.replace(q.re, q.orniga)
      : natija.replaceAll(q.matn, q.orniga);
  }
  return natija;
}

const qoidalar = qoidalarniOqi([
  "SOXTA-BAHOR-SIRI-0000",
  "parol123==>[PAROL]",
  "regex:BOT_TOKEN=\\S+==>BOT_TOKEN=***",
].join("\n"));

console.log(tozala("const k = 'SOXTA-BAHOR-SIRI-0000';", qoidalar));
console.log(tozala("DB_PAROL=parol123", qoidalar));
console.log(tozala("BOT_TOKEN=abc.def\nPORT=3000", qoidalar));

Konsolda:

text
const k = '***REMOVED***';
DB_PAROL=[PAROL]
BOT_TOKEN=***
PORT=3000

Uchala qoida turi ishladi. Uchinchi misolda muntazam ifoda faqat BOT_TOKEN= qatorini almashtirdi — \S+ bo'sh joy yoki qator oxirigacha. Regex qoidasi kuchli, lekin xavfli: juda keng naqsh (regex:.*) butun fayllarni buzadi. filter-repo'ning o'zi Python muntazam ifodalarini ishlatadi — sintaksis deyarli bir xil, lekin almashtirishdagi guruh havolasi \1 (JavaScript'da $1).

3-mashq (qiyin): to'liq tozalash

sir-repo-yarat.sh bilan bot repo'sini yarating, "markaz" sifatida bare repo oching va unga push qiling. Keyin «Tartib: avval kalit, keyin tarix» bo'limidagi 2–4 qadamlarni bajaring: --mirror klon, --sensitive-data-removal bilan .env va sir matnini olib tashlash, push --force --mirror. Oxirida markazdan yangi klon oling va isbotlang: commitlar soni o'zgarmagan, sir ham, .env ham tarixda yo'q, konfig.js ning eski versiyasida sir o'rnida belgi.

Ishora: .env tarixini git log --all --oneline -- .env ko'rsatadi; wc -l qatorlarni sanaydi. filter-repo chiqishi uzun — > /dev/null 2>&1 bilan yashiring va natijani o'zingiz tekshiring.

Yechim
bash
bash sir-repo-yarat.sh bot
git init -q --bare -b main markaz.git
git -C bot push -q ../markaz.git main

git clone -q --mirror markaz.git tozalash.git
cat > qoidalar.txt <<'EOF'
SOXTA-BAHOR-SIRI-0000-faqat-lokal
EOF
cd tozalash.git
git filter-repo --sensitive-data-removal --invert-paths \
  --path .env --replace-text ../qoidalar.txt > /dev/null 2>&1
git push -q --force --mirror origin
cd ..

git clone -q markaz.git tekshir && cd tekshir
SIR="SOXTA-BAHOR-SIRI-0000-faqat-lokal"
echo "commitlar: $(git rev-list --count HEAD)"
echo "sir tarixda: $(git log --all -S"$SIR" --oneline | wc -l)"
echo ".env tarixda: $(git log --all --oneline -- .env | wc -l)"
git show HEAD~2:konfig.js
git log --oneline

Biz ishga tushirdik:

text
commitlar: 6
sir tarixda: 0
.env tarixda: 0
export const token = '***REMOVED***';
446886c docs: ish vaqti
e6c970a fix: token env'dan o'qiladi
e24c16b feat: konfig moduli
235b65f fix: .env ni git'dan chiqarish
ae30c80 feat: sozlamalar
09785db feat: bot boshlang'ich

Hash'larni dars bilan solishtiring: 235b65f va ae30c80 — darsdagidek, chunki .env bilan bog'liq o'zgarish bir xil. Keyingi uchta commit esa boshqacha (e24c16b, darsda 586fae7): bu safar sir o'rniga ***REMOVED*** yozildi, darsda ***OLIB-TASHLANDI***. Bitta belgining farqi ham butun zanjirni o'zgartiradi. Tekshiruv yangi klonda qilinganiga e'tibor bering: tozalash joyidagi repo emas, markazdagi holat muhim.

11. Real ishda

  • Qayerda uchraydi: "ochiq repo'ga kalit tushdi" — har yili minglab holatlar; botlar ochiq repo'larni bir necha daqiqada skanerlaydi. Jamoada tozalash odatda xavfsizlik hodisasi (incident) sifatida yuritiladi: kim, qachon, qaysi kalit, rotatsiya vaqti.
  • Boshqa maqsadlar: filter-repo bilan papkani alohida repo'ga ajratish (--subdirectory-filter), katta fayllarni olib tashlash, muallif emailini ommaviy almashtirish (--mailmap) — sir bo'lmasa ham tarix qayta yoziladi va bu darsdagi hamma oqibat amal qiladi.
  • Intervyu savollari: "Repo'ga parol tushib qoldi — qadamlaringiz?", "Nega git rm yetarli emas?", "Tarixni qayta yozgandan keyin hamkasblar nima qilishi kerak?"

Xulosa

  • git grep — joriy fayllar, git log -S — butun tarix. Sir "tuzatish" commitida ham to'liq ko'rinadi.
  • Tartib: rotatsiya → yangi klonda filter-repo → tekshirish → majburiy push → hamkasblar → GitHub Support.
  • git filter-repo --invert-paths --path <fayl> — faylni, --replace-text — matnni olib tashlaydi; sir uchun --sensitive-data-removal.
  • filter-repo yangi klon talab qiladi va oddiy rejimda origin ni o'chiradi — bu himoya.
  • Sir paydo bo'lgan commitdan keyingi hamma hash o'zgaradi; imzolar yo'qoladi.
  • Bitta eski klondan pull + push sirni qaytaradi; to'g'ri yo'l — qayta klon yoki rebase --onto.
  • Push protection faqat taniladigan formatlarni ushlaydi; o'ylab topilgan sirni .gitignore va skaner himoya qiladi.

Keyingi dars: Katta repolar: Git LFS, shallow clone va sparse-checkout — tarixda 30 versiyali video bo'lsa, klonni qanday tezlashtirish mumkin: --depth 1, --filter=blob:none, sparse-checkout va Git LFS.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Tarixni qayta yozish va sirlarni tozalash: git filter-repo, majburiy push va push protection — IlmHamroh