Saltar al contenido

Formato del índice

El archivo de índice (.git/index) es el área de preparación. Registra los archivos que se incluirán en el siguiente commit, junto con metadatos que permiten calcular el estado de forma eficiente. Pitmaster lee las versiones 2, 3 y 4 del índice, y escribe las versiones 2 y 3. Escribir un índice leído como versión 4 genera un archivo que git no puede leer (#15); por tanto, convierte esos repositorios con git update-index --index-version 2 antes de escribir mediante Pitmaster.

Lectura del índice

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() devuelve un índice vacío si el archivo no existe. entries() devuelve solo las entradas de la etapa 0; allEntries() incluye las etapas de conflicto y paths() enumera las rutas con seguimiento.

IndexEntry

Cada entrada del índice es un objeto de valor de solo lectura:

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

Creación de entradas

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

Modificación del índice

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() y removeEntries() aceptan varias entradas o rutas en una sola pasada. Cualquier cambio en las entradas elimina las extensiones almacenadas (consulta más abajo).

Escritura del índice

PHP
use Pitmaster\Index\IndexWriter;

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

O a través del repositorio (que lo gestiona automáticamente):

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

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

Formato de entrada (v2)

Cada entrada tiene esta estructura:

Código
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

El campo de indicadores:

  • Bit 15: indicador de válido por defecto
  • Bits 13-12: etapa de fusión (0-3)
  • Bits 11-0: longitud del nombre de ruta, o 0xFFF cuando la ruta tiene 4095 bytes o más (el lector busca entonces el NUL)

Diferencias entre versiones

Versión 2: el formato base descrito arriba. Es la más común.

Versión 3: añade indicadores extendidos (2 bytes adicionales tras los indicadores normales). Admite los bits skip-worktree y intent-to-add. Se usa cuando alguna entrada necesita indicadores extendidos.

Versión 4: añade compresión de prefijos de ruta. En vez de almacenar rutas completas, cada entrada guarda el número de bytes que se deben quitar de la ruta de la entrada anterior, seguido del nuevo sufijo. Reduce considerablemente el tamaño del índice en árboles muy anidados.

Etapas de conflicto

Durante un conflicto de fusión, una ruta puede tener varias entradas de índice en distintas etapas:

Etapa Significado
0 Normal (sin conflicto)
1 Versión base (ancestro común)
2 La nuestra (rama actual)
3 La suya (rama que se está fusionando)
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]

Extensiones del índice

Las extensiones se añaden después de las entradas y antes de la suma de comprobación. Cada extensión tiene una firma de 4 bytes, un tamaño de 4 bytes y datos de longitud variable.

Pitmaster conserva los bloques de extensión como bytes sin procesar ($index->extensions() devuelve pares de signature y data) y los vuelve a escribir sin cambios si las entradas no se han modificado. No interpreta ni genera extensiones; cualquier cambio en las entradas las elimina todas, algo que git acepta y reconstruye cuando es necesario. A continuación se describen como referencia las extensiones que usa git.

Extensión TREE

Almacena en caché objetos de árbol precalculados para directorios. Acelera git status y git commit al evitar recalcular los árboles cuando solo ha cambiado el índice.

Firma: TREE (0x54524545)

Extensión REUC

Registra información para deshacer la resolución de conflictos. Una vez resuelto un conflicto, las entradas originales de las etapas 1/2/3 se trasladan aquí para poder restaurarlas con git checkout -m.

Firma: REUC (0x52455543)

Otras extensiones

git también escribe:

  • EOIE (final de la entrada del índice): indica dónde terminan las entradas y empiezan las extensiones
  • IEOT (tabla de desplazamientos de entradas del índice): permite cargar el índice en paralelo
  • link (índice dividido): para repositorios muy grandes que usan un índice dividido

En el formato de git, las extensiones cuya firma empieza por una letra mayúscula son opcionales; las que empiezan por una minúscula son obligatorias. Pitmaster acepta ambos tipos sin comprobarlos. No se carga un índice dividido (link), por lo que no se admiten los repositorios que lo usan.

Suma de comprobación

El archivo de índice termina con una suma de comprobación de todos los bytes anteriores, calculada con el algoritmo hash del repositorio (20 bytes SHA-1, 32 bytes SHA-256). Pitmaster la escribe al guardar, pero no la verifica al leer.

Index::open() y Index::parse() lanzan IndexParseException si falta la firma DIRC o si la versión no es compatible.

Índice y estado

El índice es fundamental para calcular el estado. WorkingTreeStatus compara:

  1. Árbol de HEAD e índice: detecta cambios preparados
  2. Índice y árbol de trabajo: detecta cambios sin preparar

Los campos stat (ctime, mtime, dev, ino, size, mode, uid, gid) permiten comprobar rápidamente si un archivo ha cambiado sin leer su contenido. Si los campos stat coinciden y el archivo se modificó por última vez antes de que empezara el análisis del estado, se considera que no ha cambiado. Un tamaño distinto indica de inmediato que se ha modificado. De lo contrario, Pitmaster lee el archivo y calcula su hash para comprobar si el contenido ha cambiado realmente.

Indicador assume-valid

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

Este indicador le dice a git que no compruebe el archivo durante el análisis del estado, aunque hayan cambiado los campos stat (git update-index --assume-unchanged). Pitmaster conserva el bit al reescribir las entradas, pero su cálculo del estado no lo respeta y sigue comparando el archivo.