Formatul indexului
Fișierul index (.git/index) este zona de pregătire. Înregistrează fișierele care vor fi incluse în următorul commit, împreună cu metadatele pentru calcularea eficientă a stării. Pitmaster citește versiunile 2, 3 și 4 ale indexului și scrie versiunile 2 și 3. Scrierea unui index citit ca versiunea 4 produce un fișier pe care git nu îl poate citi (#15), așa că convertiți astfel de depozite cu git update-index --index-version 2 înainte de a scrie prin Pitmaster.
Citirea indexului
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() returnează un index gol dacă fișierul nu există. entries() returnează numai intrările din etapa 0; allEntries() include etapele de conflict, iar paths() listează căile urmărite.
IndexEntry
Fiecare intrare din index este un obiect valoare readonly:
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
Crearea intrărilor
// 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);
Modificarea indexului
// 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() și removeEntries() procesează mai multe intrări sau căi într-o singură trecere. Orice modificare a intrărilor elimină extensiile stocate (consultați mai jos).
Scrierea indexului
use Pitmaster\Index\IndexWriter;
IndexWriter::write($index, '/path/to/.git/index');
Sau prin depozit (care gestionează automat acest lucru):
$repo->add('src/File.php'); // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes
Format binar
Antet
Signature: DIRC (4 bytes, ASCII)
Version: 4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian
Formatul intrării (v2)
Fiecare intrare este:
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
Câmpul de marcaje:
- Bitul 15: marcaj de validitate presupusă
- Biții 13-12: etapa de îmbinare (0-3)
- Biții 11-0: lungimea numelui căii sau 0xFFF când calea are 4095 octeți sau mai mult (cititorul caută apoi NUL)
Diferențe între versiuni
Versiunea 2: formatul de bază descris mai sus. Cel mai frecvent.
Versiunea 3: adaugă marcaje extinse (2 octeți suplimentari după marcajele obișnuite). Acceptă biții skip-worktree și intent-to-add. Se folosește când orice intrare necesită marcaje extinse.
Versiunea 4: adaugă comprimarea prefixelor căilor. În loc să stocheze căile complete, fiecare intrare stochează numărul de octeți care trebuie eliminați din calea intrării precedente, urmat de sufixul nou. Reduce semnificativ dimensiunea indexului pentru arborii imbricați adânc.
Etapele conflictului
În timpul unui conflict de îmbinare, o singură cale poate avea mai multe intrări în index, în etape diferite:
| Etapă | Semnificație |
|---|---|
| 0 | Normal (fără conflict) |
| 1 | Versiunea de bază (strămoș comun) |
| 2 | A noastră (ramura curentă) |
| 3 | A lor (ramura îmbinată) |
// 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]
Extensiile indexului
Extensiile sunt adăugate după intrări, înaintea sumei de verificare. Fiecare extensie are o semnătură de 4 octeți, o dimensiune de 4 octeți și date cu lungime variabilă.
Pitmaster păstrează blocurile de extensii ca octeți bruti ($index->extensions() returnează perechi signature și data) și le scrie neschimbate când intrările nu au fost modificate. Nu interpretează și nu generează nicio extensie, iar orice modificare a intrărilor le elimină pe toate; git le acceptă și le reconstruiește după cum este necesar. Extensiile folosite de git sunt descrise mai jos ca referință.
Extensia TREE
Memorează în cache obiecte arbore precomputate pentru directoare. Accelerează git status și git commit evitând recalcularea arborelui când s-a modificat doar indexul.
Semnătură: TREE (0x54524545)
Extensia REUC
Înregistrează informațiile pentru anularea rezolvării. După rezolvarea unui conflict, intrările originale din etapele 1/2/3 sunt mutate aici, astfel încât să poată fi restaurate cu git checkout -m.
Semnătură: REUC (0x52455543)
Alte extensii
git scrie și:
EOIE(sfârșitul intrării din index): marchează locul unde se termină intrările și încep extensiileIEOT(tabelul offseturilor intrărilor din index): permite încărcarea paralelă a indexuluilink(index împărțit): pentru depozite foarte mari care folosesc un index împărțit
În formatul lui git, extensiile a căror semnătură începe cu o literă majusculă sunt opționale, iar cele cu literă minusculă sunt obligatorii. Pitmaster le acceptă pe ambele fără verificare. Un index împărțit (link) nu este încărcat, așadar depozitele care folosesc unul nu sunt acceptate.
Sumă de verificare
Fișierul index se încheie cu o sumă de verificare a tuturor octeților anteriori, calculată folosind hashul depozitului (20 de octeți SHA-1, 32 de octeți SHA-256). Pitmaster o scrie la salvare, dar nu o verifică la citire.
Index::open() și Index::parse() aruncă IndexParseException dacă lipsește semnătura DIRC sau dacă versiunea nu este acceptată.
Indexul și starea
Indexul este esențial pentru calcularea stării. WorkingTreeStatus compară:
- Arborele HEAD cu indexul: detectează modificările pregătite
- Indexul cu arborele de lucru: detectează modificările nepregătite
Câmpurile stat (ctime, mtime, dev, ino, size, mode, uid, gid) permit verificări rapide de tipul „s-a schimbat acest fișier?” fără citirea conținutului fișierului. Dacă valorile câmpurilor stat corespund, iar fișierul a fost modificat ultima dată înainte de începerea scanării stării, se presupune că fișierul nu s-a schimbat. O dimensiune diferită îl marchează imediat ca modificat. În caz contrar, Pitmaster citește fișierul și îi calculează hashul pentru a verifica dacă s-a schimbat efectiv conținutul.
Marcajul de validitate presupusă
if ($entry->assumeValid()) {
// Entry has assume-valid set (flags & 0x8000)
}
Acest marcaj îi spune lui git să nu verifice fișierul în timpul calculării stării, chiar dacă valorile câmpurilor stat s-au schimbat (git update-index --assume-unchanged). Pitmaster păstrează bitul când rescrie intrările, dar calcularea stării nu ține cont de el și continuă să compare fișierul.