
# PHP API

## `Pitmaster\Pitmaster`

La classe `Pitmaster\Pitmaster` è il punto di accesso statico. Fornisce metodi factory per aprire, creare e clonare repository, oltre a funzioni di rilevamento.

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

### `open`

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

Apre un repository esistente in `$path`. Il percorso dovrebbe essere la radice dell'albero di lavoro (contenente `.git/`). Supporta anche i worktree collegati (in cui `.git` è un file che contiene `gitdir: <path>`) e i repository bare (in cui `$path` è la directory `.git` stessa).

`$options` accetta due valori booleani, entrambi impostati su `true` per impostazione predefinita:

- `hooks`: esegue gli script `.git/hooks`. Passate `false` per saltarli per questo handle.
- `processes`: consente qualsiasi operazione che avvii un processo o apra un trasporto di rete. Passate `false` per disabilitare gli hook, l'hook fsmonitor, clone, fetch, push e il trasporto di processo SSH. Le letture e scritture locali continuano a funzionare.

Genera `InvalidArgumentException` se il percorso non è un repository 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
```

Inizializza un nuovo repository in `$path`. Crea la struttura di directory `.git` con `objects/`, `refs/heads/`, `refs/tags/`, `hooks/`, `info/`, `HEAD` (che punta a `refs/heads/main`) e un `config` predefinito.

`$objectFormat` è `'sha1'` o `'sha256'`. Un repository SHA-256 riceve `repositoryformatversion = 1` e `extensions.objectformat = sha256` nella configurazione. Qualsiasi altro valore genera `InvalidArgumentException`. `$options` accetta le stesse chiavi di `open()`.

Genera `RuntimeException` se esiste già un repository nel percorso.

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

Clona un repository remoto. Inizializza un nuovo repository, individua i riferimenti remoti, recupera il file pack, configura i branch di tracciamento remoto e materializza l'albero di lavoro. Gli URL SSH (`ssh://...` o `user@host:path`) usano il trasporto SSH; gli altri URL usano HTTP intelligente e, se lo scambio intelligente non riesce, ripiegano su HTTP non intelligente. Passate `$depth` per una clonazione superficiale.

Genera `RuntimeException` quando `$options['processes']` è `false`. Se la clonazione non riesce e `$path` non esisteva prima, la directory creata parzialmente viene rimossa.

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

Restituisce `['processes' => false, 'hooks' => false]`, per gli ambienti di esecuzione che non devono avviare processi host. Passatelo a `open()` o `init()`.

### `isRepository`

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

Verifica se un percorso è un repository git. Restituisce `true` per i repository normali (directory `.git/`), i worktree collegati (file `.git`) e i repository bare (se è presente il file `HEAD`).

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

### `isWorktree`

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

Verifica se un percorso è un worktree collegato (non il repository principale). Restituisce `true` solo se `.git` è un file che contiene un riferimento `gitdir:`.

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

### `commonGitDir`

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

Risolve la directory git comune a partire da un qualsiasi percorso di checkout. Nei repository normali, è la directory `.git`. Nei worktree collegati, è la directory git condivisa che contiene gli oggetti, la configurazione e packed-refs. Restituisce `null` se il percorso non è un repository.

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

---

## `Pitmaster\Repository`

La classe `Pitmaster\Repository` è l'handle principale per tutte le operazioni git. Potete ottenerne uno tramite `Pitmaster::open()`, `Pitmaster::init()` o `Pitmaster::clone()`.

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

### Accessori dei percorsi

#### `gitDir`

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

Restituisce la directory git specifica del worktree. Nei repository normali è `.git/`. Nei worktree collegati è `.git/worktrees/<name>/`.

#### `commonGitDir`

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

Restituisce la directory git condivisa. Nei repository normali coincide con `gitDir()`. Nei worktree collegati è la directory `.git/` del repository principale, dove si trovano oggetti, configurazione e packed-refs.

