
# PHP API

## `Pitmaster\Pitmaster`

A classe `Pitmaster\Pitmaster` é o ponto de entrada estático. Ela oferece métodos de fábrica para abrir, criar e clonar repositórios, além de funções auxiliares de detecção.

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

### `open`

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

Abra um repositório existente em `$path`. O caminho deve ser a raiz da árvore de trabalho (que contém `.git/`). Também oferece suporte a árvores de trabalho vinculadas (nas quais `.git` é um arquivo que contém `gitdir: <path>`) e a repositórios bare (nos quais `$path` é o próprio diretório `.git`).

`$options` aceita dois valores booleanos, ambos `true` por padrão:

- `hooks`: executar scripts `.git/hooks`. Passe `false` para ignorá-los neste handle.
- `processes`: permitir qualquer operação que inicie um processo ou abra um transporte de rede. Passe `false` para desativar hooks, o hook fsmonitor, clone, fetch, push e o transporte de processo SSH. Leituras e gravações locais continuam funcionando.

Lança `InvalidArgumentException` se o caminho não for um repositório 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
```

Inicialize um novo repositório em `$path`. Cria a estrutura de diretórios `.git` com `objects/`, `refs/heads/`, `refs/tags/`, `hooks/`, `info/`, `HEAD` (apontando para `refs/heads/main`) e um `config` padrão.

`$objectFormat` é `'sha1'` ou `'sha256'`. Um repositório SHA-256 recebe `repositoryformatversion = 1` e `extensions.objectformat = sha256` em sua configuração. Qualquer outro valor lança `InvalidArgumentException`. `$options` aceita as mesmas chaves que `open()`.

Lança `RuntimeException` se já existir um repositório no caminho.

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

Clone um repositório remoto. Inicializa um novo repositório, descobre referências remotas, busca o arquivo pack, configura ramificações de acompanhamento remoto e materializa a árvore de trabalho. URLs SSH (`ssh://...` ou `user@host:path`) usam o transporte SSH; outras URLs usam HTTP inteligente e recorrem a HTTP simples quando a troca inteligente falha. Passe `$depth` para uma clonagem superficial.

Lança `RuntimeException` quando `$options['processes']` for `false`. Se a clonagem falhar e `$path` não existia antes, o diretório criado parcialmente é removido.

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

Retorna `['processes' => false, 'hooks' => false]`, para ambientes de execução que não devem iniciar processos no host. Passe-o para `open()` ou `init()`.

### `isRepository`

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

Verifique se um caminho é um repositório git. Retorna `true` para repositórios comuns (diretório `.git/`), árvores de trabalho vinculadas (arquivo `.git`) e repositórios bare (arquivo `HEAD` presente).

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

### `isWorktree`

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

Verifique se um caminho é uma árvore de trabalho vinculada (não o repositório principal). Retorna `true` somente se `.git` for um arquivo que contém uma referência `gitdir:`.

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

### `commonGitDir`

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

Resolva o diretório git comum a partir de qualquer caminho de cópia de trabalho. Em repositórios comuns, esse é o diretório `.git`. Em árvores de trabalho vinculadas, é o diretório compartilhado git, que contém objetos, configuração e packed-refs. Retorna `null` se o caminho não for um repositório.

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

---

## `Pitmaster\Repository`

A classe `Pitmaster\Repository` é o handle principal para todas as operações de git. Obtenha uma por meio de `Pitmaster::open()`, `Pitmaster::init()` ou `Pitmaster::clone()`.

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

### Acessores de caminho

#### `gitDir`

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

Retorna o diretório git por árvore de trabalho. Em repositórios comuns, é `.git/`. Em árvores de trabalho vinculadas, é `.git/worktrees/<name>/`.

#### `commonGitDir`

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

Retorna o diretório git compartilhado. É igual a `gitDir()` em repositórios comuns. Em árvores de trabalho vinculadas, é o diretório `.git/` do repositório principal (onde ficam os objetos, a configuração e packed-refs).

