Vai al contenuto

Formato dell'indice

Il file indice (.git/index) è l'area di staging. Registra i file che verranno inclusi nel commit successivo, insieme ai metadati necessari per calcolare lo stato in modo efficiente. Pitmaster legge gli indici nelle versioni 2, 3 e 4 e scrive le versioni 2 e 3. La scrittura di un indice letto come versione 4 produce un file che git non è in grado di leggere (#15); prima di scrivere tramite Pitmaster, convertite questi repository con git update-index --index-version 2.

Lettura dell'indice

PHP
use Pitmaster\Index\Index;

$index = Index::open('/path/to/.git/index');   // pass 32 as the second argument for SHA-256

// Or through the repository
$index = $repo->index();

// Entry count (all stages)
echo $index->count();

// Iterate stage 0 entries, keyed by path
foreach ($index->entries() as $path => $entry) {
    echo "{$entry->path} {$entry->hash->hex}\n";
}

// Look up a specific entry
$entry = $index->entry('src/File.php');

Index::open() restituisce un indice vuoto se il file non esiste. entries() restituisce solo le voci della fase 0; allEntries() include le fasi dei conflitti e paths() elenca i percorsi tracciati.

IndexEntry

Ogni voce dell'indice è un oggetto valore di sola lettura:

PHP
use Pitmaster\Index\IndexEntry;

$entry->path;          // File path relative to repo root
$entry->hash;          // ObjectId of the blob
$entry->mode;          // File mode as an int (0100644, 0100755, 0120000, 0160000)
$entry->fileSize;      // File size in bytes
$entry->ctimeSec;      // Metadata change time (seconds)
$entry->ctimeNsec;     // Metadata change time nanoseconds
$entry->mtimeSec;      // Modification time (seconds)
$entry->mtimeNsec;     // Modification time nanoseconds
$entry->dev;           // Device number
$entry->ino;           // Inode number
$entry->uid;           // User ID
$entry->gid;           // Group ID
$entry->flags;         // Raw flags field
$entry->extendedFlags; // v3 extended flags (skip-worktree, intent-to-add)
$entry->stage();       // Conflict stage (0 = normal, 1 = base, 2 = ours, 3 = theirs)
$entry->assumeValid(); // Assume-valid bit
$entry->skipWorktree(); // Skip-worktree bit

Creazione delle voci

PHP
// From file stat
$entry = IndexEntry::fromStat('src/File.php', $blobId, '/full/path/to/src/File.php');

// Minimal (no stat info)
$entry = IndexEntry::create('src/File.php', $blobId);

// With explicit mode
$entry = IndexEntry::create('deploy.sh', $blobId, 0100755);

Modifica dell'indice

PHP
// Add or replace an entry
$index->addEntry($entry);

// Remove an entry
$index->removeEntry('src/OldFile.php');

// Replace all stages of a conflicted path with one resolved entry
$index->resolveConflict('src/File.php', $entry);

addEntries() e removeEntries() elaborano più voci o percorsi in un'unica passata. Qualsiasi modifica alle voci elimina le estensioni memorizzate (vedi sotto).

Scrittura dell'indice

PHP
use Pitmaster\Index\IndexWriter;

IndexWriter::write($index, '/path/to/.git/index');

Oppure tramite il repository, che gestisce l'operazione automaticamente:

PHP
$repo->add('src/File.php');    // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes

Formato binario

Codice
Signature:   DIRC (4 bytes, ASCII)
Version:     4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian

Formato delle voci (v2)

Ogni voce è composta da:

Codice
ctime seconds:     4 bytes, big-endian
ctime nanoseconds: 4 bytes, big-endian
mtime seconds:     4 bytes, big-endian
mtime nanoseconds: 4 bytes, big-endian
dev:               4 bytes, big-endian
ino:               4 bytes, big-endian
mode:              4 bytes, big-endian
uid:               4 bytes, big-endian
gid:               4 bytes, big-endian
file size:         4 bytes, big-endian
object hash:       20 bytes SHA-1 or 32 bytes SHA-256 (raw binary)
flags:             2 bytes, big-endian
path:              variable length, NUL-terminated
padding:           1-8 NUL bytes to align to 8-byte boundary

Il campo dei flag:

  • Bit 15: flag assume-valid
  • Bit 13-12: fase di merge (0-3)
  • Bit 11-0: lunghezza del nome del percorso, oppure 0xFFF quando il percorso è di 4095 byte o più (il lettore cerca quindi il NUL)

Differenze tra le versioni

