참조
Refs(참조)는 commit을 가리키는 이름 있는 포인터입니다. 브랜치, tag, HEAD, 원격 추적 ref는 모두 ref입니다. Pitmaster은 loose 파일, packed-refs, reftable에서 ref를 읽고 loose 파일과 packed-refs에 씁니다.
RefDatabase
RefDatabase은 먼저 loose refs를 검색한 뒤 reftable(저장소에 있는 경우), packed-refs 순으로 검색하는 복합 계층입니다.
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
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
HEAD는 다음 중 하나일 수 있는 특수 ref입니다.
심볼릭: 다른 ref를 가리킵니다(브랜치에 있을 때 가장 흔함).
ref: refs/heads/main\n
분리됨: commit 해시를 직접 가리킵니다.
abc123def456789012abc345def678901234abcd\n
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을 뜻합니다.
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 객체가 여러 개일 수도 있습니다).
$peeled = $store->peeled('refs/tags/v1.0.0');
// Returns the commit ObjectId, or null if not peeled
Ref 해석 순서
ref를 해석할 때 Pitmaster은 다음 순서로 확인합니다.
- Loose ref(
.git/refs/의 파일) - Reftable(
.git/reftable/tables.list가 있는 경우) - 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를 가리키는 참조입니다.
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>
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 작업
// 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');