Към съдържанието

Формат на индекса

Файлът на индекса (.git/index) е подготвителната област. Той записва файловете, които ще бъдат включени в следващия commit, заедно с метаданни за ефективно изчисляване на статуса. Pitmaster чете версии 2, 3 и 4 на индекса и записва версии 2 и 3. Записването на индекс, прочетен като версия 4, създава файл, който git не може да прочете (#15), затова преобразувайте такива хранилища с git update-index --index-version 2, преди да записвате чрез Pitmaster.

Четене на индекса

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() връща празен индекс, ако файлът не съществува. entries() връща само записите от етап 0; allEntries() включва етапите на конфликт, а paths() изброява проследяваните пътища.

IndexEntry

Всеки запис в индекса е обект-стойност само за четене:

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

Създаване на записи

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

Промяна на индекса

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() и removeEntries() обработват няколко записа или пътя наведнъж. Всяка промяна в записите премахва съхранените разширения (вижте по-долу).

Записване на индекса

PHP
use Pitmaster\Index\IndexWriter;

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

Или чрез хранилището (което обработва това автоматично):

PHP
$repo->add('src/File.php');    // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes

Двоичен формат

Код
Signature:   DIRC (4 bytes, ASCII)
Version:     4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian

Формат на запис (v2)

Всеки запис има следния вид:

Код
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

Полето с флагове:

  • Бит 15: флаг за приемане като валиден
  • Битове 13-12: етап на сливане (0-3)
  • Битове 11-0: дължина на името на пътя или 0xFFF, когато пътят е с дължина 4095 байта или повече (тогава четецът търси NUL)

Разлики между версиите

Версия 2: основният формат, описан по-горе. Най-разпространен.

Версия 3: добавя разширени флагове (2 допълнителни байта след обичайните флагове). Поддържа битовете skip-worktree и intent-to-add. Използва се, когато някой запис изисква разширени флагове.

Версия 4: добавя компресиране на префиксите на пътищата. Вместо да съхранява пълните пътища, всеки запис съдържа броя байтове за премахване от пътя на предишния запис, последван от новата наставка. Значително намалява размера на индекса при дълбоко вложени дървета.

Етапи на конфликт

При конфликт при сливане един път може да има няколко записа в индекса на различни етапи:

Етап Значение
0 Нормален (без конфликт)
1 Базова версия (общ предшественик)
2 Нашата версия (текущият клон)
3 Тяхната версия (клонът, който се слива)
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]

Разширения на индекса

Разширенията се добавят след записите и преди контролната сума. Всяко разширение има 4-байтов подпис, 4-байтов размер и данни с променлива дължина.

Pitmaster запазва блоковете с разширения като необработени байтове ($index->extensions() връща двойки signature и data) и ги записва обратно без промяна, когато записите не са променени. Не интерпретира и не генерира разширения; всяка промяна в записите премахва всички разширения, което git приема и при нужда изгражда отново. Използваните от git разширения са описани по-долу за справка.

Разширение TREE

Кешира предварително изчислени дървовидни обекти за директориите. Ускорява git status и git commit, като избягва повторното изчисляване на дърветата, когато е променен само индексът.

Подпис: TREE (0x54524545)

Разширение REUC

Записва информация за отмяна на разрешаването. След разрешаване на конфликт първоначалните записи от етапи 1/2/3 се преместват тук, за да могат да бъдат възстановени с git checkout -m.

Подпис: REUC (0x52455543)

Други разширения

git записва и:

  • EOIE (край на записа в индекса): отбелязва края на записите и началото на разширенията
  • IEOT (таблица с отместванията на записите в индекса): позволява паралелно зареждане на индекса
  • link (разделен индекс): за много големи хранилища, използващи разделен индекс

Във формата на git разширенията, чийто подпис започва с главна буква, са незадължителни, а тези с малка буква са задължителни. Pitmaster приема и двата вида без проверка. Разделен индекс (link) не се зарежда, затова хранилищата, които го използват, не се поддържат.

Контролна сума

Файлът на индекса завършва с контролна сума на всички предходни байтове, изчислена с хеш функцията на хранилището (20 байта SHA-1, 32 байта SHA-256). Pitmaster я записва при запазване, но не я проверява при четене.

Index::open() и Index::parse() извеждат IndexParseException при липсващ подпис DIRC или неподдържана версия.

Индекс и статус

Индексът е ключов за изчисляването на статуса. WorkingTreeStatus сравнява:

  1. Дървото HEAD с индекса: открива подготвени промени
  2. Индекса с работното дърво: открива неподготвени промени

Полетата stat (ctime, mtime, dev, ino, size, mode, uid, gid) позволяват бързи проверки от типа „променен ли е този файл?“, без да се чете съдържанието му. Ако полетата stat съвпадат и файлът е бил променен за последно преди началото на сканирането за статус, файлът се счита за непроменен. Различният размер го маркира като променен веднага. В противен случай Pitmaster чете файла и изчислява хеша му, за да провери дали съдържанието действително се е променило.

Флаг за приемане като валиден

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

Този флаг указва на git да пропусне проверката на файла при изчисляване на статуса, дори ако полетата stat са се променили (git update-index --assume-unchanged). Pitmaster запазва бита при презаписване на записите, но изчисляването на статуса му не го зачита и все пак сравнява файла.