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:

text
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:

bash
uv tool install git-filter-repo
text
Resolved 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-repo

Berkas git-filter-repo berakhir di ~/.local/bin; selama direktori itu ada di PATH, Git otomatis mengenalinya sebagai subperintah git filter-repo:

bash
git filter-repo --version
text
a40bce548d2c

filter-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:

bash
git log --oneline --all
text
f9e0aab Perbarui kunci dan aplikasi
8d90089 Tambah modul aplikasi
7d370f0 Tambah konfigurasi
e1459ee Commit awal proyek

Hash di mesin pembaca berbeda — sandbox mematok tanggal dan identitas agar contoh ini dapat direproduksi. Dua commit menyentuh .env:

bash
git log --all --oneline -- .env
text
f9e0aab Perbarui kunci dan aplikasi
7d370f0 Tambah konfigurasi

Dan secret masih terbaca utuh dari commit yang menambahkannya:

bash
git show 7d370f0:.env
text
API_KEY=sk-live-YOUR_API_KEY

Pembersihannya satu baris — --path .env memilih berkasnya, --invert-paths membalik maknanya menjadi “pertahankan semuanya kecuali yang dipilih”:

bash
git filter-repo --path .env --invert-paths
text
NOTICE: 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.

bash
git log --oneline --all
text
0f7a6c5 Perbarui kunci dan aplikasi
7e6b32b Tambah modul aplikasi
e1459ee Commit awal proyek

Tambah 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:

bash
git log --all --oneline -- .env
text

Keluaran kosong: tidak ada satu pun ref yang memuat perubahan .env. Commit lama sudah tidak bisa dibaca — objek fisiknya ikut dimusnahkan oleh repacking:

bash
git show 7d370f0:.env
text
fatal: invalid object name '7d370f0'.

Pemastian terakhir dengan pickaxe -S: tidak ada commit yang pernah menambah atau menghapus string rahasianya:

bash
git log --all -S sk-live-YOUR_API_KEY --oneline
text

Origin 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 —

bash
git remote -v
text

Daftar remote kosong — origin hilang. Menutup siklus pembersihan berarti memasangnya kembali, lalu mengganti riwayat server dengan paksa:

bash
git remote add origin ../asal.git
git push origin --force --all
text
To ../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:

bash
git clone ../asal.git latihan-verifikasi
cd latihan-verifikasi
git show HEAD:.env
text
fatal: path '.env' does not exist in 'HEAD'
bash
git log --oneline
text
0f7a6c5 Perbarui kunci dan aplikasi
7e6b32b Tambah modul aplikasi
e1459ee Commit awal proyek

Clone 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:

OperasiPerintah intiKapan dipakai
Hapus berkas--path <berkas> --invert-pathsberkas 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:

bash
git show HEAD:konfigurasi.txt
text
KUNCI_API=sk-live-YOUR_API_KEY
MODE=produksi

--replace-text membaca berkas ekspresi berformat <cari>==><ganti>, satu aturan per baris:

bash
printf 'sk-live-YOUR_API_KEY==>***TERHAPUS***\n' > ganti.txt
git filter-repo --replace-text ganti.txt
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)
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:

bash
git filter-repo --replace-text ganti.txt --force
text
Parsed 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:

bash
git show HEAD:konfigurasi.txt
git show HEAD~1:konfigurasi.txt
text
KUNCI_API=***TERHAPUS***
MODE=produksi
text
server=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:

bash
git filter-repo --path-rename catatan/kunci-api.txt:docs/contoh-kunci.txt --force
text
Parsed 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:

bash
git log --all --oneline -- catatan/kunci-api.txt
git log --all --oneline -- docs/contoh-kunci.txt
text
text
0d64117 Perbarui catatan kunci
c4e7f9a Commit awal catatan

Direktori 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.