Struktur proyek hampir tidak pernah diam: berkas berganti nama, modul dipindah ke direktori baru, folder dirombak saat proyek membesar. Dengan mv biasa, git status
membelah satu kejadian menjadi dua — path lama tercatat deleted, path baru tampil untracked — dan keduanya harus kita satukan sendiri sebelum commit. Perintah git mv mengerjakan dua lapisan itu sekaligus: memindahkan berkas di disk sekaligus memperbarui index. Kejutan justru tersimpan di balik layar: Git tidak pernah mencatat kata “rename” di objeknya — status renamed adalah kesimpulan dari kemiripan isi yang dihitung ulang setiap kali ditampilkan. Chapter ini membedah perintahnya, membongkarnya menjadi mv + git rm --cached + git add, mengatur deteksinya lewat -M, lalu menelusuri riwayat lintas rename dengan git log --follow.
git mv: Satu Perintah untuk Dua Lapisan #
Repository latihan menyiapkan tiga berkas dalam tiga commit; beranda.txt sengaja punya dua versi supaya riwayatnya kelihatan panjang:
mkdir latihan-mv && cd latihan-mv
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'beranda toko v1\nsalam pembeli\n' > beranda.txt
printf 'galeri baris 1\nbaris 2\nbaris 3\nbaris 4\nbaris 5\nbaris 6\nbaris 7\n' > galeri.txt
git add beranda.txt galeri.txt
git commit -m "Buat beranda dan galeri toko"
printf 'beranda toko v2\nsalam pembeli dan penawaran\n' > beranda.txt
git add beranda.txt
git commit -m "Perbarui beranda"
printf 'kontak toko\n' > kontak.txt
git add kontak.txt
git commit -m "Tambah kontak"Kita ganti nama beranda.txt menjadi utama.txt:
git mv beranda.txt utama.txt
git statusOutput yang Dihasilkan:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: beranda.txt -> utama.txtPerpindahan langsung duduk di bagian Changes to be committed sebagai renamed: beranda.txt -> utama.txt — siap di-commit tanpa satu langkah pun tambahan. Perintah git mv sendiri tidak mencetak apa pun ketika berhasil; konfirmasinya kita baca dari status. Format ringkas memperlihatkan kodenya:
git status --shortOutput yang Dihasilkan:
R beranda.txt -> utama.txtKode R menempati kolom status staged — bukan D plus ?? yang akan kita lihat sebentar lagi. Dua hal dikerjakan git mv secara diam-diam: sistem berkas mengganti nama berkasnya, dan index membuang entri beranda.txt lalu menambahkan entri utama.txt yang menunjuk blob yang sama.
Kontras: mv Biasa Membelah Status #
mv biasa hanya menyentuh disk. Git membandingkan index dengan disk dan melihat dua kejadian terpisah:
mv kontak.txt telepon.txt
git statusOutput yang Dihasilkan:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: beranda.txt -> utama.txt
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: kontak.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
telepon.txtStatus kini memuat tiga kelompok: renamed: beranda.txt -> utama.txt dari langkah sebelumnya, deleted: kontak.txt di bagian Changes not staged, dan telepon.txt sebagai untracked. Perpindahan yang sama, tapi setengah pekerjaan: penghapusan kontak.txt dari index belum dicatat dan telepon.txt belum dikenal Git.
Membongkar git mv: mv + git rm –cached + git add #
git mv adalah kepraktian, bukan mekanisme penyimpanan baru. Perintah itu ekuivalen dengan tiga langkah yang bisa kita kerjakan tangan sendiri: mv untuk disk, git rm --cached untuk membuang entri lama dari index (pola dari chapter git rm
), dan git add untuk mendaftarkan path baru:
git rm --cached kontak.txt
git add telepon.txt
git statusOutput yang Dihasilkan:
rm 'kontak.txt'Output yang Dihasilkan:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: kontak.txt -> telepon.txt
renamed: beranda.txt -> utama.txtStatus staged kini menampilkan renamed: kontak.txt -> telepon.txt — identik dengan hasil git mv pada beranda.txt, padahal dikerjakan tanpa git mv sama sekali. Kedua jalur berakhir di commit yang sama:
git commit -m "Pindahkan beranda dan kontak"Output yang Dihasilkan:
[main b5180c9] Pindahkan beranda dan kontak
2 files changed, 0 insertions(+), 0 deletions(-)
rename kontak.txt => telepon.txt (100%)
rename beranda.txt => utama.txt (100%)Ringkasan commit menampilkan dua baris rename ... (100%) — termasuk pasangan kontak.txt => telepon.txt yang kita rakit manual. Inilah bukti bahwa label rename bukan data yang disimpan: commit hanya merekam “path kontak.txt hilang, path telepon.txt muncul dengan blob identik”, lalu Git menyimpulkan rename karena kemiripan isinya 100%. Hash b5180c9 milik sandbox — nilai di mesin pembaca menyesuaikan.
Deteksi Rename di diff: -M dan similarity index #
Kasus yang lebih realistis: berkas di-rename sekaligus disunting. Kita ubah satu baris galeri.txt, lalu pindahkan namanya sebelum commit:
printf 'galeri baris 1\nbaris 2 UBAH\nbaris 3\nbaris 4\nbaris 5\nbaris 6\nbaris 7\n' > galeri.txt
git add galeri.txt
git mv galeri.txt arsip-galeri.txt
git add arsip-galeri.txt
git statusOutput yang Dihasilkan:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: galeri.txt -> arsip-galeri.txtStatus tetap menyebut renamed meski isinya sudah berubah. Rinciannya terbuka di git diff
:
git diff --cachedOutput yang Dihasilkan:
diff --git a/galeri.txt b/arsip-galeri.txt
similarity index 80%
rename from galeri.txt
rename to arsip-galeri.txt
index 2f94ac0..37fc0cc 100644
--- a/galeri.txt
+++ b/arsip-galeri.txt
@@ -1,5 +1,5 @@
galeri baris 1
-baris 2
+baris 2 UBAH
baris 3
baris 4
baris 5Mari kita bedah baris demi baris:
similarity index 80%— enam dari tujuh baris identik; Git menilai kemiripan isi 80%, jauh di atas ambang bawaan 50%, sehingga pasangan ini tetap dilaporkan sebagai rename.rename from galeri.txt/rename to arsip-galeri.txt— pasangan path yang ditalikan deteksi.@@ -1,5 +1,5 @@— satu-satunya hunk isi:-baris 2diganti+baris 2 UBAH.
Deteksi rename ini diaktifkan lewat opsi -M — sinonimnya --find-renames — dan sejak Git 2.9 opsi itu menjadi perilaku bawaan git diff dan git status:
git diff --cached -M
git diff --cached --no-renamesOutput yang Dihasilkan:
diff --git a/galeri.txt b/arsip-galeri.txt
similarity index 80%
rename from galeri.txt
rename to arsip-galeri.txt
index 2f94ac0..37fc0cc 100644
--- a/galeri.txt
+++ b/arsip-galeri.txt
@@ -1,5 +1,5 @@
galeri baris 1
-baris 2
+baris 2 UBAH
baris 3
baris 4
baris 5Output yang Dihasilkan:
diff --git a/arsip-galeri.txt b/arsip-galeri.txt
new file mode 100644
index 0000000..37fc0cc
--- /dev/null
+++ b/arsip-galeri.txt
@@ -0,0 +1,7 @@
+galeri baris 1
+baris 2 UBAH
+baris 3
+baris 4
+baris 5
+baris 6
+baris 7
diff --git a/galeri.txt b/galeri.txt
deleted file mode 100644
index 2f94ac0..0000000
--- a/galeri.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-galeri baris 1
-baris 2
-baris 3
-baris 4
-baris 5
-baris 6
-baris 7Blok pertama identik dengan diff tanpa opsi — -M kini hanya menegaskan yang sudah aktif; bentuk -M90% menaikkan ambang kemiripan menjadi 90% sehingga rename yang “terlalu banyak berubah” tak lagi ditalikan. Blok kedua mematikan deteksi: perpindahan tampil sebagai pasangan new file + deleted file — persis seperti yang tersimpan di snapshot.
| Opsi diff | Perilaku |
|---|---|
| tanpa opsi | deteksi rename aktif — bawaan sejak Git 2.9 |
-M, --find-renames | menyalakan deteksi eksplisit; -M90% mengubah ambang kemiripan |
--no-renames | mematikan deteksi: rename tampil sebagai add + delete |
Rename Lintas Direktori #
Perpindahan ke direktori lain tidak membutuhkan opsi khusus — path barunya saja yang lebih panjang. Direktori tujuan harus sudah ada; git mv tidak membuatnya:
mkdir arsip
git mv arsip-galeri.txt arsip/galeri.txt
git statusOutput yang Dihasilkan:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: arsip-galeri.txt -> arsip/galeri.txtStatus menampilkan renamed: arsip-galeri.txt -> arsip/galeri.txt — path di dalam arsip/ diperlakukan sama seperti rename sederhana, karena memang hanya string path yang berubah. Kita bekukan dan lanjut ke pertanyaan yang lebih menarik:
git commit -m "Rapikan galeri ke direktori arsip"Menelusuri Riwayat Lintas Rename: git log –follow #
git log pada sebuah path hanya mencari commit yang menyentuh path persis itu. Setelah tiga kali berpindah, arsip/galeri.txt hanya mengenal satu commit:
git log --oneline arsip/galeri.txtOutput yang Dihasilkan:
fd7ff69 Rapikan galeri ke direktori arsipOpsi --follow menautkan riwayat mundur melewati rename — git log
berjalan dari commit paling baru, mendeteksi rename di tiap langkah, lalu melanjutkan pencarian dengan path lama:
git log --oneline --follow arsip/galeri.txtOutput yang Dihasilkan:
fd7ff69 Rapikan galeri ke direktori arsip
5e24c6b Pindahkan galeri dengan suntingan
06cb5e9 Buat beranda dan galeri tokoTiga commit muncul: Rapikan galeri ke direktori arsip, Pindahkan galeri dengan suntingan (saat masih bernama arsip-galeri.txt), sampai Buat beranda dan galeri toko (saat masih galeri.txt). Satu batasan kerap menjegal: --follow hanya menerima tepat satu pathspec:
git log --oneline --follow utama.txt arsip/galeri.txtOutput yang Dihasilkan:
fatal: --follow requires exactly one pathspecRename adalah kesimpulan dari isi, bukan catatan yang disimpan — dan selama isinya cukup mirip, Git menautkan riwayat itu otomatis, dengan atau tanpa git mv. Metadata berikutnya justru kebalikannya: satu bit permission yang berubah tanpa satu byte isi pun ikut berubah, dan tetap memicu diff. Lanjut ke File Mode dan Permission
.