Saltar al contenido

Alijo

El alijo Git guarda los cambios del directorio de trabajo y del índice para que puedas cambiar a otra tarea y restaurarlos más tarde. Pitmaster implementa el alijo mediante refs/stash y el reflog, en el diseño de almacenamiento de git. Repository no tiene un método de alijo; usa la clase Stash que aparece a continuación.

Hay dos problemas conocidos: con más de una entrada, drop() (y, por tanto, pop()) deja la entrada en la lista (#10), y apply() sobrescribe los cambios sin confirmar en archivos que el alijo no toca (#11). Aplica los alijos en un árbol de trabajo limpio.

Cómo funciona el alijo

Cada entrada del alijo es una confirmación con dos padres:

  • Padre 0: la confirmación HEAD al crear el alijo (el estado base)
  • Padre 1: una confirmación que captura el estado del índice (cambios preparados)

El árbol de la propia confirmación del alijo captura el estado del árbol de trabajo (incluidos los cambios sin preparar).

La «pila» del alijo es el reflog de refs/stash. El alijo más reciente es el propio refs/stash. Las entradas anteriores son entradas del reflog: stash@{0}, stash@{1}, etc.

Código
refs/stash -> stash commit (worktree state)
                parent[0] -> HEAD at time of stash
                parent[1] -> index state commit
                               parent[0] -> HEAD at time of stash

Crear el objeto Stash

PHP
use Pitmaster\Stash\Stash;

$stash = new Stash(
    $repo->objectDatabase(),
    $repo->refDatabase(),
    $repo->gitDir(),
    $repo->workDir(),
);

Push (guardar cambios)

PHP
// public function push(string $message = '', bool $includeUntracked = false): ObjectId
$stashId = $stash->push('Work in progress on login');
echo "Saved: {$stashId->hex}\n";

// Also stash untracked files
$stashId = $stash->push('With new files', includeUntracked: true);

Push hace lo siguiente:

  1. Lee la confirmación HEAD actual
  2. Crea un árbol a partir del índice actual (estado preparado)
  3. Crea una «confirmación del índice» con HEAD como padre
  4. Crea un árbol a partir del directorio de trabajo actual (incluidos los cambios sin preparar)
  5. Crea la confirmación del alijo (árbol del árbol de trabajo, padres = HEAD + confirmación del índice)
  6. Actualiza refs/stash para que apunte a la confirmación del alijo
  7. Añade una entrada del reflog a la pila
  8. Restablece el árbol de trabajo y el índice a HEAD

Después de hacer push, los archivos bajo seguimiento coinciden con HEAD. Los archivos sin seguimiento permanecen salvo que se establezca includeUntracked; en ese caso, se eliminan y se guardan en el árbol propio de la confirmación del alijo (git los guarda en un tercer padre, por lo que git los considera cambios bajo seguimiento). push() crea una entrada incluso si no hubo cambios, mientras que git informa «No local changes to save».

Con el mensaje predeterminado

PHP
$stashId = $stash->push();
// Message: "WIP on <branch>: <short hash> <HEAD subject>"
// With a message: "On <branch>: <message>"

Con HEAD separado, <branch> es HEAD.

Pop (aplicar y eliminar)

PHP
$stash->pop();

Aplica la entrada superior del alijo al directorio de trabajo y la elimina de la pila. Equivale a apply() seguido de drop().

Extraer una entrada específica

PHP
$stash->pop(1);  // Pop stash@{1}

Apply (restaurar sin eliminar)

PHP
$stash->apply();      // Apply stash@{0}
$stash->apply(2);     // Apply stash@{2}

Apply fusiona el alijo con cada archivo de HEAD actual (base del alijo, HEAD actual, árbol del alijo). La entrada permanece en la pila y el índice no se modifica. Los conflictos dejan marcadores etiquetados Updated upstream y Stashed changes en los archivos y generan Pitmaster\Exceptions\MergeConflictException; pop() conserva entonces la entrada. Apply no comprueba primero si hay cambios locales (consulta la nota inicial).

Enumerar entradas

PHP
$entries = $stash->listEntries();

foreach ($entries as $entry) {
    echo "stash@{{$entry['index']}}: {$entry['message']}\n";
    // stash@{0}: On main: Work in progress on login
    // stash@{1}: WIP on main: f707601 Add login form
}

Cada entrada contiene:

  • index: la posición del alijo (0 = el más reciente)
  • message: el mensaje del alijo
  • hash: el hash de la confirmación del alijo

Drop (eliminar sin aplicar)

PHP
$stash->drop();       // Drop stash@{0}
$stash->drop(1);      // Drop stash@{1}

Si se elimina la única entrada, se eliminan refs/stash y su reflog. Si hay más entradas, actualmente drop() solo mueve refs/stash y deja el reflog intacto, por lo que la entrada permanece en listEntries() (#10).

Detalles de almacenamiento

Estructura de la confirmación del alijo

Código
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>

<stash message>

Estructura de la confirmación del índice

Código
commit <hash>
tree <index-tree-hash>
parent <HEAD-hash>
author <current identity>
committer <current identity>

index on <branch>: <short hash> <HEAD subject>

La identidad se obtiene de GIT_COMMITTER_NAME, luego GIT_AUTHOR_NAME, después user.name en la configuración del repositorio y, por último, Pitmaster (y se sigue el mismo orden para el correo electrónico, que termina en pitmaster@example.invalid). La fecha se obtiene de GIT_COMMITTER_DATE, luego GIT_AUTHOR_DATE y, por último, de la hora actual. A diferencia de las confirmaciones, el alijo no lee las constantes PITMASTER_*.

Almacenamiento de Reflog

Las entradas del alijo se almacenan en .git/logs/refs/stash:

Código
0000000000000000000000000000000000000000 abc123... Name <email> 1700000000 +0000	WIP on main: f707601 Add login form
abc123... def456... Name <email> 1700000001 +0000	On main: Work in progress on login

El reflog se lee en orden cronológico. La lista de alijos lo invierte (del más reciente al más antiguo). El reflog del alijo se escribe independientemente de core.logallrefupdates.

Uso de CLI

Shell
# Save current changes
./vendor/bin/pitmaster stash
./vendor/bin/pitmaster stash push
./vendor/bin/pitmaster stash push "Work in progress on login"

# Apply and remove top entry
./vendor/bin/pitmaster stash pop

# Apply without removing
./vendor/bin/pitmaster stash apply

# List all entries
./vendor/bin/pitmaster stash list

# Remove top entry without applying
./vendor/bin/pitmaster stash drop

El mensaje es el argumento que sigue a push (o save); -m no es compatible. pop, apply y drop siempre operan en stash@{0}; CLI no tiene --include-untracked.

Limitaciones

  • Solo se puede guardar el directorio de trabajo completo (sin --keep-index); los archivos sin seguimiento solo mediante push($message, includeUntracked: true)
  • No hay modo --patch (selección interactiva de fragmentos)
  • Pop/apply restaura el estado del árbol de trabajo, pero no el índice. Las modificaciones preparadas vuelven como modificaciones sin preparar, y los archivos recién preparados vuelven como archivos sin seguimiento
  • stash@{n} no se acepta en Repository::resolve()
  • El alijo en un árbol de trabajo vinculado usa una ubicación incorrecta para la configuración y el reflog (#14)