Menghapus berkas dari proyek terdengar seperti urusan satu perintah rm — sampai git status memprotes dengan baris deleted yang terus menggantung tanpa pernah tercatat ke riwayat. Persoalannya: penghapusan adalah perubahan juga, dan setiap perubahan harus melewati index agar masuk commit. Perintah git rm mengerjakan dua lapisan itu sekaligus — menghapus berkas dari disk sekaligus mencatat penghapusannya di index. Chapter ini membedah perintahnya opsi per opsi, kontrasnya dengan rm biasa, jaring pengaman yang menolak menghapus pekerjaan yang belum ter-commit, hingga resep mengeluarkan berkas dari commit terakhir.

git rm: Dua Lapisan Sekaligus #

Repository latihan memuat enam berkas yang sudah ter-commit:

bash
mkdir latihan-rm && cd latihan-rm
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf '# Situs Toko Berkah\nberanda toko\n' > situs.md
printf 'daftar galeri produk\n' > galeri.md
printf 'catatan diskusi tim\n' > catatan.txt
mkdir aset
printf 'logo toko v1\n' > aset/logo.txt
printf 'draf konten belum final\n' > draf.txt
printf 'berkas kerja v1\n' > berkas.txt
git add situs.md galeri.md catatan.txt aset/logo.txt draf.txt berkas.txt
git commit -m "Susun awal situs toko"

Kita hapus galeri.md dengan git rm:

bash
git rm galeri.md

Output yang Dihasilkan:

text
rm 'galeri.md'

Baris rm 'galeri.md' adalah konfirmasi dua lapisan: berkas lenyap dari disk dan penghapusannya kini duduk di staging area. Dua pemeriksaan singkat membuktikannya:

bash
git status
ls galeri.md

Output yang Dihasilkan:

text
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    galeri.md
text
ls: cannot access 'galeri.md': No such file or directory

Status menampilkan deleted: galeri.md di bagian Changes to be committed — siap menjadi bagian commit berikutnya tanpa langkah tambahan, dan ls gagal karena berkas memang tidak lagi ada di disk.

Kontras: rm Biasa Mencatat Sebelah Sisi Saja #

rm biasa hanya menyentuh disk. Git melihat hasilnya sebagai perubahan belum staged:

bash
rm catatan.txt
git status

Output yang Dihasilkan:

text
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    galeri.md

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    catatan.txt

Penghapusan catatan.txt muncul di bagian Changes not staged for commit — Git tahu berkasnya hilang, tetapi keadaan itu belum dicatat ke index. Dari dua jalur penyelesaian — git add catatan.txt pun sah — kita pakai bentuk yang konsisten dengan topik:

bash
git rm catatan.txt

Output yang Dihasilkan:

text
rm 'catatan.txt'
bash
git status

Output yang Dihasilkan:

text
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    catatan.txt
	deleted:    galeri.md

Kini kedua penghapusan (catatan.txt dan galeri.md) berdampingan di staging. Inilah inti perbedaannya: rm adalah perintah sistem berkas yang kebetulan dipantau Git, sedangkan git rm adalah perintah Git yang mengoordinasikan index dan disk dalam satu langkah.

Opsi -r: Menghapus Direktori #

Penghapusan satu direktori beserta seluruh isinya butuh opsi rekursif — Git menolak git rm aset tanpa -r agar tidak menghapus massal tanpa sengaja:

bash
git rm -r aset
ls aset

Output yang Dihasilkan:

text
rm 'aset/logo.txt'
text
ls: cannot access 'aset': No such file or directory

Seluruh isi aset/ — dalam hal ini aset/logo.txt — dihapus dari index dan disk sekaligus. Kita bekukan ketiga penghapusan ini ke riwayat:

bash
git commit -m "Hapus galeri, catatan, dan aset"

Output yang Dihasilkan:

text
[main c26c6e0] Hapus galeri, catatan, dan aset
 3 files changed, 3 deletions(-)
 delete mode 100644 aset/logo.txt
 delete mode 100644 catatan.txt
 delete mode 100644 galeri.md

