본문으로 건너뛰기

인덱스 형식

인덱스 파일(.git/index)은 스테이징 영역입니다. 다음 커밋에 포함될 파일과 상태 계산을 효율적으로 수행하기 위한 메타데이터를 기록합니다. Pitmaster은 인덱스 버전 2, 3 및 4를 읽고 버전 2와 3을 씁니다. 버전 4로 읽은 인덱스를 쓰면 git이 읽을 수 없는 파일이 생성되므로(#15), Pitmaster를 통해 쓰기 전에 git update-index --index-version 2로 해당 저장소를 변환하세요.

인덱스 읽기

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: 경로 이름 길이. 경로가 4095바이트 이상이면 0xFFF이며(이 경우 읽기 작업에서 NUL을 검색합니다)

버전별 차이

버전 2: 위에서 설명한 기본 형식입니다. 가장 일반적으로 사용됩니다.

버전 3: 확장 플래그(일반 플래그 뒤에 추가 바이트 2개)가 추가됩니다. skip-worktree 및 intent-to-add 비트를 지원합니다. 확장 플래그가 필요한 항목이 있을 때 사용됩니다.

버전 4: 경로 접두사 압축이 추가됩니다. 전체 경로를 저장하는 대신 각 항목은 이전 항목 경로에서 제거할 바이트 수를 저장한 다음 새 접미사를 저장합니다. 깊게 중첩된 트리에서 인덱스 크기를 크게 줄입니다.

충돌 단계

병합 충돌이 발생하면 하나의 경로에 서로 다른 단계의 인덱스 항목이 여러 개 있을 수 있습니다.

단계 의미
0 일반(충돌 없음)
1 기본 버전(공통 조상)
2 ours(현재 브랜치)
3 theirs(병합 중인 브랜치)
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)는 로드되지 않으므로 이를 사용하는 저장소는 지원되지 않습니다.

체크섬

인덱스 파일의 끝에는 저장소의 해시를 사용한 앞선 모든 바이트의 체크섬이 있습니다(SHA-1은 20바이트, SHA-256은 32바이트). Pitmaster는 저장할 때 체크섬을 쓰지만 읽을 때는 확인하지 않습니다.

Index::open()와 Index::parse()은 DIRC 서명이 없거나 지원되지 않는 버전이면 IndexParseException를 발생시킵니다.

인덱스와 상태

인덱스는 상태 계산의 핵심입니다. 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)
}

이 플래그는 stat 필드가 변경되었더라도 상태 확인 중 파일 검사를 건너뛰도록 git에 알립니다(git update-index --assume-unchanged). Pitmaster은 항목을 다시 쓸 때 이 비트를 유지하지만, 상태 확인에서는 이를 적용하지 않고 파일을 계속 비교합니다.