Struktur Repositori Git: Panduan Teknis Lengkap
Git adalah sistem kontrol versi terdistribusi yang menyimpan riwayat proyek sebagai directed acyclic graph (DAG) dari objek snapshot yang tidak dapat diubah. Setiap repositori Git dibangun dari tiga zona logis — direktori kerja, staging index, dan penyimpanan objek di dalam .git/ — ditambah sekumpulan pointer ringan (branch, tag, remote) yang menavigasi riwayat tersebut. Memahami bagaimana lapisan-lapisan ini berinteraksi adalah perbedaan antara menggunakan Git secara mekanis dan menggunakannya dengan presisi tinggi.
Jika Anda meng-host repositori sendiri di sebuah VPS, menguasai struktur internal ini memungkinkan Anda memulihkan dari bencana, merancang pipeline CI/CD yang efisien, dan mengaudit setiap byte riwayat proyek Anda tanpa bergantung pada platform pihak ketiga.
Model Tiga Zona: Cara Git Memindahkan Data
Sebelum menyelami komponen individual, pahami model aliran data yang mengatur setiap operasi Git:
Working Directory --> Staging Area (Index) --> .git/ Object Store
(edit) (git add) (git commit)Perubahan bergerak dari kiri ke kanan saat Anda membangun commit, dan dari kanan ke kiri saat Anda memulihkan atau mereset. Setiap perintah Git pada dasarnya adalah operasi baca atau tulis pada satu atau lebih zona ini.
Direktori Kerja
Direktori kerja (juga disebut working tree) adalah tampilan filesystem proyek Anda pada kondisi checkout tertentu. Saat Anda menjalankan git clone atau git checkout, Git merekonstruksi file dari objek terkompresi di .git/objects/ dan menulisnya ke direktori ini.
File dalam direktori kerja berada dalam salah satu dari empat kondisi:
- Untracked — Git belum pernah melihat file ini; file ini hanya ada di disk.
- Tracked, unmodified — file cocok dengan snapshot commit terakhir secara persis.
- Tracked, modified — file berbeda dari snapshot commit terakhir tetapi belum di-stage.
- Tracked, deleted — file telah dihapus dari disk tetapi penghapusannya belum di-stage.
Nuansa penting yang sering membingungkan banyak developer: direktori kerja bukan salinan sederhana dari repositori. Git merekonstruksinya dengan membaca objek tree dan mendekompresi objek blob. Jika .git/ utuh, Anda selalu dapat meregenerasi direktori kerja dari awal — sebaliknya tidak berlaku.
Sparse Checkout untuk Monorepo Besar
Pada repositori dengan puluhan ribu file (umum dalam arsitektur monorepo), Anda dapat membatasi path mana yang Git materialisasikan dalam direktori kerja:
git sparse-checkout init --cone
git sparse-checkout set services/api services/authIni sangat berharga pada VPS dengan disk I/O terbatas, karena Git melewati dekompresi blob untuk path di luar cone.
Staging Area (Index)
Staging area, yang secara internal disebut index, adalah file biner yang terletak di .git/index. Ini berfungsi sebagai commit berikutnya yang diusulkan — snapshot yang dapat diubah yang berada di antara direktori kerja Anda dan penyimpanan objek permanen.
git add <file> # Stage a specific file
git add -p # Interactively stage hunks within a file
git add -u # Stage all tracked modifications and deletions
git status # Compare working directory and index against HEAD
git diff --cached # Show diff between index and HEADMengapa Index Ada
Index memecahkan masalah yang diabaikan oleh alat VCS yang lebih sederhana: partial commit. Anda mungkin telah memodifikasi lima file tetapi hanya ingin tiga di antaranya dalam commit berikutnya. Index memungkinkan Anda menyusun tepat snapshot yang ingin Anda rekam, terlepas dari apa yang sedang dibuka editor Anda.
Kasus tepi — korupsi index: Jika crash sistem menginterupsi git add, file index bisa menjadi rusak. Gejalanya termasuk git status yang hang atau melaporkan output yang aneh. Pemulihan:
rm .git/index
git resetGit membangun ulang index dari HEAD tanpa menyentuh direktori kerja Anda.
Index sebagai Register Konflik Merge
Selama konflik merge, index menyimpan tiga versi dari setiap file yang berkonflik secara bersamaan (stage 1, 2, dan 3 — base, ours, theirs). Inilah mengapa git diff --cached tidak menampilkan sesuatu yang berguna di tengah konflik; Anda memerlukan git diff --cc atau alat merge untuk memeriksa ketiga stage tersebut.
Direktori .git/: Anatomi Penyimpanan Objek
Direktori .git/ adalah repositori itu sendiri. Semua yang lain — direktori kerja, remote clone — diturunkan darinya. Menghapus .git/ mengubah repositori menjadi direktori biasa tanpa riwayat.
.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│ ├── HEAD
│ └── refs/
├── objects/
│ ├── info/
│ └── pack/
└── refs/
├── heads/
├── remotes/
└── tags/HEAD
HEAD adalah file teks biasa yang berisi symbolic ref (menunjuk ke branch) atau hash SHA-1 mentah (kondisi detached HEAD).
cat .git/HEAD
# ref: refs/heads/main <-- on a branch
# a3f1c9d... <-- detached HEADDetached HEAD bukan kondisi error — ini disengaja saat Anda checkout tag atau commit tertentu untuk inspeksi. Bahayanya adalah membuat commit dalam detached HEAD: commit tersebut hanya dapat dijangkau melalui reflog sampai Anda melampirkannya ke branch.
git checkout -b rescue-branch # Attach detached commits to a new branchconfig
File konfigurasi repositori lokal. Ini menggantikan pengaturan global (~/.gitconfig) dan sistem (/etc/gitconfig). Entri umum:
[core]
repositoryformatversion = 0
filemode = true
bare = false
[remote "origin"]
url = git@github.com:user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/mainPada server yang di-host sendiri, Anda akan sering mengedit file ini secara langsung saat merotasi URL remote atau mengonfigurasi uploadpack.allowReachableSHA1InWant untuk partial clone.
refs/
Direktori refs/ berisi file teks biasa, masing-masing menyimpan satu hash SHA-1. Ini adalah pointer bernama yang membuat DAG Git dapat dinavigasi.
| Tipe Ref | Path | Deskripsi |
|---|---|---|
| Branch lokal | refs/heads/<name> | Menunjuk ke commit tip dari sebuah branch |
| Branch remote-tracking | refs/remotes/<remote>/<name> | Cache lokal dari tip branch remote |
| Lightweight tag | refs/tags/<name> | Menunjuk langsung ke objek commit |
| Annotated tag | refs/tags/<name> | Menunjuk ke objek tag, yang menunjuk ke commit |
| Stash | refs/stash | Menunjuk ke commit stash |
Untuk performa, Git mengemas ref ke dalam .git/packed-refs setelah repositori mengumpulkan banyak ref. Selalu periksa kedua lokasi saat membuat skrip terhadap ref.
Objek Git: Inti yang Tidak Dapat Diubah
Semua yang disimpan di .git/objects/ adalah content-addressed: nama file adalah hash SHA-1 (atau SHA-256 pada versi Git yang lebih baru) dari konten objek. Ini membuat Git secara inheren tahan terhadap manipulasi — mengubah byte apa pun mengubah hash, memutus rantai.
Empat Tipe Objek
| Tipe Objek | Yang Disimpan | Menunjuk Ke |
|---|---|---|
| Blob | Konten file mentah (tanpa nama file, tanpa izin) | Tidak ada |
| Tree | Daftar direktori: nama file, izin, SHA blob/tree | Blob dan tree lainnya |
| Commit | Author, committer, timestamp, pesan, SHA parent | Satu tree + nol atau lebih commit parent |
| Tag | Identitas tagger, timestamp, pesan, tanda tangan GPG | Biasanya sebuah commit |
Memeriksa Objek Secara Langsung
# Show the type of any object
git cat-file -t a3f1c9d
# Show the content of any object
git cat-file -p a3f1c9d
# Show the tree of the current HEAD commit
git ls-tree HEAD
# Show a specific blob's content
git show HEAD:src/main.pyLoose Object vs. Pack File
Awalnya, setiap objek disimpan sebagai file terkompresi individual di bawah .git/objects/<2-char-prefix>/<38-char-suffix>. Ini adalah loose object. Seiring waktu, Git menjalankan git gc (garbage collection) untuk menggabungkan loose object ke dalam pack file (.git/objects/pack/*.pack) dengan index yang sesuai (.pack.idx).
Pack file menggunakan kompresi delta — menyimpan perbedaan antara objek yang serupa daripada salinan penuh. Repositori dengan ribuan file teks serupa dapat menyusut secara dramatis setelah dikemas. Pada VPS dengan kapasitas NVMe terbatas, menjalankan git gc --aggressive pada repositori besar sebelum pengarsipan adalah praktik standar.
git count-objects -vH # Show loose object count and disk usage
git gc --aggressive # Repack aggressively (CPU-intensive)
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20
# Find the 20 largest objects in the packRiwayat Commit: Directed Acyclic Graph
Setiap objek commit berisi tepat satu pointer ke objek tree (snapshot direktori root) dan nol atau lebih pointer ke commit parent. Ini membentuk DAG di mana:
- Nol parent = commit awal (root commit)
- Satu parent = commit normal
- Dua parent = merge commit
- Tiga atau lebih parent = octopus merge (jarang, digunakan untuk mengintegrasikan banyak feature branch secara bersamaan)
git log --oneline --graph --all # Visualize the full DAG
git log --format="%H %P" # Show each commit's SHA and parent SHA(s)Immutabilitas Commit dan Penulisan Ulang Riwayat
Karena SHA commit diturunkan dari kontennya (termasuk SHA parent), setiap penulisan ulang membuat commit baru dengan SHA baru. Operasi seperti git rebase, git commit --amend, dan git filter-repo tidak memodifikasi riwayat — mereka membuat riwayat paralel. Commit lama tetap berada di penyimpanan objek hingga di-garbage collect.
Inilah mengapa force-push riwayat yang ditulis ulang ke branch bersama bersifat destruktif: branch lokal kolaborator masih menunjuk ke rantai commit lama.
Branch: Pointer Ringan
Sebuah branch tidak lebih dari file 41-byte yang berisi hash SHA-1. Membuat branch bersifat instan terlepas dari ukuran repositori karena Git hanya menulis satu file kecil.
git branch feature/auth # Create branch at current HEAD
git checkout -b feature/auth # Create and switch in one step
git switch -c feature/auth # Modern equivalent (Git 2.23+)
git branch -d feature/auth # Delete (safe: refuses if unmerged)
git branch -D feature/auth # Delete (force: regardless of merge status)Internal Branch
cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0Saat Anda melakukan commit pada sebuah branch, Git menulis SHA commit baru ke file ini. Itulah keseluruhan dari “memajukan pointer branch.”
Tracking Branch dan Konfigurasi Upstream
Hubungan tracking memberi tahu Git branch remote mana yang harus dibandingkan oleh branch lokal untuk pelaporan divergensi git status dan perilaku git pull.
git branch --set-upstream-to=origin/main main
git branch -vv # Show tracking relationships and ahead/behind countsTag: Penanda Permanen dalam Riwayat
Tag menandai commit tertentu sebagai signifikan — biasanya rilis perangkat lunak. Tidak seperti branch, tag tidak dipindahkan oleh commit baru.
| Fitur | Lightweight Tag | Annotated Tag |
|---|---|---|
| Penyimpanan | File ref yang menunjuk ke commit | Objek tag dalam penyimpanan objek |
| Metadata | Tidak ada | Nama tagger, email, tanggal, pesan |
| Penandatanganan GPG | Tidak memungkinkan | Didukung melalui git tag -s |
| Direkomendasikan untuk rilis | Tidak | Ya |
Transfer dengan git push --tags | Ya | Ya |
git tag v2.1.0 # Lightweight tag at HEAD
git tag -a v2.1.0 -m "Release 2.1.0" # Annotated tag
git tag -s v2.1.0 -m "Signed release" # GPG-signed annotated tag
git push origin --tags # Push all tags to remote
git push origin v2.1.0 # Push a specific tagJebakan kritis: git push tidak mendorong tag secara default. Tim sering melupakan ini dan mempublikasikan catatan rilis yang merujuk tag yang tidak ada di remote.
Remote: Kolaborasi Terdistribusi
Remote adalah URL bernama yang disimpan di .git/config. Branch remote-tracking (di bawah refs/remotes/) adalah snapshot read-only lokal dari branch remote, yang diperbarui hanya saat Anda secara eksplisit melakukan fetch.
git remote add origin git@github.com:user/repo.git
git remote -v # List remotes with URLs
git remote set-url origin <new-url> # Change a remote URL
git fetch origin # Update remote-tracking branches
git fetch --prune # Remove stale remote-tracking branches
git push origin main # Push local main to remote
git push -u origin feature/auth # Push and set upstream trackingBeberapa Remote
Satu repositori dapat melacak beberapa remote — umum saat memelihara fork bersama upstream:
git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/mainSaat meng-host bare repositori sendiri di dedicated server untuk tim Anda, setiap developer menambahkan server sebagai remote dan menggunakan autentikasi SSH key untuk akses push.
Hook: Penegakan Otomatis di Setiap Event Git
Hook adalah skrip yang dapat dieksekusi di .git/hooks/. Git memanggilnya pada titik-titik yang ditentukan dalam alur kerja. Hook tidak ditransfer oleh git clone atau git push — setiap developer (atau server) harus menginstalnya secara independen. Ini adalah sumber kebingungan yang sering terjadi dalam lingkungan tim.
Hook Sisi Klien
| Hook | Pemicu | Penggunaan Umum |
|---|---|---|
pre-commit | Sebelum prompt pesan commit | Linting, pemindaian rahasia, eksekusi pengujian |
prepare-commit-msg | Setelah pesan default dibuat | Menyisipkan nama branch ke dalam pesan |
commit-msg | Setelah pengguna menulis pesan | Menegakkan format conventional commit |
post-commit | Setelah commit direkam | Notifikasi lokal |
pre-push | Sebelum git push dieksekusi | Menjalankan suite pengujian lengkap |
pre-rebase | Sebelum rebase dimulai | Mencegah rebase pada branch yang sudah dipublikasikan |
Hook Sisi Server
| Hook | Pemicu | Penggunaan Umum |
|---|---|---|
pre-receive | Sebelum ref diperbarui | Menegakkan perlindungan branch, menolak force-push |
update | Per-ref selama receive | Penegakan kebijakan per-branch |
post-receive | Setelah semua ref diperbarui | Memicu CI/CD, mengirim notifikasi |
Contoh: Hook Pre-commit untuk Deteksi Rahasia
#!/usr/bin/env bash
# .git/hooks/pre-commit
if git diff --cached --name-only | xargs grep -lE '(AKIA|passwords*=|api_keys*=)' 2>/dev/null; then
echo "ERROR: Potential secret detected in staged files. Commit aborted."
exit 1
fi
exit 0Jadikan dapat dieksekusi:
chmod +x .git/hooks/pre-commitUntuk distribusi hook di seluruh tim, gunakan alat seperti Husky (proyek Node.js) atau simpan hook dalam direktori hooks/ di root repositori dan buat symlink selama pengaturan proyek.
Reflog: Jaring Pengaman
Reflog mencatat setiap pergerakan pointer HEAD dan branch, termasuk operasi yang tampaknya menghancurkan riwayat (hard reset, rebase, amended commit). Ini disimpan di .git/logs/.
git reflog # Show HEAD movement history
git reflog show main # Show movement history for a specific branch
git checkout HEAD@{3} # Check out the state HEAD was in 3 moves ago
git branch recovered HEAD@{5} # Recover commits by branching from a reflog entryEntri reflog kedaluwarsa setelah 90 hari secara default (gc.reflogExpire). Pada server produksi, pertimbangkan untuk memperpanjang ini:
git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30Bare Repository: Hosting Sisi Server
Bare repository tidak memiliki direktori kerja. Ini hanya berisi konten .git/ di level root. Bare repository adalah format yang tepat untuk hosting terpusat — mereka menerima push tanpa komplikasi dari branch yang di-checkout.
git init --bare /srv/repos/myproject.gitSaat Anda push ke GitHub, GitLab, atau server Git yang di-host sendiri, Anda mendorong ke bare repository. Jika Anda meng-host server Git sendiri di VPS dengan cPanel atau VPS Linux biasa, bare repository di bawah /srv/repos/ dengan akses SSH adalah arsitektur standar.
Menginisialisasi Bare Repository Bersama
# On the server
git init --bare --shared=group /srv/repos/project.git
chown -R git:developers /srv/repos/project.git
# On a developer's machine
git remote add origin git@yourserver.com:/srv/repos/project.git
git push -u origin mainPenyimpanan Objek Git: Ukuran, Integritas, dan Pemeliharaan
Memeriksa Kesehatan Repositori
git fsck --full # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found # Write dangling objects to .git/lost-found/Menemukan dan Menghapus Objek Besar
File biner besar yang tidak sengaja di-commit adalah penyebab umum repositori yang membengkak. Identifikasi sebelum menggunakan git filter-repo untuk menghapusnya:
# Find the 10 largest objects by compressed size
git verify-pack -v .git/objects/pack/*.idx
| sort -k3 -rn
| head -10
| awk '{print $1}'
| xargs -I{} git cat-file -p {}# Remove a file from all history (requires git-filter-repo)
git filter-repo --path path/to/large-file.bin --invert-pathsSetelah filtering, semua kolaborator harus melakukan re-clone — repositori lokal mereka mereferensikan hash SHA yang tidak lagi ada dalam riwayat yang ditulis ulang.
Perbandingan: Konsep Kunci Repositori Git
| Konsep | Tipe | Dapat Diubah | Disimpan Di | Ditransfer oleh Push/Fetch |
|---|---|---|---|---|
| Blob | Objek | Tidak | .git/objects/ | Ya (saat dapat dijangkau) |
| Tree | Objek | Tidak | .git/objects/ | Ya (saat dapat dijangkau) |
| Commit | Objek | Tidak | .git/objects/ | Ya (saat dapat dijangkau) |
| Annotated Tag | Objek | Tidak | .git/objects/ | Hanya dengan --tags |
| Branch | Ref | Ya | .git/refs/heads/ | Ya |
| Branch remote-tracking | Ref | Ya (saat fetch) | .git/refs/remotes/ | Tidak (cache lokal) |
| Lightweight Tag | Ref | Tidak | .git/refs/tags/ | Hanya dengan --tags |
| HEAD | Symref/hash | Ya | .git/HEAD | Tidak |
| Index | File biner | Ya | .git/index | Tidak |
| Hook | Skrip | Ya | .git/hooks/ | Tidak |
| Reflog | Log | Ya (kedaluwarsa otomatis) | .git/logs/ | Tidak |
Matriks Keputusan Praktis dan Poin-Poin Utama
Gunakan daftar periksa ini saat menyiapkan atau mengaudit repositori Git di infrastruktur Anda:
Inisialisasi repositori
- Gunakan
git init --bare --shared=groupuntuk repositori apa pun yang akan menerima push dari beberapa pengguna. - Simpan bare repository di luar direktori yang dapat diakses web (jangan pernah di bawah
/var/www/).
Kesehatan penyimpanan objek
- Jalankan
git fsck --fullsetelah insiden penyimpanan atau error filesystem apa pun. - Jadwalkan
git gcsecara berkala pada repositori yang sudah lama; otomatiskan melalui cron di server Anda. - Pantau ukuran pack file dengan
git count-objects -vH; selidiki jika jumlah loose object melebihi 1.000.
Kebersihan branch dan ref
- Hapus branch yang sudah di-merge dengan segera; ref yang usang menumpuk dan memperlambat operasi
git fetch --prune. - Gunakan
git fetch --prunedalam pipeline CI untuk menghindari tindakan pada branch remote yang sudah dihapus.
Penerapan hook
- Jangan pernah mengandalkan
.git/hooks/untuk kebijakan di seluruh tim — hook tidak di-clone. Gunakan hookpre-receivesisi server atau CI gate sebagai gantinya. - Audit hook sisi server setelah setiap upgrade server Git; path interpreter hook dapat berubah.
Keamanan pada server yang di-host sendiri
- Batasi akses SSH ke pengguna
gitdengan forced command (command=diauthorized_keys). - Gunakan
git-shellsebagai login shell untuk penggunagituntuk mencegah eksekusi perintah sembarangan. - Pasangkan server repositori Anda dengan sertifikat SSL yang valid jika Anda mengekspos antarmuka web apa pun (Gitea, GitLab, cgit).
Penulisan ulang riwayat
- Jangan pernah menulis ulang riwayat pada branch yang dibagikan dengan orang lain tanpa rencana migrasi yang terkoordinasi.
- Setelah
git filter-repo, semua kolaborator harus melakukan re-clone; perbarui URL remote CI/CD segera.
Pemulihan bencana
- Perpanjang kedaluwarsa reflog pada server produksi (
gc.reflogExpire = 180). - Simpan bare clone sekunder di host terpisah sebagai cadangan;
git fetchsederhana dari primary sudah cukup.
FAQ
Apa perbedaan antara bare dan non-bare repositori Git?
Repositori non-bare memiliki direktori kerja tempat file di-checkout, ditambah subdirektori .git/ yang berisi penyimpanan objek. Bare repository hanya berisi penyimpanan objek di rootnya (tanpa direktori kerja) dan merupakan format yang tepat untuk server bersama yang menerima push.
Bisakah saya memulihkan commit setelah menjalankan git reset --hard?
Ya, selama commit belum di-garbage collect. Jalankan git reflog untuk menemukan SHA commit yang ingin Anda pulihkan, lalu git checkout -b recovery-branch <SHA> untuk melampirkannya ke branch baru. Entri reflog dipertahankan selama 90 hari secara default.
Mengapa git push tidak mentransfer tag saya?
Secara desain, git push hanya mentransfer commit yang dapat dijangkau dari ref yang Anda push secara eksplisit. Tag adalah ref terpisah dan harus di-push dengan git push origin --tags (semua tag) atau git push origin <tagname> (tag tertentu).
Apa yang terjadi pada index selama konflik merge?
Index menyimpan ketiga versi dari setiap file yang berkonflik secara bersamaan: stage 1 (ancestor/base bersama), stage 2 (versi Anda), dan stage 3 (versi mereka). git add normal hanya menulis stage 0 (yang sudah diselesaikan). Sampai semua konflik diselesaikan dan di-stage, git commit akan menolak untuk melanjutkan.
Bagaimana hook Git berbeda antara penerapan sisi klien dan sisi server?
Hook sisi klien berjalan di mesin developer dan tidak ditegakkan secara terpusat — developer mana pun dapat melewatinya dengan menghapus file hook. Hook sisi server (pre-receive, update, post-receive) berjalan di server hosting dan tidak dapat dilewati oleh klien, menjadikannya titik penegakan yang tepat untuk kebijakan perlindungan branch, persyaratan code review, dan pemicu CI/CD.
untuk semua layanan hosting