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
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:
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
// 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
// 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
use Pitmaster\Index\IndexWriter;
IndexWriter::write($index, '/path/to/.git/index');
Oppure tramite il repository, che gestisce l'operazione automaticamente:
$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
Intestazione
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:
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) |
// 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 estensioniIEOT(tabella degli offset delle voci dell'indice): consente il caricamento parallelo dell'indicelink(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:
- Albero HEAD e indice: rileva le modifiche aggiunte allo staging
- 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
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.