Pular para o conteúdo

Formato do índice

O arquivo de índice (.git/index) é a área de preparação. Ele registra os arquivos que serão incluídos no próximo commit, junto com metadados para calcular o status com eficiência. Pitmaster lê as versões 2, 3 e 4 do índice e grava as versões 2 e 3. Gravar um índice que foi lido como versão 4 produz um arquivo que git não consegue ler (#15), portanto converta esses repositórios com git update-index --index-version 2 antes de gravar usando Pitmaster.

Lendo o í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() retorna um índice vazio quando o arquivo não existe. entries() retorna apenas entradas do estágio 0; allEntries() inclui estágios de conflito, e paths() lista os caminhos rastreados.

IndexEntry

Cada entrada do índice é um objeto de valor somente para leitura:

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

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

Modificando o í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() e removeEntries() processam várias entradas ou caminhos em uma única passagem. Qualquer alteração nas entradas descarta as extensões armazenadas (veja abaixo).

Gravando o índice

PHP
use Pitmaster\Index\IndexWriter;

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

Ou pelo repositório (que trata disso automaticamente):

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 binário

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

Formato das entradas (v2)

Cada entrada é composta por:

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

O campo de flags:

  • Bit 15: flag assume-valid
  • Bits 13-12: estágio de merge (0-3)
  • Bits 11-0: comprimento do nome do caminho ou 0xFFF quando o caminho tem 4095 bytes ou mais (o leitor então procura o NUL)

Diferenças entre versões

Versão 2: o formato básico descrito acima. O mais comum.

Versão 3: adiciona flags estendidas (2 bytes extras após as flags normais). É compatível com os bits skip-worktree e intent-to-add. Usada quando alguma entrada precisa de flags estendidas.

Versão 4: adiciona compressão de prefixos de caminho. Em vez de armazenar caminhos completos, cada entrada armazena o número de bytes a remover do caminho da entrada anterior, seguido pelo novo sufixo. Reduz significativamente o tamanho do índice em árvores profundamente aninhadas.

Estágios de conflito

Durante um conflito de merge, um único caminho pode ter várias entradas de índice em estágios diferentes:

Estágio Significado
0 Normal (sem conflito)
1 Versão base (ancestral comum)
2 Nossa versão (branch atual)
3 Versão deles (branch em processo de merge)
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]

Extensões do índice

As extensões são anexadas após as entradas, antes da soma de verificação. Cada extensão tem uma assinatura de 4 bytes, um tamanho de 4 bytes e dados de comprimento variável.

Pitmaster mantém os blocos de extensão como bytes brutos ($index->extensions() retorna pares signature e data) e os grava novamente sem alterações quando as entradas não foram modificadas. Ele não interpreta nem gera nenhuma extensão, e qualquer alteração nas entradas descarta todas elas, que git aceita e recria conforme necessário. As extensões usadas por git são descritas abaixo para referência.

Extensão TREE

Armazena em cache objetos de árvore pré-computados para diretórios. Acelera git status e git commit, evitando recalcular a árvore quando apenas o índice foi alterado.

Assinatura: TREE (0x54524545)

Extensão REUC

Registra informações de desfazer resolução. Após a resolução de um conflito, as entradas originais dos estágios 1/2/3 são movidas para cá para que possam ser restauradas com git checkout -m.

Assinatura: REUC (0x52455543)

Outras extensões

git também grava:

  • EOIE (fim da entrada do índice): marca onde as entradas terminam e as extensões começam
  • IEOT (tabela de deslocamentos das entradas do índice): permite o carregamento paralelo do índice
  • link (índice dividido): para repositórios muito grandes que usam índice dividido

No formato de git, extensões cuja assinatura começa com uma letra maiúscula são opcionais, e as que começam com uma minúscula são obrigatórias. Pitmaster aceita os dois tipos sem verificá-los. Um índice dividido (link) não é carregado, então repositórios que o usam não são compatíveis.

Soma de verificação

O arquivo de índice termina com uma soma de verificação de todos os bytes anteriores, usando o hash do repositório (20 bytes SHA-1, 32 bytes SHA-256). Pitmaster a grava ao salvar, mas não a verifica na leitura.

Index::open() e Index::parse() lançam IndexParseException se a assinatura DIRC estiver ausente ou se a versão não for compatível.

Índice e status

O índice é central para o cálculo do status. WorkingTreeStatus compara:

  1. Árvore HEAD com índice: detecta alterações preparadas
  2. Índice com árvore de trabalho: detecta alterações não preparadas

Os campos stat (ctime, mtime, dev, ino, size, mode, uid, gid) permitem verificar rapidamente se um arquivo foi alterado sem ler seu conteúdo. Se os campos stat corresponderem e o arquivo tiver sido modificado pela última vez antes do início da verificação de status, ele será considerado inalterado. Um tamanho diferente o marca como modificado imediatamente. Caso contrário, Pitmaster lê o arquivo e calcula seu hash para verificar se o conteúdo realmente mudou.

Flag assume-valid

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

Essa flag informa git para ignorar a verificação do arquivo durante a consulta de status, mesmo que os campos stat tenham mudado (git update-index --assume-unchanged). Pitmaster mantém o bit ao regravar entradas, mas seu status não o respeita e ainda compara o arquivo.