본문으로 건너뛰기

참조

Refs(참조)는 commit을 가리키는 이름 있는 포인터입니다. 브랜치, tag, HEAD, 원격 추적 ref는 모두 ref입니다. Pitmaster은 loose 파일, packed-refs, reftable에서 ref를 읽고 loose 파일과 packed-refs에 씁니다.

RefDatabase

RefDatabase은 먼저 loose refs를 검색한 뒤 reftable(저장소에 있는 경우), packed-refs 순으로 검색하는 복합 계층입니다.

PHP
use Pitmaster\Ref\RefDatabase;

$refs = new RefDatabase($gitDir, $commonDir);

// Resolve a ref to an ObjectId
$id = $refs->resolve('refs/heads/main');

// Resolve HEAD (follows symbolic refs)
$headId = $refs->resolveHead();

// Read HEAD as a symbolic ref (null when HEAD is detached)
$symref = $refs->readHead();
echo $symref?->target;  // 'refs/heads/main'

// List all refs
foreach ($refs->list() as $name => $id) {
    echo "{$id->hex} {$name}\n";
}

// Update a ref
$refs->update('refs/heads/main', $newCommitId);

// Delete a ref
$refs->delete('refs/heads/old-branch');

연결된 worktree의 경우 RefDatabase은 HEAD와 worktree별 의사 ref(ORIG_HEAD, MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD, REBASE_HEAD, BISECT_HEAD)를 worktree별 git 디렉터리에 둡니다. 브랜치, tag 및 기타 refs/ 항목은 공통 디렉터리에 기록하며, packed-refs은 항상 공통 디렉터리에서 읽습니다.

느슨한 참조

Loose refs는 .git/refs/에 있는 개별 파일입니다. 각 파일에는 대상 객체의 16진수 해시(40자, SHA-256의 경우 64자)와 그 뒤의 줄바꿈 문자가 들어 있습니다.

코드
.git/refs/heads/main           -> abc123def456...\n
.git/refs/tags/v1.0.0          -> 789012abc345...\n
.git/refs/remotes/origin/main  -> abc123def456...\n
PHP
use Pitmaster\Ref\LooseRefStore;

$store = new LooseRefStore($gitDir, $commonDir); // $commonDir defaults to $gitDir

$id = $store->resolve('refs/heads/main');
$store->update('refs/heads/main', $commitId);
$store->delete('refs/heads/old');

HEAD는 다음 중 하나일 수 있는 특수 ref입니다.

심볼릭: 다른 ref를 가리킵니다(브랜치에 있을 때 가장 흔함).

코드
ref: refs/heads/main\n

분리됨: commit 해시를 직접 가리킵니다.

코드
abc123def456789012abc345def678901234abcd\n
PHP
use Pitmaster\Ref\SymbolicRef;

$symref = SymbolicRef::parse('HEAD', $headContent);

if ($symref !== null) {
    echo "On branch: {$symref->target}\n";
    // 'refs/heads/main'
} else {
    echo "Detached HEAD\n";
}

패킹된 참조

packed-refs 파일은 효율성을 위해 여러 ref를 하나의 파일로 합칩니다. Git은 git pack-refs 또는 git gc 중에 이 파일을 만듭니다.

코드
# pack-refs with: peeled fully-peeled sorted
abc123def456789012abc345def678901234abcd refs/heads/main
789012abc345def678901234abcdabc123def456 refs/tags/v1.0.0
^def456789012abc345def678901234abcdabc12

^으로 시작하는 줄은 peeled 값입니다. 주석이 달린 tag가 최종적으로 가리키는 commit을 뜻합니다.

PHP
use Pitmaster\Ref\PackedRefStore;

$store = new PackedRefStore($commonDir);

$id = $store->resolve('refs/heads/main');

foreach ($store->list() as $name => $id) {
    echo "{$id->hex} {$name}\n";
}

Peeled 값

주석이 달린 tag의 경우 packed-refs 파일에 tag ref 다음 줄에 peeled(역참조된) 값이 포함될 수 있습니다. peeled 값은 tag 객체가 최종적으로 가리키는 commit입니다(tag 객체가 여러 개일 수도 있습니다).

PHP
$peeled = $store->peeled('refs/tags/v1.0.0');
// Returns the commit ObjectId, or null if not peeled

Ref 해석 순서