#### `workDir`

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

Retorna o caminho raiz da árvore de trabalho.

#### `isLinkedWorktree`

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

Retorna `true` se este handle de repositório for uma árvore de trabalho vinculada.

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

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

Informa se o repositório não tem árvore de trabalho e quais opções `hooks` / `processes` estão ativas para este handle. `hooksEnabled()` é `false` sempre que os processos estão desativados.

---

### Objetos

#### `readObject`

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

Leia qualquer objeto pelo hash hexadecimal completo (40 caracteres ou 64 em um repositório SHA-256). Hashes abreviados não são aceitos. Retorna uma instância `Blob`, `Tree`, `Commit` ou `Tag`. Pesquisa primeiro os objetos soltos e depois os arquivos pack.

Lança `ObjectNotFoundException` se o hash não for encontrado.

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

#### `writeObject`

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

Grave um objeto no armazenamento de objetos soltos. Retorna o `ObjectId` calculado.

```php
use Pitmaster\Object\Blob;

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

#### `catFile`

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

Retorne o conteúdo bruto de um objeto, equivalente a `git cat-file -p`.

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

#### `objectExists`

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

Verifique se existe um objeto no repositório (solto ou empacotado).

#### `listObjects`

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

Liste todos os hashes de objetos no repositório. Retorna um array de strings hexadecimais.

---

### Referências

#### `head`

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

Retorna o commit HEAD atual. Segue referências simbólicas (HEAD -> refs/heads/main -> hash do commit).

Lança `RuntimeException` se HEAD não apontar para um commit válido (e.g., repositório vazio).

#### `branch`

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

Sem argumentos, retorna o nome da ramificação atual (e.g., `'main'`) ou `null` se HEAD estiver desanexado.

Com um nome de ramificação, resolve-o para o hash do commit (string hexadecimal) ou retorna `null` se a ramificação não existir.

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

#### `branches`

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

Retorna um array ordenado com todos os nomes de ramificações (sem o prefixo `refs/heads/`).

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

#### `tags`

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

Retorna um array ordenado com todos os nomes de tags (sem o prefixo `refs/tags/`).

#### `resolve`

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

Resolva uma expressão de revisão para um `ObjectId`. Compatível com:
- Hashes hexadecimais completos (40 caracteres ou 64 para SHA-256). Hashes abreviados, como `abc1234`, não são resolvidos.
- `HEAD`
- Nomes de ramificações (`main`, `feature/login`)
- Nomes de tags (`v1.0.0`)
- Caminhos completos de referências (`refs/heads/main`)
- Expressões de revisão (`HEAD~3`, `main^2`)

Lança `RuntimeException` se a revisão não puder ser resolvida.

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

#### `allRefs`

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

Retorna todas as referências em um array associativo de nome da referência para hash hexadecimal.

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

#### `updateRef`

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

Atualize uma referência para apontar para um novo destino.

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

#### `createBranch`

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

Crie uma nova ramificação. Se `$from` for null, cria uma ramificação a partir do HEAD atual.

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

#### `deleteBranch`

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

Exclua uma ramificação pelo nome. Lança `RuntimeException` se a ramificação estiver em uso ou não existir. Ao contrário de `git branch -d`, não verifica se a ramificação foi mesclada.

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

#### `createLightweightTag`

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

Crie uma tag leve. Se `$target` for null, marca diretamente o HEAD atual.

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

Cria uma tag anotada. Se `$target` for nulo, marca o HEAD atual. Retorna o `ObjectId` do objeto da tag.

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

#### `deleteTag`

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

Exclui uma tag pelo nome.

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

#### `packRefs`

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

Reescreve o arquivo compartilhado `packed-refs` com base no conjunto atual de referências soltas e remove as referências soltas compartilhadas correspondentes. Em worktrees vinculadas, grava no diretório comum git.

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

#### `defaultBranch`

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

Resolve a branch padrão do repositório. Primeiro verifica a symref HEAD remota, depois o HEAD local e, por fim, recorre a `main` ou `master`.

#### `isBranchMerged`

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

Verifica se uma branch foi totalmente mesclada a outra (por padrão, à branch padrão).

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

#### `checkout`

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

Alterna entre branches ou desanexa o HEAD. Atualiza o HEAD, o índice e a árvore de trabalho. `$target` é o nome de uma branch, uma tag, um hash completo ou uma expressão de revisão; qualquer valor que não seja uma branch desanexa o HEAD.

Lança `RuntimeException` quando uma alteração local (preparada ou não) ou um arquivo não rastreado está em um caminho que difere entre a árvore atual e a de destino.

Alterações não confirmadas em outros arquivos rastreados não são preservadas: o índice e a árvore de trabalho são reconstruídos a partir da árvore de destino, então as edições em arquivos idênticos nos dois lados são perdidas, enquanto git as manteria ([#2](https://github.com/inline0/pitmaster/issues/2)). Faça commit ou stash delas primeiro. `merge()`, `cherryPick()`, `revert()`, `rebase()` e os métodos de bisect reconstroem a árvore de trabalho da mesma forma.

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

---

### Índice

#### `index`

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

Lê e retorna o índice atual (área de preparação) de `.git/index`.

#### `add`

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

Prepara arquivos. Lê cada arquivo da árvore de trabalho, cria um objeto blob e atualiza o índice. Cada argumento deve ser o caminho de um arquivo (ou link simbólico) relativo à raiz da árvore de trabalho; diretórios e padrões glob não são expandidos, e a ausência de um arquivo lança `RuntimeException`.

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

#### `remove`

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

Remove caminhos rastreados tanto do índice quanto da árvore de trabalho. Aceita `--cached` e `-r` / `--recursive` na lista de argumentos para ter o comportamento no estilo 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
```

