
# PHP API

## `Pitmaster\Pitmaster`

Класът `Pitmaster\Pitmaster` е статичната входна точка. Предоставя фабрични методи за отваряне, създаване и клониране на хранилища, както и помощни функции за откриването им.

```php
use Pitmaster\Pitmaster;
```

### `open`

```php
public static function open(string $path, array $options = []): Repository
```

Отваря съществуващо хранилище в `$path`. Пътят трябва да е коренът на работното дърво (съдържащ `.git/`). Поддържат се и свързани работни дървета (където `.git` е файл, съдържащ `gitdir: <path>`), както и голи хранилища (където `$path` е самата директория `.git`).

`$options` приема две булеви стойности, които по подразбиране са `true`:

- `hooks`: изпълнява скриптовете `.git/hooks`. Подайте `false`, за да ги пропуснете за този обект.
- `processes`: разрешава всичко, което стартира процес или отваря мрежов транспорт. Подайте `false`, за да забраните hooks, hook-а fsmonitor, clone, fetch, push и транспорта на процеси SSH. Локалните четения и записи продължават да работят.

Генерира `InvalidArgumentException`, ако пътят не е git хранилище.

```php
$repo = Pitmaster::open('/home/user/project');
$repo = Pitmaster::open('/home/user/project', ['hooks' => false]);
$repo = Pitmaster::open('/home/user/project', Pitmaster::processFreeOptions());
```

### `init`

```php
public static function init(string $path, string $objectFormat = 'sha1', array $options = []): Repository
```

Инициализира ново хранилище в `$path`. Създава структурата на директорията `.git` с `objects/`, `refs/heads/`, `refs/tags/`, `hooks/`, `info/`, `HEAD` (сочещ към `refs/heads/main`) и стойност по подразбиране за `config`.

`$objectFormat` е `'sha1'` или `'sha256'`. Хранилище SHA-256 получава `repositoryformatversion = 1` и `extensions.objectformat = sha256` в конфигурацията си. Всяка друга стойност генерира `InvalidArgumentException`. `$options` приема същите ключове като `open()`.

Генерира `RuntimeException`, ако на пътя вече има хранилище.

```php
$repo = Pitmaster::init('/home/user/new-project');
$repo = Pitmaster::init('/home/user/sha256-project', 'sha256');
```

### `clone`

```php
public static function clone(string $url, string $path, ?int $depth = null, array $options = []): Repository
```

Клонира отдалечено хранилище. Инициализира ново хранилище, открива отдалечените refs, изтегля pack файла, настройва клоновете за проследяване на отдалечени клонове и създава работното дърво. URL адресите SSH (`ssh://...` или `user@host:path`) използват транспорта SSH; за другите URL адреси се използва smart HTTP, като при неуспешен smart обмен се преминава към dumb HTTP. Подайте `$depth` за плитко клониране.

Генерира `RuntimeException`, когато `$options['processes']` е `false`. Ако клонирането е неуспешно и `$path` не е съществувала предварително, частично създадената директория се премахва.

```php
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/repo');
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/shallow', 1);
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/repo', null, ['hooks' => false]);
```

### `processFreeOptions`

```php
public static function processFreeOptions(): array
```

Връща `['processes' => false, 'hooks' => false]` за среди за изпълнение, които не трябва да стартират процеси на хоста. Подайте го на `open()` или `init()`.

### `isRepository`

```php
public static function isRepository(string $path): bool
```

Проверява дали пътят е git хранилище. Връща `true` за обикновени хранилища (директория `.git/`), свързани работни дървета (файл `.git`) и голи хранилища (наличен файл `HEAD`).

```php
if (Pitmaster::isRepository('/path/to/check')) {
    // It's a git repo
}
```

### `isWorktree`

```php
public static function isWorktree(string $path): bool
```

Проверява дали пътят е свързано работно дърво (а не основното хранилище). Връща `true` само ако `.git` е файл, съдържащ препратка `gitdir:`.

