Saltar al contenido

Árboles de trabajo

Git Los árboles de trabajo permiten que varios directorios de trabajo compartan un mismo repositorio. Cada árbol tiene su propia extracción (HEAD, índice, archivos de trabajo), pero comparte con el repositorio principal el almacén de objetos, la configuración y las referencias empaquetadas. Pitmaster puede crear, enumerar, bloquear y eliminar árboles de trabajo vinculados, y abrirlos como repositorios. No se implementan la poda, el traslado ni la reparación de árboles de trabajo.

Conceptos

Un repositorio git normal tiene un único árbol de trabajo:

Código
project/
  .git/          (directory)
  src/
  README.md

Con los worktrees, puede tener checkouts adicionales:

Código
project/                          (main worktree)
  .git/                           (the actual git directory)
    worktrees/
      review/                     (metadata for linked worktree)
        HEAD
        commondir
        gitdir

/tmp/review/                      (linked worktree)
  .git                            (file, not directory)
  src/
  README.md

El .git del worktree vinculado es un archivo (no un directorio) que contiene una ruta relativa al worktree:

Código
gitdir: ../../path/to/project/.git/worktrees/review

Crear un worktree

PHP
$repo = Pitmaster::open('/path/to/project');

// Create a linked worktree on a new branch
$wt = $repo->addWorktree('/tmp/review', 'feature/review');

echo $wt->path;        // '/tmp/review'
echo $wt->branch;      // 'feature/review'
echo $wt->gitDir;      // '/path/to/project/.git/worktrees/review'
var_dump($wt->isMain);      // bool(false)
var_dump($wt->isDetached);  // bool(false)

addWorktree() hace lo siguiente:

  1. Crea la rama si no existe (desde HEAD o desde el commit $from especificado)
  2. Crea el directorio de metadatos en .git/worktrees/<name>/
  3. Escribe el archivo .git en el directorio del worktree
  4. Escribe los archivos de metadatos HEAD, commondir y gitdir
  5. Hace checkout del árbol de la rama en el directorio del worktree
  6. Escribe el índice del worktree

Si la rama ya existe, se ignora $from. A diferencia de git, addWorktree() no rechaza una rama que ya tenga checkout en otro worktree ni ejecuta el hook post-checkout.

Con un nombre de metadatos explícito

Use el argumento opcional name cuando varios worktrees vinculados puedan compartir el mismo nombre base de checkout.

PHP
$wt = $repo->addWorktree(
    '/tmp/sandboxes/app',
    'feature/review',
    name: 'sandbox-app',
);

Ese nombre de metadatos se usa en .git/worktrees/<name>/ en lugar de basename($path).

Desde un commit específico

PHP
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));

Listar worktrees

PHP
$worktrees = $repo->worktrees();

foreach ($worktrees as $wt) {
    $type = $wt->isMain ? 'main' : 'linked';
    $branch = $wt->branch ?? 'detached';
    $locked = $wt->isLocked ? ' [locked]' : '';

    echo "{$wt->path} ({$type}) [{$branch}]{$locked}\n";
}

