File Pack
File pack adalah mekanisme git untuk penyimpanan dan transfer yang efisien. Alih-alih menyimpan setiap objek sebagai file terkompresi zlib terpisah, file pack menggabungkan banyak objek ke dalam satu file dengan kompresi delta opsional. Hampir semua repositori hasil kloning menggunakan file pack.
Struktur file pack
File pack (.git/objects/pack/pack-{hash}.pack) berisi:
Header: PACK (4 bytes) + version (4 bytes, big-endian) + count (4 bytes, big-endian)
Entries: [type+size varint] [delta-base] [zlib-compressed data] ...
Trailer: checksum of the pack (20 bytes SHA-1, 32 bytes SHA-256)
Pengodean jenis objek
Setiap entri diawali bilangan bulat dengan panjang bervariasi, dengan jenis dan ukuran diselingkan:
First byte: 1TTTSSSS
TTT = object type (1-4 for base objects, 6 for OFS_DELTA, 7 for REF_DELTA)
SSSS = first 4 bits of uncompressed size
MSB = 1 means more size bytes follow
Nilai jenis:
| Nilai | Jenis |
|---|---|
| 1 | Commit |
| 2 | Tree |
| 3 | Blob |
| 4 | Tag |
| 6 | OFS_DELTA (delta berbasis offset) |
| 7 | REF_DELTA (delta berbasis hash) |
Membaca file pack
use Pitmaster\Pack\PackFile;
$pack = PackFile::open(
'/path/to/.git/objects/pack/pack-abc123.pack',
'/path/to/.git/objects/pack/pack-abc123.idx',
);
$pack->has($hex); // Is the object in this pack?
$object = $pack->read($hex); // Resolved GitObject, or null
$object = $pack->readAtOffset($offset);
$hashes = $pack->allHashes();
Kelas PackFile:
- Memvalidasi header
PACKdan nomor versi - Menemukan offset melalui file
.idxdan mengurai varint jenis/ukuran entri - Mendekompresi data yang dikompresi dengan zlib
- Untuk objek dasar: mengembalikan konten secara langsung
- Untuk objek delta: meresolusikan basisnya (mengikuti rantai) dan menerapkan delta, lalu menyimpan objek hasil resolusi dalam cache
File indeks pack
Setiap file .pack memiliki file .idx pasangan untuk pencarian cepat. Tanpa indeks, objek harus ditemukan dengan memindai seluruh pack.
Format indeks v2
Magic: FF 74 4F 63 (identifies v2)
Version: 00 00 00 02
Fanout: 256 x 4-byte big-endian counts (cumulative)
Names: N x 20-byte SHA-1 hashes, sorted (32 bytes for SHA-256)
CRC32s: N x 4-byte CRC32 checksums
Offsets: N x 4-byte pack offsets (MSB=1 means large offset table)
Large: M x 8-byte offsets (for packs > 2GB)
Checksums: pack checksum + index checksum (20 bytes each for SHA-1)
use Pitmaster\Pack\PackIndex;
$index = PackIndex::open('/path/to/pack-abc123.idx');
// Look up an object
$offset = $index->findOffset($hex); // Pack file offset, or null
// List all objects in the pack
$hashes = $index->allHashes();
$count = $index->objectCount();
Tabel fanout memungkinkan pencarian biner O(log n): entri 256 menunjukkan jumlah hash yang dimulai dengan setiap kemungkinan byte pertama.
Format indeks v1
Format lama tanpa checksum CRC32. Pitmaster mendukung keduanya: PackIndex::open() membaca file v1 jika magic v2 tidak ada, sehingga kode yang sama berfungsi untuk keduanya.
Resolusi delta
Objek berdelta menyimpan instruksi untuk merekonstruksi konten dari objek basis, alih-alih menyimpan konten lengkap. Cara ini menghasilkan kompresi signifikan untuk objek yang serupa (e.g., versi berurutan dari file yang sama).
Jenis delta
OFS_DELTA (jenis 6): objek basis diidentifikasi dengan offset negatif dalam file pack yang sama. Lebih umum karena tidak perlu mengetahui hash objek basis.
REF_DELTA (jenis 7): objek basis diidentifikasi dengan hash-nya. Di git, basis dapat berada di luar pack (thin pack). Pitmaster hanya mencari basis REF_DELTA di dalam pack yang sama, sehingga thin pack tidak didukung (#16).
Instruksi delta
Konten delta berupa rangkaian instruksi salin dan sisipkan:
Copy: 1XXXXXXX [offset bytes] [size bytes] (copy from base)
Insert: 0LLLLLLL <L bytes of literal data> (insert new data)
Instruksi salin: 7 bit X menunjukkan byte offset dan ukuran yang tersedia. Offset berukuran hingga 4 byte, ukuran hingga 3 byte. Jika ukurannya nol, artinya 0x10000 (65536) byte.
Instruksi sisipkan: 7 bit L menunjukkan panjang data literal (1-127 byte). L=0 dicadangkan/tidak valid.
DeltaApplier
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
DeltaApplier:
- Membaca ukuran basis dan ukuran hasil yang diharapkan dari header delta (dikodekan sebagai LEB128)
- Menelusuri instruksi:
- Salin: mengambil offset dan ukuran, lalu menyalin byte dari konten basis
- Sisipkan: mengambil panjang, lalu menyalin byte literal dari data delta
- Memverifikasi bahwa ukuran hasil cocok dengan ukuran yang diharapkan
Rantai delta
Basis suatu objek berdelta dapat berupa objek berdelta itu sendiri. PackFile::read() dan readAtOffset() mengikuti rantainya:
- Baca entri. Jika jenisnya adalah jenis dasar (commit, tree, blob, tag), kembalikan entri tersebut.
- Jika OFS_DELTA: hitung offset basis (offset saat ini dikurangi offset negatif yang dikodekan), baca entri basis, lalu lakukan rekursi.
- Jika REF_DELTA: cari basis berdasarkan hash dalam indeks pack yang sama, lalu lakukan rekursi.
- Terapkan instruksi delta pada basis yang sudah diresolusikan.
- Terapkan batas
PITMASTER_MAX_DELTA_CHAIN(default 50) untuk mencegah rantai berjalan tanpa kendali.
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);
// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
PackWriter
Pitmaster dapat menulis file pack untuk operasi jaringan (push).
use Pitmaster\Pack\PackWriter;
use Pitmaster\Pack\PackIndexer;
$packData = PackWriter::encode($objects); // GitObject[] to pack bytes
$paths = PackWriter::write($packDir, $objects); // ['packPath' => ..., 'idxPath' => ..., 'hash' => ...]
$idxPath = PackIndexer::writeIndex($packPath); // Build the .idx for a received pack
PackWriter:
- Mengumpulkan objek yang akan disertakan dalam pack
- Menserialisasikan setiap objek dengan header varint jenis/ukuran
- Mengompresi konten dengan zlib
- Menulis header PACK, entri, dan trailer SHA-1
Saat ini hanya menulis objek dasar (tanpa kompresi delta). Kompresi delta adalah optimasi yang dapat ditambahkan nanti; biarkan git gc mengemas ulang demi efisiensi penyimpanan.
PackEnumerator
Ulangi semua objek dalam pack, sesuai urutan indeks:
use Pitmaster\Pack\PackEnumerator;
$enumerator = new PackEnumerator($packFile); // a PackFile opened with its .idx
foreach ($enumerator->enumerate() as $object) {
echo "{$object->type->value} {$object->id->hex} {$object->size()}\n";
}
Setiap item adalah GitObject yang sudah diresolusikan.
MultiPackIndex
Untuk repositori dengan banyak file pack, multi-pack-index (MIDX) menyediakan satu pencarian untuk semua pack.
use Pitmaster\Pack\MultiPackIndex;
File MIDX (.git/objects/pack/multi-pack-index) berisi:
- Tabel fanout (seperti v2 indeks pack)
- ID objek yang diurutkan di seluruh pack
- Nama file pack dan penetapan pack untuk setiap objek
- Offset ke dalam setiap file pack
MultiPackIndex::open($path) mengurai file MIDX dan menyediakan findObject($hex) dan packNames(). Pencarian objek di repositori tidak menggunakannya: basis data objek selalu membaca file .idx satu per satu.
CommitGraph
File commit-graph mempercepat penelusuran riwayat commit dan penghitungan merge base.
use Pitmaster\Pack\CommitGraph;
Terletak di .git/objects/info/commit-graph, file ini berisi:
- OID commit dalam urutan terurut
- Informasi parent (diindeks, tidak memerlukan pembacaan objek)
- Nomor generasi (untuk mempercepat kueri keterjangkauan)
- Timestamp commit
Jika tersedia, penghitungan merge base di balik Repository::merge(), mergeBase(), isBranchMerged(), dan pemeriksaan fast-forward push membaca tautan parent dari commit-graph alih-alih membaca objek commit satu per satu. CommitWalker (log) tidak menggunakannya.
Bilangan bulat dengan panjang bervariasi
File pack menggunakan beberapa pengodean bilangan bulat dengan panjang bervariasi:
Varint Git (header jenis/ukuran)
Pengodean lanjutkan-pada-MSB, dengan bit paling signifikan menandakan masih ada byte berikutnya:
use Pitmaster\Encoding\VarInt;
$size = VarInt::decodePackSize($reader, $initialBits); // continues the size from the first header byte
$offset = VarInt::decodeOfsOffset($reader); // OFS_DELTA base distance
$header = VarInt::encodePackSize($type, $size);
LEB128 (header delta)
Digunakan dalam header instruksi delta untuk ukuran dasar dan ukuran hasil:
use Pitmaster\Encoding\Leb128;
$value = Leb128::decodeUnsigned($reader); // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
BinaryReader
Aliran byte tingkat rendah dengan pelacakan posisi:
use Pitmaster\Encoding\BinaryReader;
$reader = new BinaryReader($data);
$byte = $reader->readByte();
$int = $reader->readUint32(); // big-endian
$bytes = $reader->read(20);
$hash = $reader->readHash(20);
$reader->position(); // Current offset
$reader->seek(0);