```php
if (Pitmaster::isWorktree('/path/to/worktree')) {
    // It's a linked worktree
}
```

### `commonGitDir`

```php
public static function commonGitDir(string $path): ?string
```

Разрешава общата git директория от пътя на всеки checkout. При обикновени хранилища това е директорията `.git`. При свързани работни дървета това е споделената git директория, съдържаща обектите, конфигурацията и packed-refs. Връща `null`, ако пътят не е хранилище.

```php
$common = Pitmaster::commonGitDir('/path/to/worktree');
// '/path/to/main-repo/.git'
```

---

## `Pitmaster\Repository`

Класът `Pitmaster\Repository` е основният обект за всички git операции. Вземете такъв чрез `Pitmaster::open()`, `Pitmaster::init()` или `Pitmaster::clone()`.

```php
use Pitmaster\Repository;
```

### Методи за достъп до пътища

#### `gitDir`

```php
public function gitDir(): string
```

Връща git директорията на конкретното работно дърво. При обикновени хранилища това е `.git/`. При свързани работни дървета това е `.git/worktrees/<name>/`.

#### `commonGitDir`

```php
public function commonGitDir(): string
```

Връща споделената git директория. При обикновени хранилища тя съвпада с `gitDir()`. При свързани работни дървета това е директорията `.git/` на основното хранилище (в нея се намират обектите, конфигурацията и packed-refs).

#### `workDir`

```php
public function workDir(): string
```

Връща пътя към корена на работното дърво.

#### `isLinkedWorktree`

```php
public function isLinkedWorktree(): bool
```

Връща `true`, ако този обект за хранилище представлява свързано работно дърво.

#### `isBare`, `hooksEnabled`, `processesEnabled`

```php
public function isBare(): bool
public function hooksEnabled(): bool
public function processesEnabled(): bool
```

Показва дали хранилището няма работно дърво и кои от опциите `hooks` / `processes` са зададени за този обект. `hooksEnabled()` е `false` винаги когато процесите са забранени.

---

### Обекти

#### `readObject`

```php
public function readObject(string $hash): GitObject
```

Чете всеки обект по пълния му шестнадесетичен хеш (40 знака или 64 в хранилище SHA-256). Съкратени хешове не се приемат. Връща екземпляр на `Blob`, `Tree`, `Commit` или `Tag`. Първо търси сред свободните обекти, после във файловете pack.

Генерира `ObjectNotFoundException`, ако хешът не е намерен.

```php
$object = $repo->readObject('a1b2c3d4e5f6...');
```

#### `writeObject`

```php
public function writeObject(GitObject $object): ObjectId
```

Записва обект в хранилището на свободните обекти. Връща изчисления `ObjectId`.

```php
use Pitmaster\Object\Blob;

$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
```

#### `catFile`

```php
public function catFile(string $hash): string
```

Връща необработеното съдържание на обект; еквивалентно е на `git cat-file -p`.

```php
echo $repo->catFile('a1b2c3d4...');
```

#### `objectExists`

```php
public function objectExists(string $hash): bool
```

Проверява дали даден обект съществува в хранилището (свободен или пакетиран).

#### `listObjects`

```php
public function listObjects(): array
```

Изброява всички хешове на обекти в хранилището. Връща масив от шестнадесетични низове.

---

### Препратки

#### `head`

```php
public function head(): Commit
```

Връща текущия commit на HEAD. Следва символичните препратки (HEAD -> refs/heads/main -> хеш на commit).

Генерира `RuntimeException`, ако HEAD не сочи към валиден commit (e.g, празно хранилище).

#### `branch`

```php
public function branch(?string $name = null): ?string
```

Без аргументи връща името на текущия клон (e.g, `'main'`) или `null`, ако HEAD е отделен.

При име на клон го разрешава до хеша на неговия commit (шестнадесетичен низ) или връща `null`, ако клонът не съществува.

