Formát indexu
Soubor indexu (.git/index) je přípravná oblast. Zaznamenává soubory, které budou zahrnuty do příštího commitu, spolu s metadaty pro efektivní výpočet stavu. Pitmaster čte verze indexu 2, 3 a 4 a zapisuje verze 2 a 3. Zápisem indexu načteného jako verze 4 vznikne soubor, který git nedokáže přečíst (#15), proto taková úložiště před zápisem pomocí Pitmaster převeďte příkazem git update-index --index-version 2.
Čtení indexu
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() vrátí prázdný index, pokud soubor neexistuje. entries() vrátí pouze položky fáze 0; allEntries() zahrnuje fáze konfliktů a paths() vypíše sledované cesty.
IndexEntry
Každá položka indexu je objekt hodnoty pouze pro čtení:
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
Vytváření položek
// 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);
Úprava indexu
// 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() a removeEntries() zpracují několik položek nebo cest v jednom průchodu. Každá změna položek odstraní uložená rozšíření (viz níže).
Zápis indexu
use Pitmaster\Index\IndexWriter;
IndexWriter::write($index, '/path/to/.git/index');
Nebo přes úložiště (které to vyřídí automaticky):
$repo->add('src/File.php'); // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes
Binární formát
Hlavička
Signature: DIRC (4 bytes, ASCII)
Version: 4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian
Formát položky (v2)
Každá položka má podobu:
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
Pole příznaků:
- Bit 15: příznak assume-valid
- Bity 13–12: fáze sloučení (0–3)
- Bity 11–0: délka názvu cesty, nebo 0xFFF, pokud má cesta 4095 bajtů či více (čtenář pak hledá NUL)
Rozdíly mezi verzemi
Verze 2: základní formát popsaný výše. Nejběžnější.
Verze 3: přidává rozšířené příznaky (2 bajty navíc za běžnými příznaky). Podporuje bity skip-worktree a intent-to-add. Používá se, když některá položka potřebuje rozšířené příznaky.
Verze 4: přidává kompresi předpon cest. Místo ukládání celých cest každá položka ukládá počet bajtů, které se mají odebrat z cesty předchozí položky, a poté novou příponu. Výrazně zmenšuje index u hluboce vnořených stromů.
Fáze konfliktů
Během konfliktu při sloučení může mít jedna cesta více položek indexu v různých fázích:
| Fáze | Význam |
|---|---|
| 0 | Normální (bez konfliktu) |
| 1 | Základní verze (společný předek) |
| 2 | Naše (aktuální větev) |
| 3 | Jejich (slučovaná větev) |
// 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]
Rozšíření indexu
Rozšíření se připojují za položky před kontrolní součet. Každé rozšíření má 4bajtový podpis, 4bajtovou velikost a data proměnné délky.
Pitmaster uchovává bloky rozšíření jako surové bajty ($index->extensions() vrací dvojice signature a data) a při nezměněných položkách je zapíše zpět beze změny. Rozšíření neinterpretuje ani nevytváří a každá změna položek všechna rozšíření odstraní; git to přijme a podle potřeby znovu vytvoří. Rozšíření používaná gitem jsou pro informaci popsána níže.
Rozšíření TREE
Ukládá do mezipaměti předpočítané objekty stromů pro adresáře. Zrychluje git status a git commit, protože při změně pouze indexu není nutné strom znovu počítat.
Podpis: TREE (0x54524545)
Rozšíření REUC
Zaznamenává informace pro vrácení řešení konfliktu. Po vyřešení konfliktu se sem přesunou původní položky fází 1/2/3, aby je bylo možné obnovit pomocí git checkout -m.
Podpis: REUC (0x52455543)
Další rozšíření
git zapisuje také:
EOIE(konec položek indexu): označuje konec položek a začátek rozšířeníIEOT(tabulka offsetů položek indexu): umožňuje paralelní načítání indexulink(rozdělený index): pro velmi rozsáhlá úložiště používající rozdělený index
Ve formátu git jsou rozšíření, jejichž podpis začíná velkým písmenem, volitelná, zatímco ta s malým písmenem jsou povinná. Pitmaster přijímá oba druhy bez kontroly. Rozdělený index (link) se nenačítá, takže úložiště, která jej používají, nejsou podporována.
Kontrolní součet
Soubor indexu končí kontrolním součtem všech předchozích bajtů, vytvořeným pomocí hashe úložiště (20 bajtů SHA-1, 32 bajtů SHA-256). Pitmaster jej při uložení zapíše, ale při čtení neověřuje.
Index::open() a Index::parse() vyvolají IndexParseException, pokud chybí podpis DIRC nebo je verze nepodporovaná.
Index a stav
Index je zásadní pro výpočet stavu. WorkingTreeStatus porovnává:
- Strom HEAD a index: zjišťuje připravené změny
- Index a pracovní strom: zjišťuje nepřipravené změny
Statistická pole (ctime, mtime, dev, ino, size, mode, uid, gid) umožňují rychle ověřit, zda se soubor změnil, aniž by bylo nutné číst jeho obsah. Pokud se statistická pole shodují a soubor byl naposledy změněn před začátkem kontroly stavu, považuje se za nezměněný. Odlišná velikost jej okamžitě označí jako změněný. V opačném případě Pitmaster soubor přečte a vypočítá jeho hash, aby ověřil skutečné změny obsahu.
Příznak assume-valid
if ($entry->assumeValid()) {
// Entry has assume-valid set (flags & 0x8000)
}
Tento příznak říká gitu, aby při kontrole stavu soubor nekontroloval, i když se statistická pole změnila (git update-index --assume-unchanged). Pitmaster tento bit při přepisování položek zachová, ale jeho kontrola stavu na něj nebere ohled a soubor přesto porovná.