#### `workDir`

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

Restituisce il percorso della radice dell'albero di lavoro.

#### `isLinkedWorktree`

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

Restituisce `true` se questo handle del repository è un worktree collegato.

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

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

Indica se il repository non ha un albero di lavoro e quale delle opzioni `hooks` / `processes` è attiva per questo handle. `hooksEnabled()` è `false` ogni volta che i processi sono disabilitati.

---

### Oggetti

#### `readObject`

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

Legge qualsiasi oggetto usando il suo hash esadecimale completo (40 caratteri, o 64 in un repository SHA-256). Gli hash abbreviati non sono accettati. Restituisce un'istanza `Blob`, `Tree`, `Commit` o `Tag`. Cerca prima tra gli oggetti sciolti, poi nei file pack.

Genera `ObjectNotFoundException` se l'hash non viene trovato.

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

#### `writeObject`

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

Scrive un oggetto nell'archivio degli oggetti sciolti. Restituisce `ObjectId` calcolato.

```php
use Pitmaster\Object\Blob;

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

#### `catFile`

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

Restituisce il contenuto grezzo di un oggetto, equivalente a `git cat-file -p`.

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

#### `objectExists`

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

Verifica se un oggetto esiste nel repository (sciolto o impacchettato).

#### `listObjects`

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

Elenca tutti gli hash degli oggetti nel repository. Restituisce un array di stringhe esadecimali.

---

### Riferimenti

#### `head`

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

Restituisce il commit HEAD corrente. Segue i riferimenti simbolici (HEAD -> refs/heads/main -> hash del commit).

Genera `RuntimeException` se HEAD non punta a un commit valido (e.g, repository vuoto).

#### `branch`

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

Senza argomenti, restituisce il nome del branch corrente (e.g, `'main'`) oppure `null` se HEAD è scollegato.

Con un nome di branch, lo risolve nel relativo hash del commit (stringa esadecimale) oppure restituisce `null` se il branch non esiste.

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

#### `branches`

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

Restituisce un array ordinato di tutti i nomi dei branch (senza il prefisso `refs/heads/`).

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

#### `tags`

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

Restituisce un array ordinato di tutti i nomi dei tag (senza il prefisso `refs/tags/`).

#### `resolve`

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

Risolve un'espressione di revisione in un `ObjectId`. Sono supportati:
- Hash esadecimali completi (40 caratteri, o 64 per SHA-256). Gli hash abbreviati come `abc1234` non vengono risolti.
- `HEAD`
- Nomi dei branch (`main`, `feature/login`)
- Nomi dei tag (`v1.0.0`)
- Percorsi completi dei riferimenti (`refs/heads/main`)
- Espressioni di revisione (`HEAD~3`, `main^2`)

Genera `RuntimeException` se la revisione non può essere risolta.

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

#### `allRefs`

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

Restituisce tutti i riferimenti in un array associativo che associa il nome del riferimento all'hash esadecimale.

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

#### `updateRef`

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

Aggiorna un riferimento affinché punti a una nuova destinazione.

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

#### `createBranch`

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

Crea un nuovo branch. Se `$from` è null, crea un branch a partire dall'HEAD corrente.

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

#### `deleteBranch`

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

Elimina un branch per nome. Genera `RuntimeException` se il branch è estratto o non esiste. A differenza di `git branch -d`, non verifica se il branch è stato unito.

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

#### `createLightweightTag`

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

Crea un tag leggero. Se `$target` è null, applica il tag direttamente all'HEAD corrente.

```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 un tag annotato. Se `$target` è null, applica il tag all'HEAD corrente. Restituisce `ObjectId` dell'oggetto tag.

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

#### `deleteTag`

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

Elimina un tag per nome.

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

#### `packRefs`

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

Riscrive il file `packed-refs` condiviso a partire dall’insieme corrente di riferimenti sciolti e rimuove i corrispondenti riferimenti sciolti condivisi. Per gli alberi di lavoro collegati, scrive nella directory comune git.

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