Versione 2: il formato base descritto sopra. La più comune.

Versione 3: aggiunge flag estesi (2 byte dopo i flag regolari). Supporta i bit skip-worktree e intent-to-add. Viene usata quando una voce richiede flag estesi.

Versione 4: aggiunge la compressione dei prefissi dei percorsi. Invece di memorizzare i percorsi completi, ogni voce memorizza il numero di byte da rimuovere dal percorso della voce precedente, seguito dal nuovo suffisso. Riduce notevolmente le dimensioni dell'indice per gli alberi con directory annidate in profondità.

Fasi dei conflitti

Durante un conflitto di merge, un singolo percorso può avere più voci nell'indice in fasi diverse:

Fase Significato
0 Normale (nessun conflitto)
1 Versione di base (antenato comune)
2 La nostra (ramo corrente)
3 La loro (ramo in fase di merge)
PHP
// During a conflict, entries have non-zero stages
foreach ($index->allEntries() as $entry) {
    if ($entry->stage() > 0) {
        echo "Conflict: {$entry->path} (stage {$entry->stage()})\n";
    }
}

$index->hasUnmerged();            // true while conflicts remain
$index->unmergedPaths();          // conflicted paths
$index->stageEntries('a.txt');    // [1 => base, 2 => ours, 3 => theirs]

Estensioni dell'indice

Le estensioni vengono aggiunte dopo le voci, prima del checksum. Ogni estensione ha una firma di 4 byte, una dimensione di 4 byte e dati di lunghezza variabile.

Pitmaster conserva i blocchi di estensione come byte grezzi ($index->extensions() restituisce coppie signature e data) e li riscrive senza modifiche se le voci non sono state toccate. Non interpreta né genera alcuna estensione; ogni modifica alle voci le elimina tutte, cosa che git accetta e ricrea quando necessario. Le estensioni usate da git sono descritte di seguito come riferimento.

Estensione TREE

Memorizza nella cache oggetti albero precalcolati per le directory. Velocizza git status e git commit evitando di ricalcolare l'albero quando è cambiato solo l'indice.

Firma: TREE (0x54524545)

Estensione REUC

Registra le informazioni per annullare la risoluzione. Dopo aver risolto un conflitto, le voci originali delle fasi 1/2/3 vengono spostate qui, così possono essere ripristinate con git checkout -m.

Firma: REUC (0x52455543)

Altre estensioni

git scrive anche:

  • EOIE (fine della voce dell'indice): indica dove terminano le voci e iniziano le estensioni
  • IEOT (tabella degli offset delle voci dell'indice): consente il caricamento parallelo dell'indice
  • link (indice suddiviso): per i repository molto grandi che usano un indice suddiviso

Nel formato di git, le estensioni la cui firma inizia con una lettera maiuscola sono facoltative, mentre quelle con una lettera minuscola sono obbligatorie. Pitmaster accetta entrambi i tipi senza verificarli. L'indice suddiviso (link) non viene caricato, quindi i repository che lo usano non sono supportati.

Checksum

Il file indice termina con un checksum di tutti i byte precedenti, calcolato con l'hash del repository (20 byte SHA-1, 32 byte SHA-256). Pitmaster lo scrive al salvataggio, ma non lo verifica in lettura.

Index::open() e Index::parse() generano IndexParseException se manca la firma DIRC o se la versione non è supportata.

Indice e stato

L'indice è fondamentale per calcolare lo stato. WorkingTreeStatus confronta:

  1. Albero HEAD e indice: rileva le modifiche aggiunte allo staging
  2. Indice e albero di lavoro: rileva le modifiche non aggiunte allo staging

I campi stat (ctime, mtime, dev, ino, size, mode, uid, gid) consentono di controllare rapidamente se un file è cambiato senza leggerne il contenuto. Se i campi stat corrispondono e il file è stato modificato l'ultima volta prima dell'inizio dell'analisi dello stato, il file viene considerato invariato. Una dimensione diversa lo contrassegna subito come modificato. Altrimenti Pitmaster legge il file e ne calcola l'hash per verificare se il contenuto è effettivamente cambiato.

Flag assume-valid

PHP
if ($entry->assumeValid()) {
    // Entry has assume-valid set (flags & 0x8000)
}

Questo flag indica a git di non controllare il file durante il calcolo dello stato, anche se i campi stat sono cambiati (git update-index --assume-unchanged). Pitmaster conserva il bit quando riscrive le voci, ma il suo calcolo dello stato non lo rispetta e continua a confrontare il file.