Opsi –cached: Tanpa Sentuhan Fisik #

Sudah kita pakai pada chapter Mengabaikan File yang Terlanjur Dilacak : --cached menghapus dari index saja, berkas fisik dibiarkan. Resep lengkapnya — pola dulu, --cached kemudian — kita ulang pada draf.txt:

bash
printf 'draf.txt\n' > .gitignore
git add .gitignore
git rm --cached draf.txt
git status
ls draf.txt

Output yang Dihasilkan:

text
rm 'draf.txt'
text
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   .gitignore
	deleted:    draf.txt
text
draf.txt

Penghapusan tercatat staged, .gitignore ikut ter-commit nanti, dan draf.txt tetap hidup di disk — bebas dari pandangan Git selamanya setelah commit ini:

bash
git commit -m "Hentikan pelacakan draf.txt"

Output yang Dihasilkan:

text
[main 82518a2] Hentikan pelacakan draf.txt
 2 files changed, 1 insertion(+), 1 deletion(-)
 create mode 100644 .gitignore
 delete mode 100644 draf.txt

Jaring Pengaman dan Opsi -f #

git rm tidak sembarangan menghapus. Berkas yang isi disknya berbeda dari HEAD dan dari index berarti menyimpan pekerjaan yang belum ter-commit di dua arah; Git menolak dan menawarkan jalan keluar:

bash
printf 'berkas kerja v2\n' > berkas.txt
git add berkas.txt
printf 'berkas kerja v3\n' > berkas.txt
git rm berkas.txt

Output yang Dihasilkan:

text
error: the following file has staged content different from both the
file and the HEAD:
    berkas.txt
(use -f to force removal)

Penolakan ini menyelamatkan kedua versi suntingan — v2 yang staged dan v3 yang hanya ada di disk — dari kepunahan. Bila memang sudah diputuskan untuk membuangnya semua, opsi -f melangkahi jaring pengaman:

bash
git rm -f berkas.txt
ls berkas.txt

Output yang Dihasilkan:

text
rm 'berkas.txt'
text
ls: cannot access 'berkas.txt': No such file or directory

Bentuk penolakan lain yang sering muncul: berkas yang diubah tanpa staging menghasilkan pesan has local modifications dengan saran --cached atau -f. Keduanya sengaja dibuat “berisik” supaya penghapusan destruktif selalu disadari.

Menghapus dari Commit Terakhir #

Skenario penutup: berkas salah masuk pada commit terakhir yang belum sempat di-push. Alih-alih commit pembersih baru, kita sunting commit itu — pola amend yang dibahas tuntas pada chapter Amend Commit . sementara.txt kita commit lebih dulu sebagai “kecelakaan”:

bash
printf 'catatan sementara tidak sengaja\n' > sementara.txt
git add sementara.txt
git commit -m "Tambah catatan sementara"

Keluarkan dari index tanpa menghapus fisiknya, tambahkan polanya, lalu lebur ulang commit terakhir:

bash
git rm --cached sementara.txt
printf 'sementara.txt\n' >> .gitignore
git add .gitignore
git commit --amend --no-edit

Output yang Dihasilkan:

text
[main d1a6fc0] Tambah catatan sementara
 Date: Mon Oct 26 11:10:00 2026 +0700
 2 files changed, 1 insertion(+), 1 deletion(-)
 delete mode 100644 berkas.txt

Baris Date: menandakan tanggal penulis asli dipertahankan oleh amend. Hasil akhirnya simetris dengan tujuan kita:

bash
git ls-files
ls sementara.txt

Output yang Dihasilkan:

text
.gitignore
situs.md
text
sementara.txt

git ls-files hanya memuat dua berkas yang memang dilacak — sementara.txt sudah tidak tercatat, meski berkasnya masih ada di disk dan status kini bersih:

bash
git status

Output yang Dihasilkan:

text
On branch main
nothing to commit, working tree clean

Perintah git rm menguasai ujung hidup berkas; perintah sejenis menunggu di ujung lainnya — ketika berkas tidak dihapus, hanya berpindah nama atau direktori. Lanjut ke git mv .