Chapter sebelumnya menutup dengan janji yang kini harus dibayar: Menghapus Secret dari Riwayat
hanya sanggup membersihkan kasus ringan dengan --amend, sementara secret yang sudah tertanam puluhan commit ke dalam butuh alat yang menulis ulang seluruh riwayat sekaligus. git filter-repo adalah alat itu — bahkan dokumentasi resmi git filter-branch menyarankannya sebagai pengganti. Chapter ini memasangnya, menjalankan pembersihan nyata pada riwayat yang kotor, memverifikasi hasil, lalu menutup dengan dua operasi umum lainnya: mengganti nama berkas dan mengganti isi teks di seluruh riwayat.
Kenapa filter-repo, Bukan filter-branch #
Git inti memang sudah membawa alat penulisan-ulang bernama git filter-branch, tetapi alat tersebut resmi dianggap usang
: dokumentasinya sendiri memperingatkan masalah keamanan — mudah salah pakai sampai merusak repository — dan sangat lambat karena menjalankan satu proses Git per commit. filter-repo, yang dikembangkan di github.com/newren/git-filter-repo
, menulis ulang riwayat dengan satu proses Python yang cepat, aman dari jebakan klasik filter-branch, dan menjaga keamanannya lewat pemeriksaan “fresh clone” yang akan kita lihat langsung. Alat ini bukan bagian Git inti — dipasang sebagai paket Python terpisah (butuh Git ≥ 2.22.0, Python 3 ≥ 3.5).
Memasang git-filter-repo #
Tiga jalur pemasangan lazim: pip install --user git-filter-repo, pipx install git-filter-repo, atau uv tool install git-filter-repo. Mesin sandbox mencoba jalur pertama dan ditolak — Python sistemnya menjalankan kebijakan PEP 668 untuk melindungi paket distro:
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
(dipotong — saran venv dan pipx dari pesan galat)
hint: See PEP 668 for the detailed specification.Penolakan seperti ini wajar di Debian/Ubuntu baru; pipx dan uv keluar dari kebijakan itu dengan lingkungan virtual tersendiri. Sandbox memakai uv:
uv tool install git-filter-repoResolved 1 package in 462ms
Prepared 1 package in 246ms
Installed 1 package in 3ms
+ git-filter-repo==2.47.0
Installed 1 executable: git-filter-repoBerkas git-filter-repo berakhir di ~/.local/bin; selama direktori itu ada di PATH, Git otomatis mengenalinya sebagai subperintah git filter-repo:
git filter-repo --versiona40bce548d2cfilter-repo melaporkan versinya sebagai pengidentifikasi commit, bukan nomor rilis — string ini berbeda antar build.
Demo: Menghapus .env dari Seluruh Riwayat #
Sandbox menyiapkan keadaan paling realistis: sebuah “server” lokal asal.git yang riwayatnya sudah kotor — .env masuk di commit kedua, lalu isinya diganti di commit keempat — dan kita mulai dari fresh clone-nya, latihan-klon. Empat commit di dalamnya:
git log --oneline --allf9e0aab Perbarui kunci dan aplikasi
8d90089 Tambah modul aplikasi
7d370f0 Tambah konfigurasi
e1459ee Commit awal proyekHash di mesin pembaca berbeda — sandbox mematok tanggal dan identitas agar contoh ini dapat direproduksi. Dua commit menyentuh .env:
git log --all --oneline -- .envf9e0aab Perbarui kunci dan aplikasi
7d370f0 Tambah konfigurasiDan secret masih terbaca utuh dari commit yang menambahkannya:
git show 7d370f0:.envAPI_KEY=sk-live-YOUR_API_KEYPembersihannya satu baris — --path .env memilih berkasnya, --invert-paths membalik maknanya menjadi “pertahankan semuanya kecuali yang dipilih”:
git filter-repo --path .env --invert-pathsNOTICE: Removing 'origin' remote; see 'Why is my origin removed?'
in the manual if you want to push back there.
(was /home/z/my-project/tutorial-extracted/work/sandbox/keamanan-dan-akses/part-b/asal.git)
Parsed 4 commits
New history written in 0.02 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at 0f7a6c5 Perbarui kunci dan aplikasi
Completely finished after 0.04 seconds.Jalur asal.git pada keluaran menyesuaikan lokasi direktori sandbox. Tiga hal penting terjadi dalam sepersekian detik itu. Pertama, empat commit diparse dan ditulis ulang tanpa .env. Kedua, filter-repo menghapus remote origin — sengaja, dengan NOTICE; alasan keamanannya dibedah dua section di bawah. Ketiga, repacking dan pembersihan objek lama berlangsung otomatis — tidak perlu reflog expire + gc --prune=now manual seperti pada amend
.
Satu efek samping yang mengejutkan pemula: jumlah commit berkurang dari empat menjadi tiga.
git log --oneline --all0f7a6c5 Perbarui kunci dan aplikasi
7e6b32b Tambah modul aplikasi
e1459ee Commit awal proyekTambah konfigurasi lenyap karena setelah .env dibuang, commit itu menjadi kosong — tidak lagi mengubah apa pun — dan filter-repo memangkas commit kosong secara bawaan. Opsi --prune-empty never mempertahankannya bila penomoran commit harus utuh. Perhatikan juga hash yang tidak berubah: e1459ee tetap e1459ee karena isi dan induknya tidak tersentuh; Tambah modul aplikasi dan Perbarui kunci dan aplikasi di-hash ulang karena induk (dan isi) mereka berubah.
Tiga Pemeriksaan Hasil #
Riwayat mana pun tidak lagi menyentuh .env:
git log --all --oneline -- .envKeluaran kosong: tidak ada satu pun ref yang memuat perubahan .env. Commit lama sudah tidak bisa dibaca — objek fisiknya ikut dimusnahkan oleh repacking:
git show 7d370f0:.envfatal: invalid object name '7d370f0'.Pemastian terakhir dengan pickaxe -S: tidak ada commit yang pernah menambah atau menghapus string rahasianya:
git log --all -S sk-live-YOUR_API_KEY --onelineOrigin Dihapus — dan Langkah Pasca #
NOTICE di awal keluaran bukan kecelakaan: filter-repo tidak ingin hasil setengah jalan terdorong balik ke tempat yang sama, sehingga ia memutus jalannya —
git remote -vDaftar remote kosong — origin hilang. Menutup siklus pembersihan berarti memasangnya kembali, lalu mengganti riwayat server dengan paksa:
git remote add origin ../asal.git
git push origin --force --allTo ../asal.git
+ f9e0aab...0f7a6c5 main -> main (forced update)Di sinilah --force polos — bukan --force-with-lease — justru tepat: riwayat lama di server adalah benda yang sengaja kita buang. Kalau ada branch terproteksi, admin melonggarkannya sementara. Setelah push, buat clone baru untuk membuktikan server benar-benar bersih:
git clone ../asal.git latihan-verifikasi
cd latihan-verifikasi
git show HEAD:.envfatal: path '.env' does not exist in 'HEAD'git log --oneline0f7a6c5 Perbarui kunci dan aplikasi
7e6b32b Tambah modul aplikasi
e1459ee Commit awal proyekClone baru mewarisi riwayat bersih. Sisa pekerjaannya bukan lagi urusan Git: seluruh tim meninggalkan clone lama (satu merge dari clone lama mengembalikan riwayat kotor) dan kredensial tetap dirotasi — kerangka delapan langkahnya ada di Menghapus Secret dari Riwayat .
Tiga Operasi Umum #
Tiga operasi yang paling sering dipakai:
| Operasi | Perintah inti | Kapan dipakai |
|---|---|---|
| Hapus berkas | --path <berkas> --invert-paths | berkas rahasia harus musnah total |
| Ganti isi teks | --replace-text <berkas-ekspresi> | berkas tetap hidup, isinya disanitasi |
| Ganti nama | --path-rename <lama>:<baru> | nama berkas/direktori diganti massal |
Mengganti Isi Teks dengan –replace-text #
Kadang berkasnya sah — konfigurasi, log, dokumen — dan hanya nilainya yang bocor. Repo latihan kedua, latihan-replace, memuat KUNCI_API=sk-live-YOUR_API_KEY di dua commit terakhirnya:
git show HEAD:konfigurasi.txtKUNCI_API=sk-live-YOUR_API_KEY
MODE=produksi--replace-text membaca berkas ekspresi berformat <cari>==><ganti>, satu aturan per baris:
printf 'sk-live-YOUR_API_KEY==>***TERHAPUS***\n' > ganti.txt
git filter-repo --replace-text ganti.txtAborting: 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)
Please operate on a fresh clone instead. If you want to proceed
anyway, use --force.Inilah pemeriksaan keamanan yang dijanjikan: repo kedua dibangun lokal — bukan hasil clone — reflog-nya panjang, dan filter-repo menolak jalan. Kerjakan di fresh clone, atau nyatakan sadar risiko dengan --force:
git filter-repo --replace-text ganti.txt --forceParsed 3 commits
New history written in 0.01 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at a177adc Rapikan konfigurasi
Completely finished after 0.03 seconds.Hasilnya dibaca dari dua commit berurutan, keduanya sudah sanitasi:
git show HEAD:konfigurasi.txt
git show HEAD~1:konfigurasi.txtKUNCI_API=***TERHAPUS***
MODE=produksiserver=example.com
KUNCI_API=***TERHAPUS***git log --all -S sk-live-YOUR_API_KEY --oneline kini kosong. Prefiks literal: (bawaan), regex:, dan glob: di tiap baris mengatur cara pencocokannya — pola seperti regex:KUNCI_API=[a-zA-Z0-9_-]+ menangkap nilai yang berubah-ubah antar commit.
Mengganti Nama dengan –path-rename #
Repo ketiga, latihan-ganti-nama, menyimpan catatan di catatan/kunci-api.txt. Filter ini tidak menghapus apa pun — ia memindahkan seluruh jejak berkas ke jalur baru:
git filter-repo --path-rename catatan/kunci-api.txt:docs/contoh-kunci.txt --forceParsed 2 commits
New history written in 0.01 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at 0d64117 Perbarui catatan kunci
Completely finished after 0.03 seconds.Verifikasinya simetris — nama lama tanpa jejak, nama baru memuat keduanya:
git log --all --oneline -- catatan/kunci-api.txt
git log --all --oneline -- docs/contoh-kunci.txt0d64117 Perbarui catatan kunci
c4e7f9a Commit awal catatanDirektori juga bisa diganti massal dengan pola lama/:baru/; hash pada keluaran di atas berbeda di mesin pembaca.
Riwayat yang bersih menutup satu kekhawatiran; kekhawatiran berikutnya menyangkut keaslian — siapa yang benar-benar menulis commit itu, dan apakah isinya pernah diubah diam-diam. Dua pertanyaan itu dijawab tanda tangan digital pada commit, pokok bahasan chapter berikutnya.