ref를 해석할 때 Pitmaster은 다음 순서로 확인합니다.

  1. Loose ref(.git/refs/의 파일)
  2. Reftable(.git/reftable/tables.list가 있는 경우)
  3. Packed ref(.git/packed-refs의 항목)

Loose refs가 우선합니다. Packed ref를 업데이트하면 새 값은 loose ref로 기록됩니다(packed-refs 파일은 다시 쓰지 않음). ref를 삭제하면 loose 파일과 packed 항목이 모두 제거됩니다. $repo->packRefs()은 현재 refs를 기준으로 packed-refs을 다시 쓰고 loose 파일을 제거합니다.

SymbolicRef

심볼릭 ref는 객체를 직접 가리키는 대신 다른 ref를 가리키는 참조입니다.

PHP
use Pitmaster\Ref\SymbolicRef;

// Parse
$ref = SymbolicRef::parse('HEAD', "ref: refs/heads/main\n");
echo $ref->name;    // 'HEAD'
echo $ref->target;  // 'refs/heads/main'

// Update
$refs->updateSymbolic('HEAD', 'refs/heads/feature');

Reflog

reflog에는 ref가 변경된 시점이 기록되므로 git reflog에서 HEAD와 브랜치 끝의 기록을 볼 수 있습니다.

Reflog 파일은 .git/logs/에 있습니다.

코드
.git/logs/HEAD
.git/logs/refs/heads/main
.git/logs/refs/stash

각 줄에 기록되는 항목:

코드
<old-hash> <new-hash> <author> <timestamp> <tz>\t<message>
PHP
use Pitmaster\Ref\Reflog;

$reflog = Reflog::open($gitDir, 'HEAD');

// Read entries
$entries = $reflog->entries();
foreach ($entries as $entry) {
    echo "{$entry['old']} -> {$entry['new']}: {$entry['message']}\n";
}

// Append an entry
$reflog->append(
    $oldId,
    $newId,
    'Name <email> 1700000000 +0000',
    'commit: Add feature',
);

stash는 refs/stash의 reflog를 스택 저장소로 사용합니다.

Reftable

Reftable은 수천 개의 브랜치가 있는 대규모 저장소에서 성능을 높이도록 설계된 최신 바이너리 ref 저장 형식입니다. loose refs와 packed-refs를 모두 대체합니다.

Reftable 파일은 .git/reftable/에 저장되며 다음이 들어 있습니다.

  • Ref 레코드(이름순 정렬)
  • 로그 레코드(reflog 항목)
  • 빠른 조회를 위한 블록 수준 인덱싱

Pitmaster은 reftable 파일이 있을 때 해당 파일의 ref 레코드를 읽지만 로그 레코드는 읽지 않습니다. reftable이 있으면 목록 조회에서 packed-refs을 대체합니다. ref 업데이트는 항상 loose 파일로 기록되지만 git은 reftable 저장소에서 이를 읽지 않으므로, reftable 저장소는 읽기 전용으로 취급하세요.

Ref 이름 지정 규칙

패턴 의미
refs/heads/<name> 로컬 브랜치
refs/tags/<name> tag(경량 또는 주석이 달린 tag)
refs/remotes/<remote>/<name> 원격 추적 브랜치
refs/stash stash 스택
refs/notes/<namespace> Git 노트
HEAD 현재 체크아웃(심볼릭 또는 분리됨)
FETCH_HEAD 마지막 fetch 결과(git에서 기록하며, Pitmaster의 fetch()은 기록하지 않음)
MERGE_HEAD 병합 중인 브랜치(병합 중)
ORIG_HEAD 이전 HEAD(위험한 작업 전)

Repository를 통한 refs 작업

PHP
// Read
$branch = $repo->branch();          // Current branch name
$branches = $repo->branches();      // All branch names
$tags = $repo->tags();              // All tag names
$refs = $repo->allRefs();           // All refs as name => hash

// Write
$repo->createBranch('feature');     // Create refs/heads/feature
$repo->deleteBranch('feature');     // Delete refs/heads/feature
$repo->createTag('v1.0', 'Release');// Create annotated tag
$repo->createLightweightTag('v1.0-lite'); // Create lightweight tag
$repo->updateRef('refs/heads/main', $id); // Direct ref update

// Resolve
$id = $repo->resolve('HEAD');
$id = $repo->resolve('main');
$id = $repo->resolve('v1.0.0');
$id = $repo->resolve('HEAD~3');