
# Работни дървета

Git Работните дървета позволяват на няколко работни директории да споделят едно хранилище. Всяко работно дърво има собствен checkout (HEAD, индекс, работни файлове), но споделя хранилището на обектите, конфигурацията и packed-refs с основното хранилище. Pitmaster може да създава, изброява, заключва и премахва свързани работни дървета, както и да ги отваря като хранилища. Почистването, преместването и поправянето на работни дървета не са реализирани.

## Основни понятия

Обикновено git хранилище има едно работно дърво:

```
project/
  .git/          (directory)
  src/
  README.md
```

С работни дървета можете да имате допълнителни checkout-и:

```
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
```

`.git` на свързаното работно дърво е файл (а не директория), който съдържа относителен път към работното дърво:
```
gitdir: ../../path/to/project/.git/worktrees/review
```

## Създаване на работно дърво

```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()` извършва следното:
1. Създава клона, ако той не съществува (от HEAD или от зададения commit `$from`)
2. Създава директорията с метаданни в `.git/worktrees/<name>/`
3. Записва файла `.git` в директорията на работното дърво
4. Записва файловете с метаданни `HEAD`, `commondir` и `gitdir`
5. Извлича дървото на клона в директорията на работното дърво
6. Записва индекса за работното дърво

Ако клонът вече съществува, `$from` се игнорира. За разлика от git, `addWorktree()` не отказва клон, който вече е checkout-нат в друго работно дърво, и не изпълнява hook-а `post-checkout`.

### С изрично име на метаданните

Използвайте незадължителния аргумент `name`, когато няколко свързани работни дървета могат да имат еднакво базово име за checkout.

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

Това име на метаданните се използва в `.git/worktrees/<name>/` вместо `basename($path)`.

### От конкретен commit

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

## Изброяване на работните дървета

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

Списъкът винаги включва първо основното работно дърво, следвано от свързаните работни дървета. Извикайте `worktrees()` върху обект за основното хранилище: при обект за свързано работно дърво основният запис в момента показва собствения път на свързаното работно дърво ([#14](https://github.com/inline0/pitmaster/issues/14)).

## Премахване на работно дърво

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

При премахване на работно дърво:
1. Проверява дали работното дърво е заключено (неуспешно, освен ако `$force` е true)
2. Премахва файла `.git` от директорията на работното дърво
3. Премахва директорията с метаданни от `.git/worktrees/`

Файловете в работното дърво не се изтриват автоматично. Клонът продължава да съществува.

## Отваряне на свързано работно дърво

Можете да отворите свързано работно дърво точно като всяко друго хранилище:

```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'
```

Операциите с хранилището разрешават пътищата по следния начин:
- **Обекти**: четат се от и се записват в общата git директория (`project/.git/objects/`)
- **Конфигурация**: чете се от общата git директория (`project/.git/config`)
- **Packed-refs**: четат се от общата git директория
- **HEAD**: специфичен за работното дърво (всяко работно дърво следи собствения си клон), заедно с `ORIG_HEAD`, `MERGE_HEAD` и другите refs за операции
- **Индекс**: специфичен за работното дърво (всяко работно дърво има собствена зона за добавяне)
- **Loose refs**: клоновете и етикетите се споделят в общата директория (`project/.git/refs/`)
- **Hooks**: четат се от git директорията на работното дърво, затова споделените hooks не се изпълняват ([#12](https://github.com/inline0/pitmaster/issues/12))

Stash все още не обработва свързани работни дървета: чете идентичността от директорията на работното дърво, в която няма конфигурация, и записва своя reflog там ([#14](https://github.com/inline0/pitmaster/issues/14)).

## Обект със стойност на работно дърво

```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

За управление на работни дървета на ниско ниво. `WorktreeManager::add()` записва само метаданните и файла `.git`: не създава клон, не извлича файлове и не записва индекс. Използвайте `$repo->addWorktree()` за работещ checkout. Клонът трябва вече да съществува (или да е пълно име на ref, като `refs/tags/v1.0.0`, което отделя HEAD).

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

## Заключване на работни дървета

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

Заключването се съхранява във файл в `.git/worktrees/<name>/locked`. Съдържанието на файла е причината за заключването (може да е празно).

## Откриване на работни дървета

```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'
```

## Структура на файловете

### Метаданни на основното работно дърво за свързани работни дървета

```
.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
```

### Файлът .git на свързаното работно дърво

```
gitdir: <relative path to>/project/.git/worktrees/<name>
```

### Споделяне на ресурси

| Ресурс | Местоположение | Споделен? |
|----------|----------|---------|
| Обекти | `$commonDir/objects/` | Да |
| Конфигурация | `$commonDir/config` | Да |
| Packed-refs | `$commonDir/packed-refs` | Да |
| Hooks | `$gitDir/hooks/` | Не: Pitmaster не изпълнява споделените hooks в свързаните работни дървета |
| HEAD | `$gitDir/HEAD` | Не (специфичен за работното дърво) |
| Индекс | `$gitDir/index` | Не (специфичен за работното дърво) |
| Loose refs | `$commonDir/refs/` | Да (HEAD и refs за операции, например `MERGE_HEAD`, са специфични за работното дърво) |
