
# PHP API

## `Pitmaster\Pitmaster`

La clase `Pitmaster\Pitmaster` es el punto de entrada estático. Proporciona métodos de fábrica para abrir, crear y clonar repositorios, además de funciones auxiliares de detección.

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

### `open`

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

Abra un repositorio existente en `$path`. La ruta debe ser la raíz del árbol de trabajo (que contiene `.git/`). También admite worktrees vinculados (donde `.git` es un archivo que contiene `gitdir: <path>`) y repositorios bare (donde `$path` es el propio directorio `.git`).

`$options` acepta dos valores booleanos, ambos `true` de forma predeterminada:

- `hooks`: ejecuta scripts `.git/hooks`. Pase `false` para omitirlos en este controlador.
- `processes`: permite cualquier acción que inicie un proceso o abra un transporte de red. Pase `false` para desactivar los hooks, el hook fsmonitor, clone, fetch, push y el transporte de procesos SSH. Las lecturas y escrituras locales siguen funcionando.

Lanza `InvalidArgumentException` si la ruta no es un repositorio 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
```

Inicialice un repositorio nuevo en `$path`. Crea la estructura de directorios `.git` con `objects/`, `refs/heads/`, `refs/tags/`, `hooks/`, `info/`, `HEAD` (que apunta a `refs/heads/main`) y un `config` predeterminado.

`$objectFormat` es `'sha1'` o `'sha256'`. Un repositorio SHA-256 obtiene `repositoryformatversion = 1` y `extensions.objectformat = sha256` en su configuración. Cualquier otro valor lanza `InvalidArgumentException`. `$options` acepta las mismas claves que `open()`.

Lanza `RuntimeException` si ya existe un repositorio en la ruta.

```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 un repositorio remoto. Inicializa un repositorio nuevo, descubre las referencias remotas, descarga el archivo pack, configura las ramas de seguimiento remoto y materializa el árbol de trabajo. Las URL SSH (`ssh://...` o `user@host:path`) usan el transporte SSH; las demás URL usan HTTP inteligente y recurren a HTTP simple cuando falla el intercambio inteligente. Pase `$depth` para un clon superficial.

Lanza `RuntimeException` cuando `$options['processes']` sea `false`. Si el clon falla y `$path` no existía antes, se elimina el directorio creado parcialmente.

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

Devuelve `['processes' => false, 'hooks' => false]` para entornos de ejecución que no deben iniciar procesos del host. Páselo a `open()` o `init()`.

### `isRepository`

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

Comprueba si una ruta es un repositorio git. Devuelve `true` para repositorios normales (directorio `.git/`), worktrees vinculados (archivo `.git`) y repositorios bare (si existe el archivo `HEAD`).

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

### `isWorktree`

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

Comprueba si una ruta es un worktree vinculado (no el repositorio principal). Devuelve `true` solo si `.git` es un archivo que contiene una referencia `gitdir:`.

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

### `commonGitDir`

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

Resuelve el directorio git común a partir de cualquier ruta de checkout. En repositorios normales, es el directorio `.git`. En worktrees vinculados, es el directorio git compartido que contiene objetos, configuración y packed-refs. Devuelve `null` si la ruta no es un repositorio.

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

---

## `Pitmaster\Repository`

La clase `Pitmaster\Repository` es el controlador principal para todas las operaciones de git. Obtenga una instancia mediante `Pitmaster::open()`, `Pitmaster::init()` o `Pitmaster::clone()`.

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

### Acceso a rutas

#### `gitDir`

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

Devuelve el directorio git de cada worktree. En repositorios normales, es `.git/`. En worktrees vinculados, es `.git/worktrees/<name>/`.

#### `commonGitDir`

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

Devuelve el directorio git compartido. En repositorios normales, es igual que `gitDir()`. En worktrees vinculados, es el directorio `.git/` del repositorio principal (donde se encuentran los objetos, la configuración y packed-refs).

#### `workDir`

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

