Árvores de trabalho
Git árvores de trabalho permitem que vários diretórios de trabalho compartilhem um único repositório. Cada árvore de trabalho tem sua própria cópia (HEAD, índice, arquivos de trabalho), mas compartilha o armazenamento de objetos, a configuração e packed-refs com o repositório principal. Pitmaster pode criar, listar, bloquear e remover árvores de trabalho vinculadas, além de abri-las como repositórios. A limpeza, a movimentação e o reparo de árvores de trabalho não estão implementados.
Conceitos
Um repositório git comum tem uma única árvore de trabalho:
project/
.git/ (directory)
src/
README.md
Com árvores de trabalho, você pode ter cópias adicionais:
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
O .git da árvore de trabalho vinculada é um arquivo (não um diretório) que contém um caminho relativo à árvore de trabalho:
gitdir: ../../path/to/project/.git/worktrees/review
Criar uma árvore de trabalho
$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() faz o seguinte:
- Cria a ramificação, caso ela não exista (a partir de HEAD ou do commit
$fromespecificado) - Cria o diretório de metadados em
.git/worktrees/<name>/ - Grava o arquivo
.gitno diretório da árvore de trabalho - Grava os arquivos de metadados
HEAD,commondiregitdir - Verifica a árvore da ramificação no diretório da árvore de trabalho
- Grava o índice da árvore de trabalho
Se a ramificação já existir, $from será ignorado. Ao contrário de git, addWorktree() não recusa uma ramificação que já esteja verificada em outra árvore de trabalho e não executa o hook post-checkout.
Com um nome de metadados explícito
Use o argumento opcional name quando várias árvores de trabalho vinculadas puderem compartilhar o mesmo nome base de cópia.
$wt = $repo->addWorktree(
'/tmp/sandboxes/app',
'feature/review',
name: 'sandbox-app',
);
Esse nome de metadados é usado em .git/worktrees/<name>/ em vez de basename($path).
A partir de um commit específico
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Listar árvores de trabalho
$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";
}
A lista sempre inclui primeiro a árvore de trabalho principal, seguida pelas árvores de trabalho vinculadas. Chame worktrees() em um handle do repositório principal: em um handle de árvore de trabalho vinculada, a entrada principal informa atualmente o próprio caminho da árvore de trabalho vinculada (#14).
Remover uma árvore de trabalho
$repo->removeWorktree('review'); // by metadata name
$repo->removeWorktree('/tmp/review'); // or by worktree path
// Force remove (even if locked)
$repo->removeWorktree('review', force: true);
Remover uma árvore de trabalho:
- Verifica se ela está bloqueada (falha, a menos que
$forceseja true) - Remove o arquivo
.gitdo diretório da árvore de trabalho - Remove o diretório de metadados de
.git/worktrees/
Os arquivos da árvore de trabalho não são excluídos automaticamente. A ramificação continua existindo.
Abrir uma árvore de trabalho vinculada
Você pode abrir uma árvore de trabalho vinculada como qualquer outro repositório:
$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'
As operações do repositório resolvem os caminhos desta forma:
- Objetos: lidos de e gravados no diretório git comum (
project/.git/objects/) - Configuração: lida do diretório git comum (
project/.git/config) - Packed-refs: lidos do diretório git comum
- HEAD: por árvore de trabalho (cada árvore acompanha sua própria ramificação), junto com
ORIG_HEAD,MERGE_HEADe as outras referências de operação - Índice: por árvore de trabalho (cada árvore tem sua própria área de preparação)
- Referências soltas: ramificações e tags são compartilhadas no diretório comum (
project/.git/refs/) - Hooks: lidos do diretório git de cada árvore de trabalho, então os hooks compartilhados não são executados (#12)
Stash ainda não lida com árvores de trabalho vinculadas: lê a identidade do diretório por árvore de trabalho, que não tem configuração, e grava seu reflog nesse local (#14).
Objeto de valor da árvore de trabalho
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 gerenciamento de árvores de trabalho de baixo nível. WorktreeManager::add() grava apenas os metadados e o arquivo .git: não cria a ramificação, não verifica arquivos nem grava um índice. Use $repo->addWorktree() para obter uma cópia funcional. A ramificação precisa já existir (ou ser um nome de referência completo, como refs/tags/v1.0.0, que desanexa HEAD).
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 árvores de trabalho
// 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');
O bloqueio é armazenado como um arquivo em .git/worktrees/<name>/locked. O conteúdo do arquivo é o motivo do bloqueio (pode estar vazio).
Detectar árvores de trabalho
// 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'
Estrutura de arquivos
Metadados da árvore de trabalho principal para árvores vinculadas
.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
Arquivo .git da árvore de trabalho vinculada
gitdir: <relative path to>/project/.git/worktrees/<name>
Compartilhamento de recursos
| Recurso | Localização | Compartilhado? |
|---|---|---|
| Objetos | $commonDir/objects/ |
Sim |
| Configuração | $commonDir/config |
Sim |
| Packed-refs | $commonDir/packed-refs |
Sim |
| Hooks | $gitDir/hooks/ |
Não: Pitmaster não executa os hooks compartilhados em árvores de trabalho vinculadas |
| HEAD | $gitDir/HEAD |
Não (por árvore de trabalho) |
| Índice | $gitDir/index |
Não (por árvore de trabalho) |
| Referências soltas | $commonDir/refs/ |
Sim (HEAD e referências de operação como MERGE_HEAD são por árvore de trabalho) |