Chapter File Besar ditutup dengan satu kesimpulan: kalau aset besar memang bagian sah dari proyek, mengabaikannya bukan pilihan. Git LFS (Large File Storage) membagi kerjanya: repository tetap menyimpan sejarah lengkap, isi berkas besar pindah ke penyimpanan terpisah, dan yang masuk ke commit hanyalah berkas pointer berukuran puluhan byte. Clone jadi ringan, riwayat tetap utuh, dan video 700 MB tidak lagi diunduh setiap kali anggota tim baru masuk. Bab ini membedah mekanismenya dari pemasangan sampai migrasi riwayat yang terlanjur penuh.
Memasang Git LFS dan Filternya #
Git LFS adalah program terpisah dari Git; versinya terlihat dari git lfs version:
git lfs versionOutput yang Dihasilkan:
git-lfs/3.7.0 (GitHub; linux amd64; go 1.24.4; git 92dddf56)Perintah git lfs install menyambungkan Git LFS ke repository: keluarannya singkat tetapi efeknya dua lapis.
git init -b main latihan-lfs
cd latihan-lfs
git config user.name "Budi Santoso"
git config user.email "[email protected]"
git lfs install --localOutput yang Dihasilkan:
Updated Git hooks.
Git LFS initialized.Baris Updated Git hooks. berarti Git LFS menulis hook di .git/hooks — paling penting pre-push, yang mengunggah isi berkas LFS ke server sebelum git push berjalan. Baris Git LFS initialized. berarti konfigurasi filter dipasang; isi lengkapnya terlihat dengan git config --get-regexp '^filter\.lfs' | sort (diurutkan agar stabil):
filter.lfs.clean git-lfs clean -- %f
filter.lfs.process git-lfs filter-process
filter.lfs.required true
filter.lfs.smudge git-lfs smudge -- %fPasangan filter.lfs.clean dan filter.lfs.smudge adalah jantungnya: saat git add, clean mengubah isi berkas menjadi pointer; saat checkout, smudge mengembalikan isi asli. filter.lfs.process adalah mode komunikasi batch yang lebih cepat, dan filter.lfs.required true membuat Git gagal keras saat filter bermasalah — lebih aman daripada diam-diam menyimpan isi penuh.
Melacak Pola Berkas dengan git lfs track #
LFS tidak menebak berkas mana yang besar; kita mendaftarkan polanya, sama seperti pola .gitignore :
git lfs track "*.png"
cat .gitattributesOutput yang Dihasilkan:
Tracking "*.png"Output yang Dihasilkan (isi .gitattributes):
*.png filter=lfs diff=lfs merge=lfs -textGit LFS menulis aturan ke berkas .gitattributes — setiap *.png kini membawa empat atribut. filter=lfs mengaktifkan pasangan clean/smudge. diff=lfs membuat git diff menampilkan ringkasan pointer, bukan mencoba membedah biner. merge=lfs memasang driver merge khusus: konflik dua versi diselesaikan memilih salah satu isi utuh, bukan menyambung teks. -text mematikan normalisasi baris baru yang justru merusak berkas biner. Status atribut satu berkas bisa diverifikasi:
git check-attr filter diff merge text -- hero.pngOutput yang Dihasilkan:
hero.png: filter: lfs
hero.png: diff: lfs
hero.png: merge: lfs
hero.png: text: unsetPointer di Repository, Isi Asli di Penyimpanan LFS #
Mekanismenya kita uji. Berkas PNG 300.000 byte dibuat dari data acak berbenih tetap agar identik di mesin mana pun, lalu ditambahkan bersama .gitattributes:
python3 -c "import random; random.seed(2026); open('hero.png','wb').write(random.randbytes(300000))"
git add .gitattributes hero.png
git commit -m "Tambah gambar hero"Output yang Dihasilkan:
[main (root-commit) 668c6b8] Tambah gambar hero
2 files changed, 4 insertions(+)
create mode 100644 .gitattributes
create mode 100644 hero.pngHash commit di mesin pembaca akan berbeda karena bergantung pada waktu dan identitas. Yang menarik adalah isi hero.png menurut Git:
git cat-file -p :hero.pngOutput yang Dihasilkan:
version https://git-lfs.github.com/spec/v1
oid sha256:00505388913a71dc4d96c831c480a96f26240c8b0adbfb14c2040bbc4f640067
size 300000Tiga baris ini adalah berkas pointer. version menunjukkan spesifikasi formatnya. oid berisi hash SHA-256 dari isi asli — 300.000 byte acak dirangkum menjadi sidik jari 64 karakter. size mencatat ukuran asli sehingga alat bisa menampilkan “300 KB” tanpa mengunduh. git show HEAD:hero.png menghasilkan tiga baris yang sama: yang tersimpan di commit memang pointer, bukan gambar. Isi aslinya ada di penyimpanan lokal LFS di dalam .git:
find .git/lfs/objects -type f
git lfs ls-filesOutput yang Dihasilkan:
.git/lfs/objects/00/50/00505388913a71dc4d96c831c480a96f26240c8b0adbfb14c2040bbc4f640067Output yang Dihasilkan:
0050538891 * hero.pngPath objek memakai sharding dua-dua digit: dua karakter pertama oid (00) menjadi direktori, dua berikutnya (50) subdirektori, sisanya nama berkas. git lfs ls-files merangkum statusnya: potongan oid 0050538891 mengidentifikasi objek dan tanda * berarti isi asli ada di cache lokal. Bukti terakhir: hapus berkas fisik, checkout ulang — isi 300.000 byte pulih lewat filter smudge.
rm hero.png
git checkout -- hero.png
stat -c '%s %n' hero.pngOutput yang Dihasilkan:
300000 hero.png| Aspek | Tanpa LFS | Dengan LFS |
|---|---|---|
| Isi di commit | Blob penuh 300 KB | Pointer 131 byte |
| Revisi kedua berkas | Blob 300 KB baru | Pointer baru + satu objek LFS |
Ukuran git clone | Seluruh versi ikut | Pointer saja; isi diunduh sesuai kebutuhan |
git diff | “Binary files differ” | Ringkasan perubahan pointer |
Push dan Clone Mentransfer Objek LFS #
Bukti terpentingnya transfer antar repository. Kita buat gudang bare lokal sebagai pengganti server, sambungkan sebagai origin, lalu push:
git init --bare -b main ../gudang-lfs.git
git remote add origin ../gudang-lfs.git
git push -u origin mainOutput yang Dihasilkan:
To ../gudang-lfs.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.Keluaran push tampak biasa, tetapi hook pre-push bekerja diam-diam: sebelum Git mengirim commit, git lfs memeriksa objek LFS yang dirujuk dan menyalinnya ke gudang. Buktinya ada di dalam gudang:
find ../gudang-lfs.git/lfs/objects -type fOutput yang Dihasilkan:
../gudang-lfs.git/lfs/objects/00/50/00505388913a71dc4d96c831c480a96f26240c8b0adbfb14c2040bbc4f640067Objek dengan oid yang sama kini tersimpan di sisi server. Pada transfer lewat jaringan, git-lfs juga mencetak baris kemajuan seperti Uploading LFS objects: 100% (1/1), 300 KB | 1.2 MB/s, done. — pada transfer lokal baris itu kadang muncul kadang tidak karena salinan selesai seketika. Sekarang pihak lain meng-clone:
cd ..
git clone gudang-lfs.git klon-lfs
stat -c '%s %n' klon-lfs/hero.pngOutput yang Dihasilkan:
Cloning into 'klon-lfs'...
done.Output yang Dihasilkan:
131 hero.pngKlon berhasil, tetapi hero.png hanya berukuran 131 byte — pointer-nya. Klon segar memang belum mengaktifkan filter LFS, sehingga checkout menyimpan pointer apa adanya. Di mesin yang sudah menjalankan git lfs install global, smudge berjalan otomatis saat checkout. Di klon ini kita aktifkannya lokal lalu menarik isi:
cd klon-lfs
git lfs install --local
git lfs pull
stat -c '%s %n' hero.png
git lfs ls-filesOutput yang Dihasilkan:
Updated Git hooks.
Git LFS initialized.Perintah git lfs pull menyelesaikan tanpa keluaran; hasilnya terlihat dari dua perintah berikutnya:
300000 hero.png0050538891 * hero.pngIsi asli 300.000 byte pulih dari gudang lewat endpoint LFS, dan tanda * kembali hadir. Pada remote ber-path lokal, git-lfs menangani transfer lewat transfer agent standalone; server LFS sungguhan seperti milik GitHub dan GitLab berbicara lewat HTTPS di endpoint /info/lfs — mekanismenya sama, salurannya berbeda. Detail lengkap ada di situs resmi Git LFS
dan repositori git-lfs di GitHub
.
Migrasi Riwayat yang Terlanjur Penuh #
Bagaimana dengan repository yang sudah telanjur penuh blob besar? Repo latihan-migrasi meniru kondisi itu: banner 2 MB di-commit tanpa LFS, disusul satu commit README, dengan total object database 1,93 MiB. git lfs migrate import --everything menulis ulang seluruh riwayat: blob yang cocok pola diganti pointer, dan aturan .gitattributes ditambahkan otomatis.
git lfs migrate import --include="*.png" --everything
git log --onelineOutput yang Dihasilkan:
Sorting commits: ..., done.
Rewriting commits: 100% (2/2), done.
Updating refs: ..., done.
Checkout: ..., done.Output yang Dihasilkan:
ee0daa0 Tambah README
46ddeab Tambah banner tanpa LFSEmpat tahap keluaran pertama berjalan berurutan: mengurutkan commit terdampak, menulis ulang, memperbarui refs, lalu me-refresh working directory. Hash pada log kedua sudah berbeda dari commit asal — sifat penulisan ulang. Hasilnya bisa diverifikasi: git lfs ls-files menampilkan bc7b550702 * banner.png, git cat-file -p HEAD:banner.png menampilkan pointer ber-oid bc7b550702... berukuran 2.000.000 byte, dan git lfs checkout mencetak Checking out LFS objects: 100% (1/1), 2.0 MB | 0 B/s, done.
Namun git count-objects -vH langsung setelah migrasi masih menunjukkan sekitar 1,95 MiB — blob 2 MB lama belum terbuang! Objek itu kini tak terjangkau dari refs mana pun tetapi masih dilindungi reflog. Dua perintah housekeeping menghabisinya:
git reflog expire --expire-unreachable=now --all
git gc --prune=now
git count-objects -vHOutput yang Dihasilkan:
count: 0
size: 0 bytes
in-pack: 7
packs: 1
size-pack: 1.88 KiB
prune-packable: 0
garbage: 0
size-garbage: 0 bytesObject database menyusut dari 1,93 MiB menjadi 1,88 KiB. Pemindaian blob terbesar ala chapter File Besar
memastikan tak ada sisa: kandidat terbesar hanyalah pointer 132 banner.png.
Pointer di repository, isi di penyimpanan terpisah — repository kembali ramping walau asetnya besar. Meski begitu, clone tetap mengambil seluruh riwayat dan seluruh direktori; dua chapter berikutnya memangkas dua sisi itu, dimulai dari direktori mana yang layak hadir di working directory: Sparse Checkout .