Devuelve la ruta raíz del árbol de trabajo.

#### `isLinkedWorktree`

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

Devuelve `true` si este controlador de repositorio es un worktree vinculado.

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

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

Indica si el repositorio carece de árbol de trabajo y cuáles opciones de `hooks` / `processes` están activas para este controlador. `hooksEnabled()` es `false` siempre que los procesos estén desactivados.

---

### Objetos

#### `readObject`

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

Lee cualquier objeto mediante su hash hexadecimal completo (40 caracteres, o 64 en un repositorio SHA-256). No se aceptan hashes abreviados. Devuelve una instancia `Blob`, `Tree`, `Commit` o `Tag`. Busca primero en los objetos sueltos y luego en los archivos pack.

Lanza `ObjectNotFoundException` si no se encuentra el hash.

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

#### `writeObject`

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

Escribe un objeto en el almacén de objetos sueltos. Devuelve el `ObjectId` calculado.

```php
use Pitmaster\Object\Blob;

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

#### `catFile`

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

Devuelve el contenido sin procesar de un objeto, equivalente a `git cat-file -p`.

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

#### `objectExists`

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

Comprueba si existe un objeto en el repositorio (suelto o empaquetado).

#### `listObjects`

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

Enumera todos los hashes de objetos del repositorio. Devuelve un array de cadenas hexadecimales.

---

### Referencias

#### `head`

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

Devuelve el commit HEAD actual. Sigue las referencias simbólicas (HEAD -> refs/heads/main -> hash del commit).

Lanza `RuntimeException` si HEAD no apunta a un commit válido (e.g., repositorio vacío).

#### `branch`

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

Sin argumentos, devuelve el nombre de la rama actual (e.g., `'main'`) o `null` si HEAD está separado de una rama.

Con un nombre de rama, lo resuelve a su hash de commit (cadena hexadecimal) o devuelve `null` si la rama no existe.

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

#### `branches`

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

Devuelve un array ordenado de todos los nombres de rama (sin el prefijo `refs/heads/`).

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

#### `tags`

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

Devuelve un array ordenado de todos los nombres de etiqueta (sin el prefijo `refs/tags/`).

#### `resolve`

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

Resuelve una expresión de revisión a un `ObjectId`. Admite:
- Hashes hexadecimales completos (40 caracteres, o 64 para SHA-256). No resuelve hashes abreviados como `abc1234`.
- `HEAD`
- Nombres de rama (`main`, `feature/login`)
- Nombres de etiqueta (`v1.0.0`)
- Rutas completas de referencia (`refs/heads/main`)
- Expresiones de revisión (`HEAD~3`, `main^2`)

Lanza `RuntimeException` si no se puede resolver la revisión.

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

#### `allRefs`

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

Devuelve todas las referencias en un array asociativo de nombres de referencia a hashes hexadecimales.

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

#### `updateRef`

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

Actualiza una referencia para que apunte a un destino nuevo.

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

#### `createBranch`

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

Crea una rama nueva. Si `$from` es null, crea la rama desde el HEAD actual.

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

#### `deleteBranch`

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

Elimina una rama por nombre. Lanza `RuntimeException` si la rama tiene checkout o no existe. A diferencia de `git branch -d`, no comprueba si la rama se ha fusionado.

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

#### `createLightweightTag`

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

Crea una etiqueta ligera. Si `$target` es null, etiqueta directamente el HEAD actual.

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

Crea una etiqueta anotada. Si `$target` es null, etiqueta el HEAD actual. Devuelve el `ObjectId` del objeto de etiqueta.

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

#### `deleteTag`

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

Elimina una etiqueta por nombre.

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

#### `packRefs`

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

Reescribe el archivo `packed-refs` compartido a partir del conjunto actual de referencias sueltas y elimina las referencias sueltas compartidas correspondientes. En worktrees vinculados, escribe en el directorio git común.

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

#### `defaultBranch`

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

Resuelve la rama predeterminada del repositorio. Comprueba primero la referencia simbólica HEAD remota, luego el HEAD local y, por último, recurre a `main` o `master`.

#### `isBranchMerged`

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

Comprueba si una rama se ha fusionado por completo con otra (de forma predeterminada, con la rama predeterminada).

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

#### `checkout`

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

Cambia de rama o separa HEAD de una rama. Actualiza HEAD, el índice y el árbol de trabajo. `$target` puede ser un nombre de rama, una etiqueta, un hash completo o una expresión de revisión; si no es una rama, HEAD queda separado.

Lanza `RuntimeException` cuando un cambio local (preparado o no) o un archivo sin seguimiento se encuentre en una ruta que difiera entre el árbol actual y el de destino.

Los cambios sin commit en otros archivos con seguimiento no se conservan: se reconstruyen el índice y el árbol de trabajo a partir del árbol de destino, por lo que se pierden las modificaciones en archivos idénticos en ambos lados, que git conservaría ([#2](https://github.com/inline0/pitmaster/issues/2)). Haga commit de esos cambios o guárdelos en stash primero. `merge()`, `cherryPick()`, `revert()`, `rebase()` y los métodos de bisect reconstruyen el árbol de trabajo del mismo modo.

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

---

### Índice

#### `index`

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

Lee y devuelve el índice actual (área de preparación) desde `.git/index`.

#### `add`

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

Prepara archivos. Lee cada archivo del árbol de trabajo, crea un objeto blob y actualiza el índice. Cada argumento debe ser la ruta de un archivo (o enlace simbólico) relativa a la raíz del árbol de trabajo; no se expanden directorios ni patrones glob, y si falta un archivo se lanza `RuntimeException`.

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

#### `remove`

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

Elimina rutas con seguimiento tanto del índice como del árbol de trabajo. Admite `--cached` y `-r` / `--recursive` en la lista de argumentos para un comportamiento similar a 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
```