Move ou renomeia um arquivo tanto na árvore de trabalho quanto no índice.

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

---

### Commits

#### `commit`

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

Cria um commit a partir do índice atual. Constrói uma hierarquia de árvores com as entradas do índice, cria o objeto commit com o HEAD como pai (se houver) e atualiza o HEAD. Retorna o `ObjectId` do novo commit.

`$message` só pode ser `null` enquanto um merge, cherry-pick, revert ou rebase estiver parado por causa de um conflito; nesse caso, usa-se a mensagem preparada. Caso contrário, uma mensagem `null` lança `RuntimeException('Commit message required')`. Um commit também lança uma exceção quando o índice tem caminhos não mesclados, quando o índice está vazio em um repositório sem commits e quando a árvore não mudou em relação ao HEAD.

O parâmetro `$author` é uma string de autor git completa (`Name <email> timestamp timezone`). Se for nulo, o valor é resolvido a partir de variáveis de ambiente, constantes e `.git/config`, na ordem descrita em [Primeiros passos](https://pitmaster.inline0.dev/pt/docs/getting-started).

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

#### `show`

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

Exibe um commit-ish e seu diff em relação ao primeiro pai. Quando a revisão é resolvida como uma tag anotada, o valor de retorno também inclui `['tag' => Tag]`, e `commit` é o commit de destino após a remoção da tag.

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

Redefine o HEAD para um commit. Modos:
- `'soft'`: move apenas o HEAD
- `'mixed'`: move o HEAD e redefine o índice (padrão)
- `'hard'`: move o HEAD, redefine o índice e redefine a árvore de trabalho

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

Restaura um caminho a partir do índice ou de uma revisão de origem específica. Se nenhuma das flags for definida, Pitmaster restaura a árvore de trabalho a partir do índice (ou de `$source`, quando especificado). `staged: true` restaura o índice a partir do HEAD (ou de `$source`) e não altera a árvore de trabalho; passe `staged: true, worktree: true` para restaurar ambos.

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

Aplica um commit como um novo commit na branch atual, preservando o autor original. Se conflitos interromperem a operação, resolva-os, prepare os caminhos e chame `cherryPickContinue()` ou `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
```

Cria um commit que desfaz outro commit. Se conflitos interromperem o revert, resolva-os, prepare os caminhos e chame `revertContinue()` ou `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
```

Faz rebase da branch atual sobre outra revisão. Atualmente, Pitmaster abrange rebases lineares com estado de interrupção no estilo Git para conflitos, além de continuar, abortar e ignorar. O HEAD deve apontar para uma branch.

`rebase()`, `rebaseContinue()` e `rebaseSkip()` retornam `['success' => bool, 'commits' => int, 'conflicts' => list<string>]`: se o rebase terminou, quantos commits foram aplicados e os caminhos conflitantes quando houve uma interrupção.

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

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

---

### Status

#### `status`

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

Calcula o status da árvore de trabalho. Retorna um array de objetos `StatusEntry`, cada um contendo:
- `path`: o caminho do arquivo
- `index`: enum `FileStatus` para alterações do HEAD ao índice
- `worktree`: enum `FileStatus` para alterações do índice à árvore de trabalho
- `origPath`, `renameScore`: definidos para entradas renomeadas; caso contrário, `null`

Arquivos não rastreados têm `FileStatus::Untracked` nas duas colunas. Arquivos ignorados não são informados.

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

#### `statusPorcelainV2`

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

Saída de status legível por máquina no formato porcelana v2 de git.

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

---

### Diff

#### `diff`

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

Compara a árvore de trabalho com o índice (alterações não preparadas). Retorna um array de objetos `DiffResult`. `$pathspec` filtra por um caminho de arquivo exato. `$algorithm` pode ser `'myers'` (padrão), `'patience'`, `'histogram'` ou `'minimal'`; qualquer outro valor lança `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
```

Compara o índice com o HEAD (alterações preparadas). Retorna `DiffResult[]`, com detecção de renomeações entre arquivos excluídos e adicionados.

O argumento `$pathspec` é ignorado no momento, então todos os arquivos preparados são retornados, e o método retorna um array vazio antes do primeiro 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
```

Compara duas árvores pelas `ObjectId`. Retorna `DiffResult[]`. Passe IDs de árvore, não IDs de commit: um ID de commit não é lido como árvore e produz um resultado vazio.

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

---

### Log

#### `log`

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

Percorre o histórico de commits, do mais recente ao mais antigo pela data do committer. Retorna `Commit[]`. Se `$from` for nulo, começa no HEAD. Retorna um array vazio se o HEAD ainda não tiver um commit.

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

#### `logAll`

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

Percorre os commits alcançáveis a partir de cada referência e do HEAD, do mais recente ao mais antigo. Retorna `Commit[]`.

#### `logOneline`

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

Retorna linhas do log no formato `git log --oneline` (`abc1234 Subject`), a partir do HEAD, de todas as referências (`$all`) ou filtradas por caminho.

#### `logPath`

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

Exibe o log filtrado pelo caminho de um arquivo. Retorna apenas commits cujo diff em relação ao primeiro pai adiciona, modifica, exclui ou renomeia exatamente esse arquivo. Caminhos de diretórios não correspondem.

Somente os `5 * $limit` commits mais recentes são examinados, então commits mais antigos que alteraram o arquivo podem estar ausentes do resultado ([#7](https://github.com/inline0/pitmaster/issues/7)).

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

---

### Merge

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

Mescla uma branch (ou qualquer revisão) no HEAD. Detecta mesclagens fast-forward automaticamente. Em mesclagens que não sejam fast-forward, detecta conflitos no nível da árvore e cria um commit de merge se não houver conflitos. Se houver conflitos, grava no índice e na árvore de trabalho um estado de merge no estilo Git; resolva os caminhos, prepare-os e chame `mergeContinue()` ou `mergeAbort()`.

`$strategy` `'ours'` registra um commit de merge que mantém a árvore do HEAD; qualquer outro valor usa o merge recursivo padrão. `mergeOctopus()` mescla duas ou mais branches em um commit. Se alguma branch tiver conflitos, retorna um resultado com conflitos sem gravar o estado do merge nem criar um commit.

Mesclar uma branch que já está contida no HEAD cria outro commit de merge, em vez de informar "Already up to date" ([#6](https://github.com/inline0/pitmaster/issues/6)). Alterações não confirmadas em arquivos rastreados são sobrescritas conforme descrito em `checkout()`.

Retorna um `MergeResult` com:
- `clean`: se o merge terminou sem conflitos
- `commitId`: ID do commit de merge (se não houver conflitos)
- `conflictPaths`: array de caminhos de arquivos conflitantes (se houver conflitos)
- `mergedContents`: caminho conflitante para o conteúdo do arquivo com marcadores de conflito (se houver conflitos)

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

Encontra a base do merge (ancestral comum mais baixo) de dois commits. Retorna nulo se não houver ancestral comum.

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

### Bisect

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

Faz uma busca binária pelo commit que introduziu uma alteração. `bisectStart()` registra as revisões boas e ruins, verifica o primeiro candidato e o retorna. `bisectGood()` e `bisectBad()` marcam o HEAD atual (ou `$revision`), verificam o próximo candidato e o retornam. Quando resta um único suspeito, esse commit é retornado e permanece como candidato: é o primeiro commit ruim. `bisectReset()` encerra a sessão e retorna à branch ou ao commit em que ela começou.

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

---

### Rede

#### `fetch`

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

Busca dados de um repositório remoto. Baixa novos objetos e atualiza as referências de rastreamento remoto. Passe `$depth` para aprofundar ou limitar um histórico raso.

`fetch()` e os métodos `push*()` lançam `RuntimeException` em um identificador aberto com `['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
```

Envia a branch atual (ou uma branch especificada) para um repositório remoto. Há variantes com force-with-lease, atômicas e espelhadas para atualizações de referências mais estritas ou abrangentes.

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

---

### Worktrees

#### `addWorktree`

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

Adiciona uma worktree vinculada. Cria a branch se ela não existir, configura os metadados da worktree e materializa os arquivos da árvore de trabalho. Passe `$name` quando precisar de um identificador de metadados determinístico que seja diferente do nome-base do diretório do 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
```

Remove uma worktree vinculada. Use `$force = true` para removê-la mesmo que esteja bloqueada.

#### `worktrees`

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

Lista todas as worktrees (principal e vinculadas). Retorna `Worktree[]`.

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

---

### Configuração

#### `config`

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

Acessa a configuração git do repositório. Lê e grava valores de configuração.

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

---

### Acesso interno

Esses métodos expõem componentes internos para casos de uso avançados.

#### `objectDatabase`

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

Acessa o banco de dados bruto de objetos (armazenamentos soltos e compactados).

#### `refDatabase`

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

Acessa o banco de dados bruto de referências (referências soltas e compactadas).

---

## Classes de nível inferior

Essas classes não são envolvidas por `Repository` e são instanciadas a partir de seus componentes internos.

### `Pitmaster\Graph\Blame`

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

Retorna uma entrada por linha de `$path` em `$headId`: `['line' => int, 'hash' => string, 'author' => string, 'content' => string]`. `author` é a string de identidade completa (`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
```

Pesquisa os blobs de uma árvore. Retorna `['path' => string, 'line' => int, 'content' => string]` por linha correspondente. Opções: `regex` (trata o padrão como um padrão PCRE) e `ignore_case`. Uma expressão regular inválida lança `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
```

Lê e grava notas git armazenadas sob uma referência de notas. `listAll()` retorna um hash de commit para o conteúdo da nota.

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