Á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:
project/
.git/ (directory)
src/
README.md
Con los worktrees, puede tener checkouts adicionales:
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:
gitdir: ../../path/to/project/.git/worktrees/review
Crear un worktree
$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:
- Crea la rama si no existe (desde HEAD o desde el commit
$fromespecificado) - Crea el directorio de metadatos en
.git/worktrees/<name>/ - Escribe el archivo
.giten el directorio del worktree - Escribe los archivos de metadatos
HEAD,commondirygitdir - Hace checkout del árbol de la rama en el directorio del worktree
- 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.
$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
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Listar worktrees
$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
$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:
- Comprueba si está bloqueado (falla a menos que
$forcesea true) - Elimina el archivo
.gitdel directorio del worktree - 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:
$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_HEADy 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
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).
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
// 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
// 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
.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
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) |