Mueve o cambia el nombre de un archivo tanto en el árbol de trabajo como en el índice.

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

---

### Commits

#### `commit`

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

Crea un commit a partir del índice actual. Construye una jerarquía de árboles a partir de las entradas del índice, crea el objeto commit con HEAD como padre (si lo hay) y actualiza HEAD. Devuelve el `ObjectId` del nuevo commit.

`$message` solo puede ser `null` cuando una fusión, selección de commit, reversión o reorganización se detiene por un conflicto; en ese caso, se usa el mensaje preparado. De lo contrario, un mensaje `null` lanza `RuntimeException('Commit message required')`. También se lanza una excepción si el índice tiene rutas sin fusionar, si está vacío en un repositorio sin commits o si el árbol no ha cambiado con respecto a HEAD.

El parámetro `$author` es una cadena completa de autor de git (`Name <email> timestamp timezone`). Si es null, se resuelve a partir de las variables de entorno, las constantes y `.git/config`, en el orden descrito en [Introducción](https://pitmaster.inline0.dev/es/docs/getting-started).

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

#### `show`

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

Muestra un commit o referencia similar y sus diferencias con respecto al primer padre. Cuando la revisión se resuelve en una etiqueta anotada, el valor devuelto también incluye `['tag' => Tag]` y `commit` es el commit de destino de la etiqueta.

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

Restablece HEAD a un commit. Modos:
- `'soft'`: mueve solo HEAD
- `'mixed'`: mueve HEAD y restablece el índice (predeterminado)
- `'hard'`: mueve HEAD, restablece el índice y restablece el árbol de trabajo

```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 una ruta desde el índice o desde una revisión de origen específica. Si no se activa ninguno de los indicadores, Pitmaster restaura el árbol de trabajo desde el índice (o desde `$source` si se indica). `staged: true` restaura el índice desde HEAD (o desde `$source`) y deja intacto el árbol de trabajo; pase `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 un commit como un commit nuevo en la rama actual y conserva el autor original. Si la operación se detiene por conflictos, resuélvalos, prepare las rutas y luego llame a `cherryPickContinue()` o `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
```

Crea un commit que deshace otro commit. Si la reversión se detiene por conflictos, resuélvalos, prepare las rutas y luego llame a `revertContinue()` o `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
```

Reorganiza la rama actual sobre otra revisión. Actualmente, Pitmaster admite reorganizaciones lineales con un estado de detención Git similar para los conflictos, además de continuar, abortar y omitir. HEAD debe apuntar a una rama.

`rebase()`, `rebaseContinue()` y `rebaseSkip()` devuelven `['success' => bool, 'commits' => int, 'conflicts' => list<string>]`: si la reorganización terminó, cuántos commits se aplicaron y cuáles eran las rutas en conflicto cuando se detuvo.

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

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

---

### Estado

#### `status`

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

Calcula el estado del árbol de trabajo. Devuelve un array de objetos `StatusEntry`, cada uno con:
- `path`: la ruta del archivo
- `index`: enumeración `FileStatus` de los cambios entre HEAD y el índice
- `worktree`: enumeración `FileStatus` de los cambios entre el índice y el árbol de trabajo
- `origPath`, `renameScore`: se establecen para las entradas renombradas; en los demás casos, `null`

Los archivos sin seguimiento tienen `FileStatus::Untracked` en ambas columnas. No se informan los archivos ignorados.

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

#### `statusPorcelainV2`

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

Salida del estado legible por máquinas en el formato porcelain v2 de git.

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

---

### Diferencias

#### `diff`

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

Compara el árbol de trabajo con el índice (cambios sin preparar). Devuelve un array de objetos `DiffResult`. `$pathspec` filtra por una ruta de archivo exacta. `$algorithm` es `'myers'` (predeterminado), `'patience'`, `'histogram'` o `'minimal'`; cualquier otro valor lanza `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 el índice con HEAD (cambios preparados). Devuelve `DiffResult[]`, con detección de cambios de nombre entre archivos eliminados y añadidos.

Actualmente se ignora el argumento `$pathspec`, por lo que se devuelven todos los archivos preparados. El método devuelve un array vacío antes del primer 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 dos árboles mediante sus `ObjectId`. Devuelve `DiffResult[]`. Pase los ID de árbol, no los ID de commit: un ID de commit no se interpreta como árbol y produce un resultado vacío.

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

---

### Registro

#### `log`

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

Recorre el historial de commits, del más reciente al más antiguo según la marca de tiempo del autor del commit. Devuelve `Commit[]`. Si `$from` es null, comienza desde HEAD. Devuelve un array vacío si HEAD aún no tiene un commit.

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

#### `logAll`

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

Recorre los commits accesibles desde cada referencia y desde HEAD, del más reciente al más antiguo. Devuelve `Commit[]`.

#### `logOneline`

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

Devuelve las líneas del registro en formato `git log --oneline` (`abc1234 Subject`), desde HEAD, desde todas las referencias (`$all`) o filtradas por ruta.

#### `logPath`

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

Filtra el registro por una ruta de archivo. Devuelve solo los commits cuya diferencia con el primer padre añade, modifica, elimina o renombra ese archivo exacto. Las rutas de directorio no coinciden.

Solo se examinan los `5 * $limit` commits más recientes, por lo que el resultado puede omitir commits anteriores que afecten al archivo ([#7](https://github.com/inline0/pitmaster/issues/7)).

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

---

### Fusión

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

Fusiona una rama (o cualquier revisión) en HEAD. Detecta automáticamente las fusiones de avance rápido. Para las demás fusiones, detecta conflictos a nivel de árbol y crea un commit de fusión si no hay conflictos. Si hay conflictos, la operación se detiene y escribe en el índice y el árbol de trabajo un estado de fusión similar a Git; resuelva las rutas, prepárelas y luego llame a `mergeContinue()` o `mergeAbort()`.

`$strategy` `'ours'` registra un commit de fusión que conserva el árbol de HEAD; cualquier otro valor utiliza la fusión recursiva predeterminada. `mergeOctopus()` fusiona dos o más ramas en un solo commit. Si alguna rama tiene conflictos, devuelve un resultado con conflictos sin escribir el estado de fusión ni crear un commit.

Al fusionar una rama que ya está incluida en HEAD se crea otro commit de fusión en lugar de indicar «Already up to date» ([#6](https://github.com/inline0/pitmaster/issues/6)). Los cambios sin confirmar en archivos con seguimiento se sobrescriben, como se describe en `checkout()`.

Devuelve un `MergeResult` con:
- `clean`: si la fusión se completó sin conflictos
- `commitId`: el ID del commit de fusión (si se completó sin conflictos)
- `conflictPaths`: la lista de rutas de archivos en conflicto (si hubo conflictos)
- `mergedContents`: la ruta en conflicto al contenido del archivo con marcadores de conflicto (si hubo conflictos)

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

Busca la base de fusión (el ancestro común más bajo) de dos commits. Devuelve null si no existe ningún ancestro común.

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

### Búsqueda binaria

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

Busca mediante búsqueda binaria el commit que introdujo un cambio. `bisectStart()` registra las revisiones correctas e incorrectas, selecciona el primer candidato y lo devuelve. `bisectGood()` y `bisectBad()` marcan el HEAD actual (o `$revision`), seleccionan el siguiente candidato y lo devuelven. Cuando queda un solo sospechoso, se devuelve ese commit y sigue siendo el candidato: es el primer commit incorrecto. `bisectReset()` finaliza la sesión y vuelve a la rama o al commit donde comenzó.

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

---

### Red

#### `fetch`

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

Obtiene datos de un remoto. Descarga objetos nuevos y actualiza las referencias de seguimiento remoto. Pasa `$depth` para ampliar o limitar un historial superficial.

`fetch()` y los métodos `push*()` lanzan `RuntimeException` en un controlador abierto con `['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
```

Envía la rama actual (o una rama especificada) a un remoto. Hay variantes con force-with-lease, atómica y espejo para actualizar referencias de forma más estricta o amplia.

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

---

### Árboles de trabajo

#### `addWorktree`

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

Añade un árbol de trabajo vinculado. Crea la rama si no existe, configura los metadatos del árbol de trabajo y materializa los archivos del árbol. Pasa `$name` cuando necesites un identificador de metadatos determinista distinto del nombre base del directorio de la copia de trabajo.

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

Elimina un árbol de trabajo vinculado. Usa `$force = true` para eliminarlo incluso si está bloqueado.

#### `worktrees`

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

Lista todos los árboles de trabajo (principal y vinculados). Devuelve `Worktree[]`.

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

---

### Configuración

#### `config`

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

Accede a la configuración de git del repositorio. Lee y escribe valores de configuración.

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

---

### Acceso interno

Estos métodos exponen componentes internos para casos de uso avanzados.

#### `objectDatabase`

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

Accede a la base de datos de objetos sin procesar (almacenes loose y pack).

#### `refDatabase`

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

Accede a la base de datos de referencias sin procesar (referencias loose y empaquetadas).

---

## Clases de nivel inferior

Estas clases no están envueltas por `Repository` y se crean a partir de sus componentes internos.

### `Pitmaster\Graph\Blame`

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

Devuelve una entrada por cada línea de `$path` en `$headId`: `['line' => int, 'hash' => string, 'author' => string, 'content' => string]`. `author` es la cadena de identidad 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
```

Busca en los blobs de un árbol. Devuelve `['path' => string, 'line' => int, 'content' => string]` por cada línea coincidente. Opciones: `regex` (trata el patrón como una expresión PCRE) y `ignore_case`. Una expresión regular no válida lanza `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
```

Lee y escribe notas de git almacenadas bajo una referencia de notas. `listAll()` devuelve una asignación de hashes de commits al contenido de las notas.

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