La lista siempre incluye primero el worktree principal, seguido de los worktrees vinculados. Llame a worktrees() en un controlador del repositorio principal: en un controlador de un worktree vinculado, la entrada principal informa actualmente la propia ruta del worktree vinculado (#14).

Eliminar un worktree

PHP
$repo->removeWorktree('review');          // by metadata name
$repo->removeWorktree('/tmp/review');     // or by worktree path

// Force remove (even if locked)
$repo->removeWorktree('review', force: true);

Al eliminar un worktree:

  1. Comprueba si está bloqueado (falla a menos que $force sea true)
  2. Elimina el archivo .git del directorio del worktree
  3. Elimina el directorio de metadatos de .git/worktrees/

Los archivos del árbol de trabajo no se eliminan automáticamente. La rama sigue existiendo.

Abrir un worktree vinculado

Puede abrir un worktree vinculado igual que cualquier repositorio:

PHP
$repo = Pitmaster::open('/tmp/review');

// Pitmaster detects the .git file and follows the gitdir reference
echo $repo->isLinkedWorktree();  // true
echo $repo->gitDir();            // '/path/to/project/.git/worktrees/review'
echo $repo->commonGitDir();      // '/path/to/project/.git'
echo $repo->workDir();           // '/tmp/review'

Las operaciones del repositorio resuelven las rutas de esta manera:

  • Objetos: se leen del directorio git común y se escriben en él (project/.git/objects/)
  • Configuración: se lee del directorio git común (project/.git/config)
  • Packed-refs: se lee del directorio git común
  • HEAD: por worktree (cada worktree registra su propia rama), junto con ORIG_HEAD, MERGE_HEAD y las demás referencias de operaciones
  • Índice: por worktree (cada worktree tiene su propia área de preparación)
  • Referencias sueltas: las ramas y etiquetas se comparten en el directorio común (project/.git/refs/)
  • Hooks: se leen del directorio git de cada worktree, por lo que los hooks compartidos no se ejecutan (#12)

Stash aún no admite worktrees vinculados: lee la identidad del directorio de cada worktree, que no tiene configuración, y escribe allí su reflog (#14).

Objeto de valor Worktree

PHP
use Pitmaster\Worktree\Worktree;

$wt->name;          // Metadata name (null for the main worktree)
$wt->path;          // Working directory path
$wt->gitDir;        // Per-worktree git dir
$wt->branch;        // Branch name (null if detached)
$wt->head;          // ObjectId of HEAD commit
$wt->isMain;        // true for the main worktree
$wt->isDetached;    // true if HEAD is detached
$wt->isLocked;      // true if worktree is locked
$wt->lockReason;    // Lock reason, or null when there is none (an empty reason is also null)

WorktreeManager

Para la gestión de worktrees de bajo nivel. WorktreeManager::add() solo escribe los metadatos y el archivo .git: no crea la rama, no hace checkout de archivos ni escribe un índice. Use $repo->addWorktree() para obtener un checkout funcional. La rama ya debe existir (o ser un nombre de referencia completo, como refs/tags/v1.0.0, que separa HEAD de la rama).

PHP
use Pitmaster\Worktree\WorktreeManager;

$manager = new WorktreeManager($repo->commonGitDir(), $mainWorkDir);

// List
$worktrees = $manager->list();

// Add
$wt = $manager->add('/tmp/review', 'feature/review');
$wt = $manager->add('/tmp/review', 'feature/review', 'review-app');

// Remove
$manager->remove('review');
$manager->remove('review', force: true);

// Lock/unlock (metadata name only)
$manager->lock('review', 'Do not delete, code review in progress');
$manager->unlock('review');

Bloquear worktrees

PHP
// Lock (prevents removal)
$manager->lock('review', 'Code review in progress');

// Attempt to remove locked worktree
try {
    $manager->remove('review');
} catch (\RuntimeException $e) {
    // "Worktree is locked: review (Code review in progress)"
}

// Force remove ignores the lock
$manager->remove('review', force: true);

// Unlock
$manager->unlock('review');

El bloqueo se guarda como un archivo en .git/worktrees/<name>/locked. El contenido del archivo es el motivo del bloqueo (puede estar vacío).

Detectar worktrees

PHP
// Check if a path is a linked worktree
if (Pitmaster::isWorktree('/tmp/review')) {
    echo "This is a linked worktree\n";
}

// Resolve the common git dir from any path
$common = Pitmaster::commonGitDir('/tmp/review');
// '/path/to/project/.git'

// Resolve a .git file to the actual git directory
$gitDir = WorktreeManager::resolveGitFile('/tmp/review');
// '/path/to/project/.git/worktrees/review'

Estructura de archivos

Metadatos del worktree principal para worktrees vinculados

Código
.git/worktrees/<name>/
  HEAD         Per-worktree HEAD (symbolic ref or detached hash)
  commondir    Relative path to the shared git dir ("../..")
  gitdir       Path back to the worktree's .git file, as passed to addWorktree()
  locked       Present if locked (content = reason)
  index        Per-worktree index/staging area

Archivo .git del worktree vinculado

Código
gitdir: <relative path to>/project/.git/worktrees/<name>

Recursos compartidos

Recurso Ubicación ¿Compartido?
Objetos $commonDir/objects/ Sí
Configuración $commonDir/config Sí
Packed-refs $commonDir/packed-refs Sí
Hooks $gitDir/hooks/ No: Pitmaster no ejecuta los hooks compartidos en worktrees vinculados
HEAD $gitDir/HEAD No (por worktree)
Índice $gitDir/index No (por worktree)
Referencias sueltas $commonDir/refs/ Sí (HEAD y referencias de operaciones como MERGE_HEAD son por worktree)