
# 참조

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

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