Indexformat
Die Indexdatei (.git/index) ist der Staging-Bereich. Sie speichert die Dateien für den nächsten Commit sowie Metadaten, um den Status effizient zu ermitteln. Pitmaster liest die Indexversionen 2, 3 und 4 und schreibt die Versionen 2 und 3. Wird ein Index der Version 4 geschrieben, kann git die Datei nicht lesen (#15); wandle solche Repositories daher mit git update-index --index-version 2 um, bevor du mit Pitmaster schreibst.
Index lesen
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() gibt einen leeren Index zurück, wenn die Datei nicht existiert. entries() gibt nur Einträge der Stufe 0 zurück; allEntries() schließt Konfliktstufen ein, und paths() listet die nachverfolgten Pfade auf.
IndexEntry
Jeder Eintrag im Index ist ein schreibgeschütztes Wertobjekt:
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
Einträge erstellen
// 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);
Index ändern
// 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() und removeEntries() verarbeiten mehrere Einträge oder Pfade in einem Durchgang. Jede Änderung an den Einträgen verwirft die gespeicherten Erweiterungen (siehe unten).
Index schreiben
use Pitmaster\Index\IndexWriter;
IndexWriter::write($index, '/path/to/.git/index');
Oder über das Repository, das dies automatisch übernimmt:
$repo->add('src/File.php'); // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes
Binärformat
Header
Signature: DIRC (4 bytes, ASCII)
Version: 4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian
Eintragsformat (v2)
Jeder Eintrag sieht so aus:
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
Das Flags-Feld:
- Bit 15: assume-valid-Flag
- Bits 13-12: Merge-Stufe (0-3)
- Bits 11-0: Länge des Pfadnamens oder 0xFFF, wenn der Pfad mindestens 4095 Byte lang ist (der Reader sucht dann nach dem NUL-Zeichen)
Versionsunterschiede
Version 2: das oben beschriebene Basisformat. Am häufigsten verwendet.
Version 3: ergänzt erweiterte Flags (2 zusätzliche Bytes nach den regulären Flags). Unterstützt die Bits skip-worktree und intent-to-add. Wird verwendet, wenn ein Eintrag erweiterte Flags benötigt.
Version 4: ergänzt eine Präfixkomprimierung für Pfade. Statt vollständiger Pfade speichert jeder Eintrag die Anzahl der Bytes, die vom Pfad des vorherigen Eintrags entfernt werden, gefolgt vom neuen Suffix. Das reduziert die Indexgröße bei tief verschachtelten Trees erheblich.
Konfliktstufen
Bei einem Merge-Konflikt kann ein einzelner Pfad mehrere Indexeinträge auf unterschiedlichen Stufen haben:
| Stufe | Bedeutung |
|---|---|
| 0 | Normal (kein Konflikt) |
| 1 | Basisversion (gemeinsamer Vorfahr) |
| 2 | Unsere Version (aktueller Branch) |
| 3 | Deren Version (zusammenzuführender Branch) |
// 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]
Indexerweiterungen
Erweiterungen werden nach den Einträgen und vor der Prüfsumme angehängt. Jede Erweiterung hat eine 4-Byte-Signatur, eine 4-Byte-Größe und Daten variabler Länge.
Pitmaster speichert Erweiterungsblöcke als Rohbytes ($index->extensions() gibt signature- und data-Paare zurück) und schreibt sie unverändert zurück, wenn die Einträge nicht geändert wurden. Es interpretiert oder erzeugt keine Erweiterungen. Jede Änderung an den Einträgen verwirft alle Erweiterungen; git akzeptiert das und erstellt sie bei Bedarf neu. Die von git verwendeten Erweiterungen werden unten zur Referenz beschrieben.
TREE-Erweiterung
Speichert vorberechnete Tree-Objekte für Verzeichnisse zwischen. Beschleunigt git status und git commit, da Trees nicht neu berechnet werden müssen, wenn sich nur der Index geändert hat.
Signatur: TREE (0x54524545)
REUC-Erweiterung
Speichert Informationen zum Rückgängigmachen einer Konfliktauflösung. Nach dem Beheben eines Konflikts werden die ursprünglichen Einträge der Stufen 1/2/3 hierher verschoben, damit sie mit git checkout -m wiederhergestellt werden können.
Signatur: REUC (0x52455543)
Weitere Erweiterungen
git schreibt außerdem:
EOIE(Ende des Indexeintrags): markiert, wo die Einträge enden und die Erweiterungen beginnenIEOT(Offset-Tabelle der Indexeinträge): ermöglicht paralleles Laden des Indexlink(geteilter Index): für sehr große Repositories mit geteiltem Index
Im git-Format sind Erweiterungen mit einer Signatur, die mit einem Großbuchstaben beginnt, optional; solche mit einem Kleinbuchstaben sind erforderlich. Pitmaster akzeptiert beide Arten ohne Prüfung. Ein geteilter Index (link) wird nicht geladen; Repositories, die ihn verwenden, werden daher nicht unterstützt.
Prüfsumme
Die Indexdatei endet mit einer Prüfsumme aller vorherigen Bytes, berechnet mit dem Hashalgorithmus des Repositorys (20 Bytes SHA-1, 32 Bytes SHA-256). Pitmaster schreibt sie beim Speichern, prüft sie beim Lesen aber nicht.
Index::open() und Index::parse() werfen IndexParseException, wenn die Signatur DIRC fehlt oder die Version nicht unterstützt wird.
Index und Status
Der Index ist zentral für die Statusermittlung. WorkingTreeStatus vergleicht:
- HEAD-Tree mit Index: erkennt bereitgestellte Änderungen
- Index mit Arbeitsbaum: erkennt nicht bereitgestellte Änderungen
Die Stat-Felder (ctime, mtime, dev, ino, size, mode, uid, gid) ermöglichen schnelle Prüfungen, ob sich eine Datei geändert hat, ohne ihren Inhalt zu lesen. Stimmen die Stat-Felder überein und wurde die Datei vor Beginn der Statusprüfung zuletzt geändert, wird angenommen, dass sie unverändert ist. Eine abweichende Größe kennzeichnet sie sofort als geändert. Andernfalls liest Pitmaster die Datei und berechnet ihren Hash, um tatsächliche Inhaltsänderungen zu prüfen.
Assume-valid-Flag
if ($entry->assumeValid()) {
// Entry has assume-valid set (flags & 0x8000)
}
Dieses Flag weist git an, die Datei bei der Statusprüfung nicht zu überprüfen, selbst wenn sich die Stat-Felder geändert haben (git update-index --assume-unchanged). Pitmaster behält das Bit beim Neuschreiben von Einträgen bei, berücksichtigt es bei der Statusprüfung jedoch nicht und vergleicht die Datei weiterhin.