Git Oggetti
In un repository git, tutto viene memorizzato come oggetto. Esistono quattro tipi: blob (contenuto dei file), tree (elenchi di directory), commit (snapshot con metadati) e tag (riferimenti annotati). Ogni oggetto è identificato dall'hash SHA-1 del suo contenuto.
Tipi di oggetto
Blob
Un blob contiene il contenuto grezzo di un file, senza metadati (né nome file né permessi).
use Pitmaster\Object\Blob;
// Create from content
$blob = Blob::fromContent('Hello, world!');
echo $blob->content; // 'Hello, world!'
echo $blob->size(); // 13
echo $blob->id->hex; // SHA-1 hash
echo $blob->type->value; // 'blob' (the property is ObjectType::Blob)
Tree
Un tree è un elenco di directory. Ogni voce contiene una modalità, un nome e un hash che fa riferimento a un blob o a un sottoalbero.
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;
// Read a tree
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
echo "{$entry->mode} {$entry->name} {$entry->hash->hex}\n";
}
// Look up a specific entry
$entry = $tree->entry('README.md');
// Create a tree (entries already in git's sort order)
$entries = [
new TreeEntry('100644', 'README.md', $blobId),
new TreeEntry('100755', 'deploy.sh', $execBlobId),
new TreeEntry('40000', 'src', $subtreeId),
];
$tree = Tree::fromEntries($entries);
Tree::fromEntries() scrive le voci nell'ordine fornito e le stringhe delle modalità esattamente come fornite; occorre quindi ordinarle prima e scrivere le modalità delle directory come 40000. Un tree scritto con 040000 o in un altro ordine ha un hash diverso da quello di git, e git fsck --strict mostra la modalità completata con zeri iniziali.
TreeEntry
TreeEntry è un oggetto valore di sola lettura:
use Pitmaster\Object\TreeEntry;
$entry->mode; // '100644', '100755', '40000', '120000', '160000'
$entry->name; // Filename
$entry->hash; // ObjectId
// Type checks
$entry->isBlob(); // regular or executable file (mode 10xxxx)
$entry->isExecutable(); // mode 100755
$entry->isTree(); // mode 40000 (also accepts 040000)
$entry->isSymlink(); // mode 120000
$entry->isGitlink(); // mode 160000 (submodule)
Valori delle modalità:
| Modalità | Significato |
|---|---|
100644 |
File normale |
100755 |
File eseguibile |
40000 |
Directory (tree); mostrata come 040000 da git ls-tree |
120000 |
Collegamento simbolico |
160000 |
Gitlink (sottomodulo) |
Le voci di un tree vengono ordinate per nome; ai fini del confronto, i nomi delle directory vengono considerati come se terminassero con /.
Commit
Un commit collega un tree alla cronologia e ai metadati.
use Pitmaster\Object\Commit;
$commit = $repo->head();
$commit->id; // ObjectId
$commit->tree; // ObjectId of the tree
$commit->parents; // array of ObjectId (zero for root, one for normal, two+ for merge)
$commit->author; // 'Name <email> timestamp timezone'
$commit->committer; // 'Name <email> timestamp timezone'
$commit->message; // Full commit message
$commit->isMerge(); // true if two or more parents
Creazione del contenuto di un commit
$content = Commit::buildContent(
tree: $treeId,
parents: [$parentId],
author: 'Jane <jane@example.com> 1700000000 +0000',
committer: 'Jane <jane@example.com> 1700000000 +0000',
message: "Add feature\n",
);
Il formato grezzo di un commit:
tree <hex hash>\n
parent <hex hash>\n (zero or more)
author <name> <<email>> <timestamp> <tz>\n
committer <name> <<email>> <timestamp> <tz>\n
\n
<message>
Tag
I tag annotati sono oggetti con metadati.
use Pitmaster\Object\Tag;
$tag = $repo->readObject($tagHash);
$tag->object; // ObjectId of the target
$tag->objectType; // ObjectType enum (ObjectType::Commit, ...)
$tag->name; // Tag name
$tag->tagger; // 'Name <email> timestamp timezone'
$tag->message; // Tag message
Formato grezzo di un tag:
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>
ObjectId
ObjectId è un oggetto valore di sola lettura che rappresenta un hash SHA-1 (o SHA-256).
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;
// From hex string
$id = ObjectId::fromHex('a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2');
// From binary (20 bytes for SHA-1, 32 for SHA-256)
$id = ObjectId::fromBinary($rawBytes);
// Compute from content
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!');
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!', 'sha256');
// Zero hash
$id = ObjectId::zero();
$id = ObjectId::zero('sha256');
// Properties
$id->hex; // 40-character hex string (64 for SHA-256)
$id->binary; // 20-byte raw binary (32 for SHA-256)
$id->algo; // 'sha1' or 'sha256'
$id->isSha256();
$id->isZero();
// Comparison
$id->equals($otherId);
L'hash viene calcolato sui dati non compressi: sha1("{type} {size} oppure SHA-256 degli stessi byte. fromHex() e fromBinary() scelgono l'algoritmo in base alla lunghezza.
Enum ObjectType
use Pitmaster\Object\ObjectType;
ObjectType::Blob; // 'blob'
ObjectType::Tree; // 'tree'
ObjectType::Commit; // 'commit'
ObjectType::Tag; // 'tag'
Formato degli oggetti non compressi in pack
Gli oggetti non compressi in pack vengono memorizzati come file singoli in .git/objects/, organizzati in base ai primi due caratteri esadecimali del loro hash.
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
Il contenuto del file è compresso con zlib. Il contenuto non compresso è:
<type> <decimal size>\0<raw content>
Ad esempio, un blob che contiene "Hello" viene memorizzato come:
zlib_encode("blob 5\0Hello")
Lettura degli oggetti non compressi in pack
use Pitmaster\Storage\LooseObjectStore;
$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);
LooseObjectStore:
- Calcola il percorso del file a partire dall'hash
- Legge e decomprime il file con zlib
- Analizza l'intestazione per estrarre il tipo e la dimensione
- Affida il contenuto al parser appropriato (
Blob,Tree,Commit,Tag)
Scrittura degli oggetti non compressi in pack
$store->write($object);
Procedura di scrittura:
- Serializza:
"{type} {size}\0{content}" - Usa l'
iddell'oggetto, calcolato durante la sua creazione (la lettura verifica l'hash) - Comprime:
zlib_encode(serialized) - Scrive in
.git/objects/{hash[0..1]}/{rest of hash} - Scrittura atomica (scrive in un file temporaneo, poi lo rinomina)
ObjectSerializer
ObjectSerializer gestisce la codifica e la decodifica del formato wire degli oggetti.
use Pitmaster\Storage\ObjectSerializer;
// Encode
$raw = ObjectSerializer::encodeRaw($object);
// "blob 5\0Hello"
$compressed = ObjectSerializer::encode($object);
// zlib-compressed form, as stored in a loose object file
// Decode
$object = ObjectSerializer::decodeRaw($raw);
$object = ObjectSerializer::decode($compressed, $expectedHex); // throws CorruptObjectException on a hash mismatch
ObjectDatabase
ObjectDatabase è un archivio composito che cerca prima gli oggetti non compressi in pack, poi i file pack.
use Pitmaster\Storage\ObjectDatabase;
$db = new ObjectDatabase('/path/to/.git/objects');
$object = $db->read($objectId); // Search loose, then packs
$db->write($object); // Always writes to loose
$exists = $db->exists($objectId); // Check loose, then packs
$all = $db->listAll(); // All hashes from loose + packs
La scrittura diretta nell'archivio non compresso è intenzionale. Lasciate che git gc ricompatti gli oggetti nei file pack.
Supporto SHA-256
Pitmaster astrae l'algoritmo di hash tramite ObjectId. SHA-1 è l'impostazione predefinita. SHA-256 viene scelto per ogni repository al momento della creazione:
$repo = Pitmaster::init('/path/to/project', 'sha256');
./vendor/bin/pitmaster init --object-format=sha256
Questo scrive extensions.objectformat = sha256 nella configurazione. I repository aperti successivamente leggono il formato da quella voce; da quel momento, ogni operazione su oggetti, tree, indici e riferimenti usa stringhe esadecimali di 64 caratteri e hash binari di 32 byte. Non esiste un interruttore o una costante globale per l'algoritmo.
Le classi degli oggetti accettano l'algoritmo come argomento quando si creano oggetti autonomamente: Blob::fromContent($content, 'sha256'), Tree::fromEntries($entries, 'sha256'), ObjectId::compute($type, $content, 'sha256'). Pitmaster::clone() crea repository SHA-1.
Formato binario delle voci tree
All'interno del contenuto di un oggetto tree, ogni voce è codificata come segue:
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>
Nota: l'hash è in formato binario grezzo, non esadecimale, a differenza degli hash esadecimali negli oggetti commit e tag.
Le voci vengono ordinate confrontando i nomi come se le directory avessero un / finale. Questo ordinamento deve essere esatto affinché l'hash del tree corrisponda a quello calcolato da git.