```php
$current = $repo->branch();        // 'main'
$hash = $repo->branch('feature');   // 'abc123...' or null
```

#### `branches`

```php
public function branches(): array
```

Връща сортиран масив с всички имена на клонове (без префикса `refs/heads/`).

```php
$branches = $repo->branches();
// ['bugfix/typo', 'feature/login', 'main']
```

#### `tags`

```php
public function tags(): array
```

Връща сортиран масив с всички имена на етикети (без префикса `refs/tags/`).

#### `resolve`

```php
public function resolve(string $revision): ObjectId
```

Разрешава израз за ревизия до `ObjectId`. Поддържа:
- Пълни шестнадесетични хешове (40 знака или 64 за SHA-256). Съкратени хешове като `abc1234` не се разрешават.
- `HEAD`
- Имена на клонове (`main`, `feature/login`)
- Имена на етикети (`v1.0.0`)
- Пълни пътища към refs (`refs/heads/main`)
- Изрази за ревизия (`HEAD~3`, `main^2`)

Генерира `RuntimeException`, ако ревизията не може да бъде разрешена.

```php
$id = $repo->resolve('HEAD~3');
$id = $repo->resolve('v1.0.0');
$id = $repo->resolve('feature/login');
```

#### `allRefs`

```php
public function allRefs(): array
```

Връща всички refs като асоциативен масив от имена на refs към шестнадесетични хешове.

```php
$refs = $repo->allRefs();
// ['refs/heads/main' => 'abc123...', 'refs/tags/v1.0.0' => 'def456...']
```

#### `updateRef`

```php
public function updateRef(string $name, ObjectId $target): void
```

Обновява ref така, че да сочи към нова цел.

```php
$repo->updateRef('refs/heads/main', $commitId);
```

#### `createBranch`

```php
public function createBranch(string $name, ?ObjectId $from = null): void
```

Създава нов клон. Ако `$from` е null, създава клона от текущия HEAD.

```php
$repo->createBranch('feature/new-thing');
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));
```

#### `deleteBranch`

```php
public function deleteBranch(string $name): void
```

Изтрива клон по име. Хвърля `RuntimeException`, ако клонът е checkout-нат или не съществува. За разлика от `git branch -d`, не проверява дали клонът е слят.

```php
$repo->deleteBranch('feature/old');
```

#### `createLightweightTag`

```php
public function createLightweightTag(string $name, ?ObjectId $target = null): void
```

Създава лек таг. Ако `$target` е null, поставя тага директно върху текущия HEAD.

```php
$repo->createLightweightTag('v2.0.0');
$repo->createLightweightTag('hotfix-start', $repo->resolve('main~2'));
```

#### `createTag`

```php
public function createTag(string $name, string $message, ?ObjectId $target = null, ?string $tagger = null): ObjectId
```

Създава анотиран таг. Ако `$target` е null, поставя тага върху текущия HEAD. Връща `ObjectId` на обекта на тага.

```php
$tagId = $repo->createTag('v2.0.0', 'Release version 2.0.0');
```

#### `deleteTag`

```php
public function deleteTag(string $name): void
```

Изтрива таг по име.

```php
$repo->deleteTag('v1.0.0');
```

#### `packRefs`

```php
public function packRefs(): void
```

Презаписва споделения файл `packed-refs` от текущия набор loose refs и премахва съответните споделени loose refs. При свързани работни дървета записва в общата git директория.

```php
$repo->packRefs();
```

#### `defaultBranch`

```php
public function defaultBranch(): string
```

Определя клона по подразбиране на хранилището. Първо проверява символната препратка на отдалечения HEAD, после локалния HEAD и накрая използва `main` или `master` като резервен вариант.

#### `isBranchMerged`

```php
public function isBranchMerged(string $branch, ?string $target = null): bool
```

