Aller au contenu

Objets Git

Tout ce qui se trouve dans un dépôt git est stocké sous forme d'objet. Il en existe quatre types : les blobs (contenu des fichiers), les trees (listes de répertoires), les commits (instantanés avec métadonnées) et les tags (références annotées). Chaque objet est identifié par le hachage de son contenu SHA-1.

Types d'objets

Blob

Un blob contient le contenu brut d'un fichier, sans métadonnées (ni nom de fichier ni permissions).

PHP
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 est une liste de répertoire. Chaque entrée comporte un mode, un nom et un hachage pointant vers un blob ou un sous-tree.

PHP
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() écrit les entrées dans l'ordre indiqué et conserve exactement les chaînes de mode fournies. Il faut donc les trier au préalable et écrire les modes de répertoire sous la forme 40000. Un tree écrit avec 040000 ou dans un autre ordre obtient un hachage différent de celui de git, et git fsck --strict affiche le mode complété par des zéros.

TreeEntry

TreeEntry est un objet-valeur en lecture seule :

PHP
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)

Valeurs de mode :

Mode Signification
100644 Fichier ordinaire
100755 Fichier exécutable
40000 Répertoire (tree) ; affiché sous la forme 040000 par git ls-tree
120000 Lien symbolique
160000 Gitlink (sous-module)

Les entrées d'un tree sont triées par nom ; pour la comparaison, les noms des répertoires sont considérés comme s'ils se terminaient par /.

Commit

Un commit associe un tree à son historique et à ses métadonnées.

PHP
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

Construire le contenu d'un commit

PHP
$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",
);

Format brut d'un commit :

Code
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

Les tags annotés sont des objets contenant des métadonnées.

PHP
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

Format brut d'un tag :

Code
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>

ObjectId

ObjectId est un objet-valeur en lecture seule représentant un hachage SHA-1 (ou SHA-256).

PHP
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);

Le hachage est calculé sur les données non compressées : sha1("{type} {size}{content}") ou SHA-256 des mêmes octets. fromHex() et fromBinary() choisissent l'algorithme en fonction de la longueur.

Énumération ObjectType

PHP
use Pitmaster\Object\ObjectType;

ObjectType::Blob;    // 'blob'
ObjectType::Tree;    // 'tree'
ObjectType::Commit;  // 'commit'
ObjectType::Tag;     // 'tag'

Format des objets libres

Les objets libres sont stockés dans des fichiers individuels dans .git/objects/, classés selon les deux premiers caractères hexadécimaux de leur hachage.

Code
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Le contenu du fichier est compressé avec zlib. Le contenu décompressé est :

Code
<type> <decimal size>\0<raw content>

Par exemple, un blob contenant « Hello » est stocké ainsi :

Code
zlib_encode("blob 5\0Hello")

Lire les objets libres

PHP
use Pitmaster\Storage\LooseObjectStore;

$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);

LooseObjectStore :

  1. Calcule le chemin du fichier à partir du hachage
  2. Lit et décompresse le fichier avec zlib
  3. Analyse l'en-tête pour extraire le type et la taille
  4. Délègue l'opération à l'analyseur approprié (Blob, Tree, Commit, Tag)

Écrire des objets libres

PHP
$store->write($object);

Processus d'écriture :

  1. Sérialiser : "{type} {size}\0{content}"
  2. Utiliser le id de l'objet, calculé lors de sa création (les lectures vérifient le hachage)
  3. Compresser : zlib_encode(serialized)
  4. Écrire dans .git/objects/{hash[0..1]}/{rest of hash}
  5. Écriture atomique (écriture dans un fichier temporaire, puis renommage)

ObjectSerializer

ObjectSerializer gère l'encodage et le décodage du format filaire des objets.

PHP
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 est un magasin composite qui recherche d'abord les objets libres, puis les fichiers pack.

PHP
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

L'écriture directe dans le stockage libre est intentionnelle. Laissez git gc regrouper les objets dans des fichiers pack.

Prise en charge de SHA-256

Pitmaster abstrait l'algorithme de hachage via ObjectId. SHA-1 est la valeur par défaut. SHA-256 est choisi par dépôt lors de sa création :

PHP
$repo = Pitmaster::init('/path/to/project', 'sha256');
Shell
./vendor/bin/pitmaster init --object-format=sha256

Cela écrit extensions.objectformat = sha256 dans la configuration. Les dépôts ouverts par la suite lisent le format à partir de cette entrée, puis toutes les opérations sur les objets, les trees, l'index et les références utilisent des chaînes hexadécimales de 64 caractères et des hachages binaires de 32 octets. Il n'existe aucun paramètre global ni constante pour l'algorithme.

Les classes d'objets prennent l'algorithme en argument lorsque vous créez vous-même des objets : Blob::fromContent($content, 'sha256'), Tree::fromEntries($entries, 'sha256'), ObjectId::compute($type, $content, 'sha256'). Pitmaster::clone() crée des dépôts SHA-1.

Format binaire des entrées de tree

Dans le contenu d'un objet tree, chaque entrée est encodée ainsi :

Code
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>

Remarque : le hachage est en binaire brut, et non en hexadécimal, contrairement aux hachages hexadécimaux des objets commit et tag.

Les entrées sont triées en comparant les noms comme si les répertoires se terminaient par /. Cet ordre doit être respecté exactement pour que le hachage du tree corresponde au calcul de git.