Zum Inhalt springen

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

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() 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:

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

Einträge erstellen

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

Index ändern

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

PHP
use Pitmaster\Index\IndexWriter;

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

Oder über das Repository, das dies automatisch übernimmt:

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

Code
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:

Code
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)
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]

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 beginnen
  • IEOT (Offset-Tabelle der Indexeinträge): ermöglicht paralleles Laden des Index
  • link (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:

  1. HEAD-Tree mit Index: erkennt bereitgestellte Änderungen
  2. 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

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