#### `defaultBranch`

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

Risolvi il branch predefinito del repository. Controlla prima il symref HEAD remoto, poi l'HEAD locale e infine ripiega su `main` o `master`.

#### `isBranchMerged`

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

Verifica se un branch è stato unito completamente a un altro branch (per impostazione predefinita, il branch predefinito).

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

#### `checkout`

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

Passa da un branch all'altro o scollega HEAD. Aggiorna HEAD, l'indice e la working tree. `$target` è il nome di un branch, un tag, un hash completo o un'espressione di revisione; se non è un branch, HEAD viene scollegato.

Genera `RuntimeException` quando una modifica locale (in stage o meno) o un file non tracciato si trova in un percorso diverso tra l'albero corrente e quello di destinazione.

Le modifiche non confermate negli altri file tracciati non vengono mantenute: l'indice e la working tree vengono ricreati dall'albero di destinazione, quindi le modifiche ai file identici da entrambe le parti vanno perse, mentre git le manterrebbe ([#2](https://github.com/inline0/pitmaster/issues/2)). Eseguite prima il commit o mettetele da parte con stash. `merge()`, `cherryPick()`, `revert()`, `rebase()` e i metodi bisect ricreano la working tree allo stesso modo.

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

---

### Indice

#### `index`

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

Legge e restituisce l'indice corrente (area di staging) da `.git/index`.

#### `add`

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

Mette i file in stage. Legge ogni file dalla working tree, crea un oggetto blob e aggiorna l'indice. Ogni argomento deve essere il percorso di un file (o link simbolico) relativo alla radice della working tree; le directory e i glob non vengono espansi e, se un file non esiste, viene generato `RuntimeException`.

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

#### `remove`

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

Rimuove i percorsi tracciati sia dall'indice sia dalla working tree. Supporta `--cached` e `-r` / `--recursive` nell'elenco degli argomenti per un comportamento simile 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
```

Sposta o rinomina un file sia nella working tree sia nell'indice.

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

---

### Commit

#### `commit`

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

Crea un commit a partire dall'indice corrente. Crea una gerarchia di alberi dalle voci dell'indice, crea l'oggetto commit con HEAD come genitore (se presente) e aggiorna HEAD. Restituisce `ObjectId` del nuovo commit.

`$message` può essere `null` solo quando un merge, cherry-pick, revert o rebase si è interrotto a causa di un conflitto; in tal caso viene usato il messaggio preparato. Negli altri casi, un messaggio `null` genera `RuntimeException('Commit message required')`. Il commit genera un errore anche se l'indice contiene percorsi non uniti, se è vuoto in un repository senza commit o se l'albero è invariato rispetto a HEAD.

Il parametro `$author` è una stringa completa dell’autore git (`Name <email> timestamp timezone`). Se è null, viene risolto dalle variabili d’ambiente, dalle costanti e da `.git/config`, nell’ordine descritto in [Guida introduttiva](https://pitmaster.inline0.dev/it/docs/getting-started).

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

#### `show`

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

Mostra un commit-ish e le relative differenze rispetto al primo genitore. Se la revisione si risolve in un tag annotato, il valore restituito include anche `['tag' => Tag]` e `commit` è il commit di destinazione a cui punta il 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
```

Reimposta HEAD su un commit. Modalità:
- `'soft'`: sposta solo HEAD
- `'mixed'`: sposta HEAD e reimposta l'indice (predefinito)
- `'hard'`: sposta HEAD, reimposta l'indice e reimposta la working tree

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

Ripristina un percorso dall'indice o da una revisione di origine specifica. Se nessun flag è impostato, Pitmaster ripristina la working tree dall'indice (o da `$source`, se specificato). `staged: true` ripristina l'indice da HEAD (o da `$source`) senza modificare la working tree; passate `staged: true, worktree: true` per ripristinare entrambi.

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

Applica un commit come nuovo commit sul branch corrente, mantenendo l'autore originale. Se l'operazione si interrompe a causa di conflitti, risolveteli, mettete i percorsi in stage e poi chiamate `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 che annulla un altro commit. Se il revert si interrompe a causa di conflitti, risolveteli, mettete i percorsi in stage e poi chiamate `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
```

Esegue il rebase del branch corrente su un'altra revisione. Pitmaster attualmente gestisce i rebase lineari con uno stato di interruzione per conflitti strutturato come Git, oltre alle opzioni per continuare, interrompere e saltare. HEAD deve puntare a un branch.

`rebase()`, `rebaseContinue()` e `rebaseSkip()` restituiscono `['success' => bool, 'commits' => int, 'conflicts' => list<string>]`: se il rebase è terminato, quanti commit sono stati applicati e i percorsi in conflitto in caso di interruzione.

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

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

---

### Stato

#### `status`

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

Calcola lo stato della working tree. Restituisce un array di oggetti `StatusEntry`, ciascuno contenente:
- `path`: il percorso del file
- `index`: enum `FileStatus` per le modifiche da HEAD all'indice
- `worktree`: enum `FileStatus` per le modifiche dall'indice alla working tree
- `origPath`, `renameScore`: impostati per le voci rinominate, altrimenti `null`

I file non tracciati hanno `FileStatus::Untracked` in entrambe le colonne. I file ignorati non vengono segnalati.

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

#### `statusPorcelainV2`

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

Output dello stato leggibile dalle macchine nel formato porcelain v2 di git.

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

---

### Differenze

#### `diff`

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

Confronta la working tree con l'indice (modifiche non in stage). Restituisce un array di oggetti `DiffResult`. `$pathspec` filtra in base a un singolo percorso file esatto. `$algorithm` è `'myers'` (predefinito), `'patience'`, `'histogram'` o `'minimal'`; qualsiasi altro valore genera `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
```

Confronta l'indice con HEAD (modifiche in stage). Restituisce `DiffResult[]`, rilevando i rinominamenti tra file eliminati e aggiunti.

L'argomento `$pathspec` viene attualmente ignorato, quindi vengono restituiti tutti i file in stage e il metodo restituisce un array vuoto prima del primo 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
```

Confronta due alberi in base al loro `ObjectId`. Restituisce `DiffResult[]`. Passate gli ID degli alberi, non gli ID dei commit: un ID commit non viene letto come albero e produce un risultato vuoto.

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

Esplora la cronologia dei commit, dal più recente in base al timestamp del committer. Restituisce `Commit[]`. Se `$from` è null, parte da HEAD. Restituisce un array vuoto se HEAD non ha ancora commit.

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

#### `logAll`

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

Esplora i commit raggiungibili da ogni riferimento e da HEAD, dal più recente. Restituisce `Commit[]`.

#### `logOneline`

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

Restituisce le righe del log nel formato `git log --oneline` (`abc1234 Subject`), a partire da HEAD, da tutti i riferimenti (`$all`) o filtrate per percorso.

#### `logPath`

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

Filtra il log in base al percorso di un file. Restituisce solo i commit la cui differenza rispetto al primo genitore aggiunge, modifica, elimina o rinomina quel file specifico. I percorsi delle directory non corrispondono.

Vengono esaminati solo i commit `5 * $limit` più recenti; di conseguenza, i commit più vecchi che modificano il file potrebbero non comparire nel risultato ([#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
```

Unisce un branch (o qualsiasi revisione) a HEAD. Rileva automaticamente i merge fast-forward. Per i merge non fast-forward, rileva i conflitti a livello di albero e, se non ve ne sono, crea un commit di merge. Se si verifica un conflitto, scrive nell'indice e nella working tree uno stato di merge strutturato come Git; risolvete i percorsi, metteteli in stage e poi chiamate `mergeContinue()` o `mergeAbort()`.

`$strategy` `'ours'` registra un commit di merge che mantiene l'albero di HEAD; qualsiasi altro valore usa il merge ricorsivo predefinito. `mergeOctopus()` unisce due o più branch in un unico commit. Se un branch presenta conflitti, restituisce un risultato non pulito senza scrivere lo stato di merge né creare un commit.

Unire un branch già contenuto in HEAD crea un altro commit di merge invece di segnalare "Già aggiornato" ([#6](https://github.com/inline0/pitmaster/issues/6)). Le modifiche non confermate nei file tracciati vengono sovrascritte come descritto nella sezione `checkout()`.

Restituisce un `MergeResult` con:
- `clean`: se il merge è riuscito senza conflitti
- `commitId`: ID del commit di merge (se pulito)
- `conflictPaths`: array dei percorsi dei file in conflitto (se non pulito)
- `mergedContents`: percorso in conflitto e contenuto del file con i marcatori di conflitto (se non pulito)

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

Trova la base del merge (antenato comune più basso) di due commit. Restituisce null se non esiste un antenato comune.

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

Ricerca binaria del commit che ha introdotto una modifica. `bisectStart()` registra le revisioni errata e corretta, esegue il checkout del primo candidato e lo restituisce. `bisectGood()` e `bisectBad()` contrassegnano l'HEAD corrente (o `$revision`), quindi eseguono il checkout del candidato successivo e lo restituiscono. Quando resta un solo sospettato, quel commit viene restituito e rimane il candidato: è il primo commit errato. `bisectReset()` termina la sessione e ripristina il branch o commit da cui era iniziata.

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

---

### Rete

#### `fetch`

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

Recupera i dati da un remoto. Scarica nuovi oggetti e aggiorna i riferimenti di tracking remoti. Passate `$depth` per approfondire o limitare una cronologia shallow.

`fetch()` e i metodi `push*()` generano `RuntimeException` su un handle aperto 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
```

Invia il branch corrente (o un branch specificato) a un remoto. Sono disponibili le varianti force-with-lease, atomic e mirror per aggiornare i riferimenti con vincoli più rigorosi o in modo più ampio.

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

---

### Worktree

#### `addWorktree`

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

Aggiunge una worktree collegata. Crea il branch se non esiste, imposta i metadati della worktree e crea i file nella working tree. Passate `$name` quando serve uno slug deterministico per i metadati, diverso dal nome di base della directory di 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
```

Rimuove una worktree collegata. Usate `$force = true` per rimuoverla anche se è bloccata.

#### `worktrees`

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

Elenca tutte le worktree (principale e collegate). Restituisce `Worktree[]`.

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

---

### Configurazione

#### `config`

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

Accede alla configurazione git del repository. Legge e scrive i valori di configurazione.

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

---

### Accesso interno

Questi metodi espongono componenti interni per casi d'uso avanzati.

#### `objectDatabase`

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

Accede al database degli oggetti grezzi (archivi loose e pack).

#### `refDatabase`

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

Accede al database dei riferimenti grezzi (riferimenti loose e packed).

---

## Classi di livello inferiore

Queste classi non sono avvolte da `Repository` e vengono create a partire dai suoi componenti interni.

### `Pitmaster\Graph\Blame`

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

Restituisce una voce per ogni riga di `$path` in `$headId`: `['line' => int, 'hash' => string, 'author' => string, 'content' => string]`. `author` è la stringa completa dell'identità (`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
```

Cerca nei blob di un albero. Restituisce `['path' => string, 'line' => int, 'content' => string]` per ogni riga corrispondente. Opzioni: `regex` (interpreta il pattern come pattern PCRE) e `ignore_case`. Una regex non valida genera `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
```

Legge e scrive le note git archiviate sotto un riferimento notes. `listAll()` restituisce la corrispondenza tra hash del commit e contenuto della 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'));
```
