Tim platform memelihara pustaka utilitas di repository sendiri, sementara aplikasi toko memakai pustaka itu dan ingin memastikan setiap developer — serta pipeline build — membangun dengan versi yang sama persis. Menyalin kode pustaka ke setiap aplikasi berarti pembaruan manual; menjadikannya satu repository berarti riwayat dua proyek berbeda irama tercampur. Submodule menawari jalan tengah: repository pustaka hadir sebagai direktori di dalam repository aplikasi, dan yang dicatat aplikasi hanyalah satu commit pustaka — sematan yang berganti hanya saat kita sengaja menaikkannya. Bab ini menyematkan, meng-clone, memperbarui, dan melepas submodule sungguhan, lengkap dengan dua jebakan yang paling sering menjatuhkan orang.
Dua Repository: Pustaka dan Aplikasi #
Sandbox menyediakan dua repository lokal yang berdiri bersebelahan:
mkdir latihan-pustaka && cd latihan-pustaka
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'pustaka utilitas toko v1\n' > README.md
printf 'def sapa():\n return "halo"\n' > util.py
git add -A
git commit -m "Rilis pustaka v1"Aplikasi dibuat dengan cara yang sama di direktori latihan-aplikasi, berisi satu README.md dan commit “Awal aplikasi”. Keduanya adalah repository Git biasa tanpa hal khusus — sifat submodule muncul ketika keduanya dijahit.
Pagar Keamanan Protokol file #
Mencoba langsung menyematkan pustaka ber-path lokal menghasilkan kegagalan yang informatif:
git submodule add ../latihan-pustaka libs/pustakaOutput yang Dihasilkan:
Cloning into '/home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/latihan-aplikasi/libs/pustaka'...
fatal: transport 'file' not allowed
fatal: clone of '/home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/latihan-pustaka' into submodule path '/home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/latihan-aplikasi/libs/pustaka' failedJalur pada keluaran di atas mengikuti lokasi sandbox dan akan menyesuaikan di mesin pembaca. Sejak Git 2.38.1, pengklonan submodule melalui protokol file ditolak secara bawaan — penutupan celah keamanan terhadap berkas .gitmodules berbahaya yang bisa memerintahkan Git membaca path lokal sewenang-wenang. Sandbox yang memang memakai path lokal melewati pagar ini dengan izin eksplisit sekali jalan:
git -c protocol.file.allow=always submodule add ../latihan-pustaka libs/pustakaOutput yang Dihasilkan:
Cloning into '/home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/latihan-aplikasi/libs/pustaka'...
done.Pada remote HTTPS sungguhan — kasus normal di GitHub atau GitLab — pagar ini tidak pernah muncul.
Menyematkan: .gitmodules dan Gitlink Mode 160000 #
Perintah add melakukan tiga hal sekaligus: meng-clone pustaka ke libs/pustaka, menulis berkas .gitmodules, dan menambahkan satu entri khusus ke index. Isi .gitmodules pantas dibaca utuh:
[submodule "libs/pustaka"]
path = libs/pustaka
url = ../latihan-pustakaBerkas ini adalah peta bagi siapa pun yang meng-clone aplikasi: nama submodule, lokasinya, dan dari mana mengambilnya. URL relatif seperti ../latihan-pustaka akan di-resolve terhadap URL remote superproject — pola yang nyaman untuk repositori bersaudara di satu server. Sementara itu, index menyimpan sesuatu yang berbeda dari semua bab sebelumnya:
git ls-files -sOutput yang Dihasilkan:
100644 7d5ad918cf4c152856cf01bdea1f5cba664c72c3 0 .gitmodules
100644 eb8c99953f0d821386a0e160a1f95a2eeb2e85eb 0 README.md
160000 caef2a69e2a77a104cf0289d218c7d28b0ae6d42 0 libs/pustakaDua baris pertama adalah pola lama: mode 100644, hash blob, stage. Baris ketiga memakai mode 160000 — kode khusus yang oleh Git disebut gitlink: hash yang dirujuk bukan blob di repository aplikasi, melainkan commit di repository pustaka. Pohon commit memperlihatkan hal yang sama dari sisi objek:
git ls-tree HEAD libs/pustakaOutput yang Dihasilkan:
160000 commit caef2a69e2a77a104cf0289d218c7d28b0ae6d42 libs/pustakaHash akan berbeda di mesin pembaca, tetapi maknanya tetap: superproject menyematkan pustaka pada commit caef2a6 — bukan pada branch, bukan pada “terbaru”, melainkan satu titik pasti. Status submodule meringkasnya:
caef2a69e2a77a104cf0289d218c7d28b0ae6d42 libs/pustaka (heads/main)Spasi di depan berarti sematan bersih; git submodule status menempatkan tanda + bila checkout submodule lebih baru dari sematan dan - bila belum diinisialisasi. Satu detail arsitektur lagi: .git di dalam direktori submodule bukan direktori, melainkan berkas penunjuk:
gitdir: ../../.git/modules/libs/pustakaRepository pustaka sesungguhnya hidup di .git/modules/ milik superproject — trik yang sama yang akan kita temui lagi di git worktree
. Setelah semuanya siap, sematan di-commit:
git add .gitmodules
git commit -m "Sematkan pustaka"Output yang Dihasilkan:
[main d9adeca] Sematkan pustaka
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 libs/pustakaBaris create mode 160000 menegaskan yang di-commit adalah gitlink, bukan isi pustaka.
Clone: Kosong Sampai Diinisialisasi #
Apa yang diterima orang yang meng-clone aplikasi? Git sengaja tidak mengunduh submodule secara otomatis:
cd ..
git clone latihan-aplikasi klon-aplikasi
ls klon-aplikasi
ls klon-aplikasi/libs/pustakaOutput yang Dihasilkan:
README.md
libs
ls libs/pustaka:Direktori libs hadir tetapi kosong. Perintah satu arus membangunkannya:
cd klon-aplikasi
git submodule update --initOutput yang Dihasilkan:
Submodule 'libs/pustaka' (/home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/latihan-pustaka) registered for path 'libs/pustaka'
Cloning into '/home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/klon-aplikasi/libs/pustaka'...
done.
Submodule path 'libs/pustaka': checked out 'caef2a69e2a77a104cf0289d218c7d28b0ae6d42'Flag --init mendaftarkan submodule dari .gitmodules sekali gus sebelum meng-clone dan checkout sematan caef2a6. Klon baru sejak awal juga bisa melakukannya sekaligus: git clone --recurse-submodules latihan-aplikasi klon-rekursif menjalankan urutan yang sama selama proses klon, dan ls klon-rekursif/libs/pustaka menampilkan README.md serta util.py langsung tersedia.
Satu kejutan menanti siapa yang masuk ke dalam submodule:
git -C libs/pustaka statusOutput yang Dihasilkan:
HEAD detached at caef2a6
nothing to commit, working tree cleanHEAD detached adalah perilaku yang disengaja: sematan menunjuk commit, bukan branch, sehingga checkout submodule memang berdiri di titik lepas. Kita bebas berpindah branch di dalamnya, tetapi status default superproject selalu membaca commit mana yang sedang dicheckout.
Memperbarui Sematan #
Pustaka melaju: upstream merilis v2 dengan README baru di latihan-pustaka. Aplikasi menaikkan sematannya dari dalam klon:
cd ../klon-rekursif
git -C libs/pustaka switch main
git -C libs/pustaka pull
git statusOutput yang Dihasilkan (switch):
Switched to branch 'main'
Your branch is up to date with 'origin/main'.Output yang Dihasilkan (pull):
From /home/z/my-project/tutorial-extracted/work/sandbox/mengelola-file-dan-repository/part-b/latihan-pustaka
caef2a6..2f204ba main -> origin/main
Updating caef2a6..2f204ba
Fast-forward
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)Output yang Dihasilkan (status):
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: libs/pustaka (new commits)
no changes added to commit (use "git add" and/or "git commit -a")Frasa libs/pustaka (new commits) adalah cara Git berkata: checkout submodule kini berdiri di commit lain, dan superproject menawarkan pencatatan itu sebagai perubahan. Penawaran diterima seperti perubahan biasa:
git add libs/pustaka
git commit -m "Naikkan pustaka ke v2"
git ls-files -s libs/pustakaOutput yang Dihasilkan:
[main 88d53ee] Naikkan pustaka ke v2
1 file changed, 1 insertion(+), 1 deletion(-)Output yang Dihasilkan (ls-files -s):
160000 2f204baa1f7752ae293617dcb8fb3fff5f89cfcb 0 libs/pustakaGitlink bergerak dari caef2a6 ke 2f204ba — seluruh “pembaruan dependensi” di superproject sesungguhnya hanya mengganti satu baris sematan.
Melepas Submodule #
Melepas dilakukan dua tahap supaya bersih. git submodule deinit mengosongkan direktori dan mencabut pendaftaran:
git submodule deinit -f libs/pustakaOutput yang Dihasilkan:
Cleared directory 'libs/pustaka'
Submodule 'libs/pustaka' (../latihan-pustaka) unregistered for path 'libs/pustaka'Lalu git rm menghapus gitlink dari index beserta berkas .gitmodules dan sisa metadata di .git/modules:
git rm -f libs/pustaka
git status --shortOutput yang Dihasilkan:
rm 'libs/pustaka'Output yang Dihasilkan (status):
M .gitmodules
D libs/pustakaPenghapusan tinggal di-commit seperti biasa. Tanpa git rm, sisa direktori .git/modules/libs/pustaka dan entri submodule.libs/pustaka.* di konfigurasi lokal akan mengganggu penyematan ulang di masa depan — Pro Git bab Git Tools
menyarankan urutan deinit-rm-commit ini, dan dokumentasi git submodule
merinci tiap langkahnya.
Jebakan Klasik Submodule #
Submodule juga bukan manajer dependensi: tidak ada batasan versi, tidak ada resolusi konflik otomatis antar dua sematan, dan setiap pembaruan adalah commit tersendiri di superproject. Bila kebutuhan hanya berbagi kode, registri paket bahasa atau sparse checkout di atas monorepo sering lebih sederhana.
Submodule menjahit banyak repository menjadi satu pohon direktori. Teknik penutup bab section ini bekerja ke arah sebaliknya — satu repository dibuka menjadi banyak direktori kerja sekaligus: git worktree .