Проверява дали даден клон е напълно слят с друг клон (по подразбиране — с клона по подразбиране).

```php
if ($repo->isBranchMerged('feature/done')) {
    $repo->deleteBranch('feature/done');
}
```

#### `checkout`

```php
public function checkout(string $target): void
```

Превключва клонове или отделя HEAD. Обновява HEAD, индекса и работното дърво. `$target` е име на клон, таг, пълен хеш или израз за ревизия; ако не е клон, HEAD се отделя.

Хвърля `RuntimeException`, когато локална промяна (индексирана или не) или непроследяван файл се намира в път, който се различава между текущото и целевото дърво.

Непотвърдените промени в други проследявани файлове не се пренасят: индексът и работното дърво се изграждат отново от целевото дърво, така че редакциите във файлове, които са еднакви от двете страни, се губят, въпреки че git би ги запазил ([#2](https://github.com/inline0/pitmaster/issues/2)). Първо ги потвърдете или запазете в stash. `merge()`, `cherryPick()`, `revert()`, `rebase()` и методите за bisect изграждат работното дърво отново по същия начин.

```php
$repo->checkout('feature/login');
$repo->checkout($repo->resolve('v1.0.0')->hex);  // Detached HEAD
```

---

### Индекс

#### `index`

```php
public function index(): Index
```

Чете и връща текущия индекс (областта за подготовка) от `.git/index`.

#### `add`

```php
public function add(string ...$paths): void
```

Индексира файлове. Чете всеки файл от работното дърво, създава blob обект и обновява индекса. Всеки аргумент трябва да е път до файл (или символна връзка) спрямо корена на работното дърво; директориите и glob шаблоните не се разгръщат, а при липсващ файл се хвърля `RuntimeException`.

```php
$repo->add('src/Feature.php', 'tests/FeatureTest.php');
```

#### `remove`

```php
public function remove(string ...$paths): void
public function removeCached(string ...$paths): void
```

Премахва проследявани пътища както от индекса, така и от работното дърво. Поддържа `--cached` и `-r` / `--recursive` в списъка с аргументи за поведение като при Git.

```php
$repo->remove('old-file.txt');
$repo->remove('-r', 'src');
$repo->remove('--cached', 'old-file.txt');
$repo->removeCached('old-file.txt');
```

#### `mv`

```php
public function mv(string $source, string $destination): void
```

Премества/преименува файл както в работното дърво, така и в индекса.

```php
$repo->mv('old-name.php', 'new-name.php');
```

---

### Потвърждения

#### `commit`

```php
public function commit(?string $message = null, ?string $author = null): ObjectId
```

Създава commit от текущия индекс. Изгражда йерархия на дървото от записите в индекса, създава commit обект с HEAD като родител (ако има такъв) и обновява HEAD. Връща `ObjectId` на новия commit.

`$message` може да бъде `null` само когато сливане, cherry-pick, revert или rebase е спряло заради конфликт; тогава се използва подготвеното съобщение. В противен случай съобщение `null` хвърля `RuntimeException('Commit message required')`. Commit също хвърля грешка, когато индексът съдържа несляти пътища, когато индексът е празен в хранилище без commit-и или когато дървото не се различава от това в HEAD.

Параметърът `$author` е пълен git низ за автор (`Name <email> timestamp timezone`). Ако е null, се определя от променливи на средата, константи и `.git/config` в реда, описан в [Първи стъпки](https://pitmaster.inline0.dev/bg/docs/getting-started).

```php
$id = $repo->commit('Fix null pointer bug');
```

#### `show`

```php
public function show(string $revision): array
```

Показва commit-ish и разликите му спрямо първия родител. Когато ревизията съответства на анотиран таг, върнатата стойност включва и `['tag' => Tag]`, а `commit` е целевият commit след премахване на тага.

```php
$result = $repo->show('HEAD');
echo $result['commit']->message;

foreach ($result['diff'] as $diff) {
    echo $diff->format();
}
```

#### `reset`

```php
public function reset(string $revision, string $mode = 'mixed'): void
```

Нулира HEAD до commit. Режими:
- `'soft'`: премества само HEAD
- `'mixed'`: премества HEAD + нулира индекса (по подразбиране)
- `'hard'`: премества HEAD + нулира индекса + нулира работното дърво

```php
$repo->reset('HEAD~1', 'soft');
$repo->reset('main', 'hard');
```

#### `restore`

```php
public function restore(string $path, ?string $source = null, bool $staged = false, bool $worktree = false): void
```

Възстановява път от индекса или от конкретна изходна ревизия. Ако не е зададен нито един флаг, Pitmaster възстановява работното дърво от индекса (или от `$source`, ако е зададен). `staged: true` възстановява индекса от HEAD (или от `$source`) и не променя работното дърво; подайте `staged: true, worktree: true`, за да възстановите и двете.

```php
$repo->restore('src/File.php');                                        // Worktree from index
$repo->restore('src/File.php', staged: true);                          // Index from HEAD
$repo->restore('src/File.php', 'HEAD~1');                              // Worktree from another commit
$repo->restore('src/File.php', 'HEAD~1', staged: true, worktree: true); // Index + worktree from another commit
```

#### `cherryPick`

```php
public function cherryPick(string $revision): ObjectId
public function cherryPickContinue(): ObjectId
public function cherryPickAbort(): void
```

Прилага commit като нов commit върху текущия клон, като запазва първоначалния автор. Ако операцията спре заради конфликти, разрешете ги, индексирайте пътищата и извикайте `cherryPickContinue()` или `cherryPickAbort()`.

```php
$newId = $repo->cherryPick('feature~2');

// After resolving conflicts:
$repo->add('src/File.php');
$repo->cherryPickContinue();
```

#### `revert`

```php
public function revert(string $revision): ObjectId
public function revertContinue(): ObjectId
public function revertAbort(): void
```

Създава commit, който отменя друг commit. Ако revert спре заради конфликти, разрешете ги, индексирайте пътищата и извикайте `revertContinue()` или `revertAbort()`.

```php
$revertId = $repo->revert('HEAD~1');

// After resolving conflicts:
$repo->add('src/File.php');
$repo->revertContinue();
```

#### `rebase`

```php
public function rebase(string $onto): array
public function rebaseContinue(): array
public function rebaseAbort(): void
public function rebaseSkip(): array
```

Извършва rebase на текущия клон върху друга ревизия. Pitmaster засега поддържа линейни rebase операции със състояние при спиране заради конфликти във формата на Git, както и продължаване, прекратяване и пропускане. HEAD трябва да сочи към клон.

`rebase()`, `rebaseContinue()` и `rebaseSkip()` връщат `['success' => bool, 'commits' => int, 'conflicts' => list<string>]`: дали rebase е завършил, колко commit-а са приложени и кои пътища са в конфликт при спирането.

```php
$result = $repo->rebase('main');

if (!$result['success']) {
    // Resolve conflicts, then continue or skip/abort.
    $repo->add('src/File.php');
    $repo->rebaseContinue();
}
```

---

### Състояние

#### `status`

```php
public function status(): array
```

Изчислява състоянието на работното дърво. Връща масив от обекти `StatusEntry`, всеки от които съдържа:
- `path`: пътя до файла
- `index`: enum `FileStatus` за промените от HEAD до индекса
- `worktree`: enum `FileStatus` за промените от индекса до работното дърво
- `origPath`, `renameScore`: зададени за преименувани записи, в противен случай `null`

Непроследяваните файлове имат `FileStatus::Untracked` и в двете колони. Игнорираните файлове не се показват.

```php
foreach ($repo->status() as $entry) {
    echo $entry->shortFormat() . "\n"; // "M  src/File.php", "?? notes.txt"
}
```

#### `statusPorcelainV2`

```php
public function statusPorcelainV2(): string
```

Изход за машинна обработка на състоянието във формат porcelain v2 на git.

```php
echo $repo->statusPorcelainV2();
// 1 M. N... 000000 000000 000000 0000...0000 0000...0000 modified-file.txt
// ? untracked-file.txt
```

---

### Разлики

#### `diff`

```php
public function diff(?string $pathspec = null, string $algorithm = 'myers'): array
```

Показва разликите между работното дърво и индекса (неиндексирани промени). Връща масив от обекти `DiffResult`. `$pathspec` филтрира по един точен път до файл. `$algorithm` е `'myers'` (по подразбиране), `'patience'`, `'histogram'` или `'minimal'`; всяка друга стойност хвърля `InvalidArgumentException`.

```php
$diffs = $repo->diff();

foreach ($diffs as $diff) {
    echo $diff->format();  // Unified diff output
}
```

#### `diffStaged`

```php
public function diffStaged(?string $pathspec = null, string $algorithm = 'myers'): array
```

Показва разликите между индекса и HEAD (индексирани промени). Връща `DiffResult[]` с откриване на преименувания между изтрити и добавени файлове.

Аргументът `$pathspec` в момента се игнорира, така че се връщат всички индексирани файлове, а методът връща празен масив преди първия commit ([#5](https://github.com/inline0/pitmaster/issues/5)).

```php
$staged = $repo->diffStaged();
```

#### `diffTree`

```php
public function diffTree(ObjectId $a, ObjectId $b, string $algorithm = 'myers'): array
```

Сравнява две дървета по техните `ObjectId`. Връща `DiffResult[]`. Подайте идентификатори на дървета, а не идентификатори на commit-и: идентификаторът на commit не се разпознава като дърво и резултатът е празен.

```php
$old = $repo->readObject($repo->resolve('HEAD~1')->hex);
$new = $repo->head();
$diffs = $repo->diffTree($old->tree, $new->tree);
```

---

### История

#### `log`

```php
public function log(int $limit = 50, ?ObjectId $from = null): array
```

Обхожда историята на commit-ите, започвайки от най-новия според времевия печат на committer-а. Връща `Commit[]`. Ако `$from` е null, започва от HEAD. Връща празен масив, ако HEAD все още няма commit.

```php
$commits = $repo->log(10);
```

#### `logAll`

```php
public function logAll(int $limit = 50): array
```

Обхожда commit-ите, достижими от всички refs и HEAD, започвайки от най-новия. Връща `Commit[]`.

#### `logOneline`

```php
public function logOneline(int $limit = 50, bool $all = false, ?string $path = null): array
```

Връща редовете от журнала във формат `git log --oneline` (`abc1234 Subject`), от HEAD, от всички refs (`$all`) или филтрирани по път.

#### `logPath`

```php
public function logPath(string $path, int $limit = 50): array
```

Журнал, филтриран по път до файл. Връща само commit-ите, чиито разлики спрямо първия им родител добавят, променят, изтриват или преименуват точно този файл. Пътищата до директории не съвпадат.

Проверяват се само най-новите `5 * $limit` commit-а, затова по-стари commit-и, които засягат файла, може да липсват в резултата ([#7](https://github.com/inline0/pitmaster/issues/7)).

```php
$commits = $repo->logPath('src/Repository.php', 20);
```

---

### Сливане

#### `merge`

```php
public function merge(string $branch, string $strategy = 'recursive'): MergeResult
public function mergeOctopus(array $branches): MergeResult
public function mergeContinue(): ObjectId
public function mergeAbort(): void
```

Слива клон (или произволна ревизия) с HEAD. Автоматично открива сливане с преместване само напред. При останалите сливания проверява за конфликти на ниво дърво и създава merge commit, ако няма конфликти. При конфликт операцията спира и записва състояние на сливане във формат Git в индекса и работното дърво; разрешете конфликтите, индексирайте пътищата и извикайте `mergeContinue()` или `mergeAbort()`.

`$strategy` `'ours'` записва merge commit, който запазва дървото на HEAD; всяка друга стойност използва стандартното рекурсивно сливане. `mergeOctopus()` слива два или повече клона в един commit. Ако има конфликт в някой клон, връща резултат с конфликти, без да записва състояние на сливане или да създава commit.

Сливането на клон, който вече е включен в HEAD, създава още един merge commit, вместо да съобщи „Вече е актуално“ ([#6](https://github.com/inline0/pitmaster/issues/6)). Непотвърдените промени в проследяваните файлове се презаписват, както е описано в `checkout()`.

Връща `MergeResult` със:
- `clean`: дали сливането е завършило без конфликти
- `commitId`: идентификатора на merge commit (ако няма конфликти)
- `conflictPaths`: масив с пътищата до файловете с конфликти (ако има конфликти)
- `mergedContents`: път до файл със съдържание, съдържащо маркери за конфликт (ако има конфликти)

```php
$result = $repo->merge('feature/login');

if ($result->clean) {
    echo "Merged: {$result->commitId->hex}\n";
} else {
    echo "Conflicts in: " . implode(', ', $result->conflictPaths) . "\n";
    $repo->add('src/File.php');
    $repo->mergeContinue();
}
```

#### `mergeBase`

```php
public function mergeBase(ObjectId $a, ObjectId $b): ?ObjectId
```

Намира базата за сливане (най-близкия общ предшественик) на два commit-а. Връща null, ако няма общ предшественик.

```php
$base = $repo->mergeBase(
    $repo->resolve('main'),
    $repo->resolve('feature/login'),
);
```

### Двоично търсене

```php
public function bisectStart(string $bad, string $good): ObjectId
public function bisectGood(?string $revision = null): ?ObjectId
public function bisectBad(?string $revision = null): ?ObjectId
public function bisectReset(): void
```

Търси двоично commit-а, въвел дадена промяна. `bisectStart()` записва лошата и добрата ревизия, checkout-ва първия кандидат и го връща. `bisectGood()` и `bisectBad()` маркират текущия HEAD (или `$revision`), checkout-ват следващия кандидат и го връщат. Когато остане един заподозрян commit, той се връща и остава кандидатът: това е първият лош commit. `bisectReset()` завършва сесията и се връща към клона или commit-а, от който е започнала.

```php
$candidate = $repo->bisectStart('HEAD', 'v1.0.0');
// test $candidate ...
$candidate = $repo->bisectBad();
$candidate = $repo->bisectGood();
$repo->bisectReset();
```

---

### Мрежа

#### `fetch`

```php
public function fetch(string $remote = 'origin', ?int $depth = null): void
```

Извлича промени от отдалечено хранилище. Изтегля нови обекти и обновява refs за проследяване на отдалечени клонове. Подайте `$depth`, за да задълбочите или ограничите плитката история.

`fetch()` и методите `push*()` хвърлят `RuntimeException` за манипулатор, отворен с `['processes' => false]`.

```php
$repo->fetch();
$repo->fetch('upstream');
```

#### `push`

```php
public function push(string $remote = 'origin', ?string $branch = null): void
public function pushForceWithLease(string $remote = 'origin', ?string $branch = null, ?ObjectId $expected = null): void
public function pushAtomic(string $remote = 'origin', array $branches = []): void
public function pushMirror(string $remote = 'origin'): void
```

Изпраща текущия клон (или зададен клон) към отдалечено хранилище. Налични са варианти с force-with-lease, атомарно изпращане и огледално копие за по-строги или по-широки актуализации на refs.

```php
$repo->push();
$repo->push('origin', 'feature/login');
$repo->pushForceWithLease('origin', 'main', $repo->resolve('refs/remotes/origin/main'));
$repo->pushAtomic('origin', ['main', 'release']);
$repo->pushMirror();
```

---

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

#### `addWorktree`

```php
public function addWorktree(string $path, string $branch, ?ObjectId $from = null, ?string $name = null): Worktree
```

Добавя свързано работно дърво. Създава клона, ако не съществува, настройва метаданните на работното дърво и създава файловете в него. Подайте `$name`, когато ви е необходим детерминистичен slug за метаданните, различен от основното име на директорията за checkout.

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

#### `removeWorktree`

```php
public function removeWorktree(string $pathOrName, bool $force = false): void
```

Премахва свързано работно дърво. Използвайте `$force = true`, за да го премахнете дори ако е заключено.

#### `worktrees`

```php
public function worktrees(): array
```

Извежда списък на всички работни дървета (основното и свързаните). Връща `Worktree[]`.

```php
foreach ($repo->worktrees() as $wt) {
    echo "{$wt->path} [{$wt->branch}]\n";
}
```

---

### Конфигурация

#### `config`

```php
public function config(): GitConfig
```

Достъп до конфигурацията на git на хранилището. Чете и записва конфигурационни стойности.

```php
$config = $repo->config();
$name = $config->get('user.name');
$config->set('user.email', 'new@example.com');
```

---

### Вътрешен достъп

Тези методи предоставят достъп до вътрешни компоненти за разширени случаи на употреба.

#### `objectDatabase`

```php
public function objectDatabase(): ObjectDatabase
```

Достъп до базата данни със сурови обекти (loose + pack хранилища).

#### `refDatabase`

```php
public function refDatabase(): RefDatabase
```

Достъп до базата данни със сурови refs (loose + packed refs).

---

## Класове от ниско ниво

Тези класове не са обвити от `Repository` и се създават от вътрешните му компоненти.

### `Pitmaster\Graph\Blame`

```php
public function blame(ObjectId $headId, string $path): array
```

Връща по един запис на ред от `$path` в `$headId`: `['line' => int, 'hash' => string, 'author' => string, 'content' => string]`. `author` е пълният низ за идентичност (`Name <email> timestamp timezone`).

```php
use Pitmaster\Graph\Blame;

$blame = new Blame($repo->objectDatabase());

foreach ($blame->blame($repo->resolve('HEAD'), 'src/File.php') as $line) {
    echo substr($line['hash'], 0, 8) . " {$line['content']}\n";
}
```

### `Pitmaster\Graph\Grep`

```php
public function grep(ObjectId $treeId, string $pattern, string $prefix = '', array $options = []): array
```

Търси в blob-овете на дърво. Връща по `['path' => string, 'line' => int, 'content' => string]` за всеки съвпадащ ред. Опции: `regex` (третира шаблона като PCRE шаблон) и `ignore_case`. Невалиден regex хвърля `InvalidArgumentException`.

```php
use Pitmaster\Graph\Grep;

$grep = new Grep($repo->objectDatabase());
$matches = $grep->grep($repo->head()->tree, 'TODO', '', ['ignore_case' => true]);
```

### `Pitmaster\Ref\Notes`

```php
public function get(ObjectId $commitId, string $notesRef = 'refs/notes/commits'): ?string
public function set(ObjectId $commitId, string $noteContent, string $notesRef = 'refs/notes/commits'): void
public function remove(ObjectId $commitId, string $notesRef = 'refs/notes/commits'): void
public function listAll(string $notesRef = 'refs/notes/commits'): array
public function merge(string $sourceRef, string $targetRef = 'refs/notes/commits'): void
```

Чете и записва git бележки, съхранени под notes ref. `listAll()` връща хеш на commit към съдържанието на бележката.

```php
use Pitmaster\Ref\Notes;

$notes = new Notes($repo->objectDatabase(), $repo->refDatabase());
$notes->set($repo->resolve('HEAD'), "Reviewed\n");
echo $notes->get($repo->resolve('HEAD'));
```
