Saltar al contenido

Refs

Las refs (referencias) son punteros con nombre a commits. Las ramas, etiquetas, HEAD y refs de seguimiento remoto son todas refs. Pitmaster lee refs de archivos sueltos, packed-refs y reftable, y escribe archivos sueltos y packed-refs.

RefDatabase

RefDatabase es la capa compuesta que busca primero refs sueltas, luego reftable (cuando el repositorio tiene uno) y, por último, 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');

En worktrees vinculados, RefDatabase mantiene HEAD y las demás pseudo-refs por worktree (ORIG_HEAD, MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD, REBASE_HEAD, BISECT_HEAD) en el directorio git de cada worktree. Las ramas, etiquetas y otras entradas refs/ se escriben en el directorio común, y packed-refs siempre se lee desde el directorio común.

Refs sueltas

Las refs sueltas son archivos individuales en .git/refs/. Cada archivo contiene el hash hexadecimal del objeto de destino (40 caracteres, o 64 para SHA-256), seguido de un salto de línea.

Código
.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 es una ref especial que puede ser:

Simbólica: apunta a otra ref (lo más habitual cuando se está en una rama):

Código
ref: refs/heads/main\n

Separada: apunta directamente al hash de un commit:

Código
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";
}

Refs empaquetadas

El archivo packed-refs combina muchas refs en un solo archivo para ahorrar espacio. Git lo crea durante git pack-refs o git gc.

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

Las líneas que comienzan con ^ son valores desreferenciados: el commit al que apunta finalmente una etiqueta anotada.

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";
}

Valores desreferenciados

En las etiquetas anotadas, el archivo packed-refs puede incluir el valor desreferenciado en la línea siguiente a la ref de la etiqueta. Este valor es el commit al que apunta finalmente el objeto de etiqueta (posiblemente a través de varios objetos de etiqueta).

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

Orden de resolución de refs

Al resolver una ref, Pitmaster comprueba en este orden:

  1. Ref suelta (archivo en .git/refs/)
  2. Reftable, cuando existe .git/reftable/tables.list
  3. Ref empaquetada (entrada en .git/packed-refs)

Las refs sueltas tienen prioridad. Cuando se actualiza una ref empaquetada, el nuevo valor se escribe como ref suelta (el archivo packed-refs no se vuelve a escribir). Al eliminar una ref, se quitan tanto el archivo suelto como la entrada empaquetada. $repo->packRefs() vuelve a escribir packed-refs a partir de las refs actuales y elimina los archivos sueltos.

SymbolicRef

Las refs simbólicas son referencias que apuntan a otras referencias en lugar de hacerlo directamente a objetos.

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

El reflog registra cuándo cambian las refs, lo que permite a git reflog mostrar el historial de HEAD y las puntas de las ramas.

Los archivos Reflog se encuentran en .git/logs/:

Código
.git/logs/HEAD
.git/logs/refs/heads/main
.git/logs/refs/stash

Cada línea registra:

Código
<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',
);

El stash usa el reflog de refs/stash como almacenamiento de pila.

Reftable

Reftable es un formato binario más reciente para almacenar refs, diseñado para ofrecer mejor rendimiento con muchas refs (miles de ramas). Sustituye tanto las refs sueltas como packed-refs.

Los archivos Reftable se almacenan en .git/reftable/ y contienen:

  • Registros de refs (ordenados por nombre)
  • Registros de log (entradas de reflog)
  • Índices por bloque para búsquedas rápidas

Pitmaster lee los registros de refs de los archivos reftable cuando están presentes; no lee los registros de log. Cuando hay un reftable, este sustituye a packed-refs para los listados. Las actualizaciones de refs siempre se escriben como archivos sueltos, que git no lee en un repositorio reftable, así que trate los repositorios reftable como de solo lectura.

Convenciones para nombrar refs

Patrón Significado
refs/heads/<name> Rama local
refs/tags/<name> Etiqueta (ligera o anotada)
refs/remotes/<remote>/<name> Rama de seguimiento remoto
refs/stash Pila stash
refs/notes/<namespace> Notas de Git
HEAD Checkout actual (simbólico o separado)
FETCH_HEAD Último resultado de fetch (escrito por git; el fetch() de Pitmaster no lo escribe)
MERGE_HEAD Rama que se está fusionando (durante una fusión)
ORIG_HEAD HEAD anterior (antes de operaciones peligrosas)

Trabajar con refs mediante Repository

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