
# PHP API

## `Pitmaster\Pitmaster`

La classe `Pitmaster\Pitmaster` est le point d'entrée statique. Elle fournit des méthodes de fabrique pour ouvrir, créer et cloner des dépôts, ainsi que des fonctions de détection.

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

### `open`

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

Ouvre un dépôt existant à l'emplacement `$path`. Le chemin doit être la racine de l'arborescence de travail (contenant `.git/`). Prend également en charge les worktrees liés (où `.git` est un fichier contenant `gitdir: <path>`) et les dépôts nus (où `$path` est le répertoire `.git` lui-même).

`$options` accepte deux booléens, tous deux `true` par défaut :

- `hooks` : exécuter les scripts `.git/hooks`. Transmettez `false` pour les ignorer pour ce handle.
- `processes` : autoriser toute opération qui lance un processus ou ouvre un transport réseau. Transmettez `false` pour désactiver les hooks, le hook fsmonitor, clone, fetch, push et le transport de processus SSH. Les lectures et écritures locales restent possibles.

Lève `InvalidArgumentException` si le chemin n’est pas un dépôt 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
```

Initialise un nouveau dépôt à l'emplacement `$path`. Crée la structure de répertoires `.git` avec `objects/`, `refs/heads/`, `refs/tags/`, `hooks/`, `info/`, `HEAD` (pointant vers `refs/heads/main`) et un `config` par défaut.

`$objectFormat` est `'sha1'` ou `'sha256'`. Un dépôt SHA-256 reçoit `repositoryformatversion = 1` et `extensions.objectformat = sha256` dans sa configuration. Toute autre valeur lève `InvalidArgumentException`. `$options` accepte les mêmes clés que `open()`.

Lève `RuntimeException` si un dépôt existe déjà à cet emplacement.

```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 dépôt distant. Initialise un nouveau dépôt, découvre les références distantes, récupère le fichier pack, configure les branches de suivi distantes et matérialise l'arborescence de travail. Les URL SSH (`ssh://...` ou `user@host:path`) utilisent le transport SSH ; les autres URL utilisent le protocole intelligent HTTP et basculent vers le protocole simple HTTP en cas d'échec de l'échange intelligent. Transmettez `$depth` pour un clone superficiel.

Lève `RuntimeException` lorsque `$options['processes']` vaut `false`. Si le clone échoue et que `$path` n'existait pas auparavant, le répertoire partiellement créé est supprimé.

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

Renvoie `['processes' => false, 'hooks' => false]`, pour les environnements d'exécution qui ne doivent pas lancer de processus hôtes. Transmettez-le à `open()` ou `init()`.

### `isRepository`

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

Vérifie si un chemin correspond à un dépôt git. Renvoie `true` pour les dépôts ordinaires (dossier `.git/`), les espaces de travail liés (fichier `.git`) et les dépôts nus (fichier `HEAD` présent).

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

### `isWorktree`

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

Vérifie si un chemin correspond à un worktree lié (et non au dépôt principal). Renvoie `true` uniquement si `.git` est un fichier contenant une référence `gitdir:`.

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

### `commonGitDir`

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

Résout le répertoire Git commun à partir de n'importe quel chemin de checkout. Pour les dépôts ordinaires, il s'agit du répertoire `.git`. Pour les worktrees liés, il s'agit du répertoire Git partagé qui contient les objets, la configuration et packed-refs. Renvoie `null` si le chemin ne correspond pas à un dépôt.

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

---

## `Pitmaster\Repository`

La classe `Pitmaster\Repository` est l’objet principal pour toutes les opérations git. Obtenez-en une via `Pitmaster::open()`, `Pitmaster::init()` ou `Pitmaster::clone()`.

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

### Accesseurs de chemin

#### `gitDir`

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

Renvoie le répertoire Git propre au worktree. Pour les dépôts ordinaires, il s'agit de `.git/`. Pour les worktrees liés, il s'agit de `.git/worktrees/<name>/`.

#### `commonGitDir`

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

Renvoie le répertoire Git partagé. Identique à `gitDir()` pour les dépôts ordinaires. Pour les worktrees liés, il s'agit du répertoire `.git/` du dépôt principal (où se trouvent les objets, la configuration et packed-refs).

#### `workDir`

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

Renvoie le chemin racine de l'arborescence de travail.

#### `isLinkedWorktree`

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

Renvoie `true` si ce handle de dépôt correspond à un worktree lié.

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

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

Indique si le dépôt n'a pas d'arborescence de travail et précise quelles options `hooks` / `processes` s'appliquent à ce handle. `hooksEnabled()` vaut `false` lorsque les processus sont désactivés.

---

### Objets

#### `readObject`

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

Lit un objet à partir de son hash hexadécimal complet (40 caractères, ou 64 dans un dépôt SHA-256). Les hash abrégés ne sont pas acceptés. Renvoie une instance `Blob`, `Tree`, `Commit` ou `Tag`. Recherche d'abord les objets libres, puis les fichiers pack.

Lève `ObjectNotFoundException` si le hash est introuvable.

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

#### `writeObject`

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

Écrit un objet dans le magasin d'objets libres. Renvoie le `ObjectId` calculé.

```php
use Pitmaster\Object\Blob;

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

#### `catFile`

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

Renvoie le contenu brut d'un objet, ce qui équivaut à `git cat-file -p`.

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

#### `objectExists`

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

Vérifie si un objet existe dans le dépôt (libre ou empaqueté).

#### `listObjects`

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

Liste tous les hash d'objets du dépôt. Renvoie un tableau de chaînes hexadécimales.

---

### Références

#### `head`

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

Renvoie le commit HEAD actuel. Suit les références symboliques (HEAD -> refs/heads/main -> hash du commit).

Lève `RuntimeException` si HEAD ne pointe pas vers un commit valide (e.g., dépôt vide).

#### `branch`

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

Sans argument, renvoie le nom de la branche actuelle (e.g., `'main'`), ou `null` si HEAD est détaché.

Avec un nom de branche, le résout en son hash de commit (chaîne hexadécimale) ou renvoie `null` si la branche n'existe pas.

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

#### `branches`

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

Renvoie un tableau trié de tous les noms de branche (sans le préfixe `refs/heads/`).

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

#### `tags`

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

Renvoie un tableau trié de tous les noms de tag (sans le préfixe `refs/tags/`).

#### `resolve`

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

Résout une expression de révision en un `ObjectId`. Prend en charge :
- Hash hexadécimaux complets (40 caractères, ou 64 pour SHA-256). Les hash abrégés, comme `abc1234`, ne sont pas résolus.
- `HEAD`
- Noms de branche (`main`, `feature/login`)
- Noms de tag (`v1.0.0`)
- Chemins de référence complets (`refs/heads/main`)
- Expressions de révision (`HEAD~3`, `main^2`)

Lève `RuntimeException` si la révision ne peut pas être résolue.

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

#### `allRefs`

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

Renvoie toutes les références sous forme de tableau associatif associant chaque nom de référence à un hash hexadécimal.

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

#### `updateRef`

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

Met à jour une référence pour qu'elle pointe vers une nouvelle cible.

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

#### `createBranch`

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

Crée une branche. Si `$from` est nul, elle est créée à partir du HEAD actuel.

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

#### `deleteBranch`

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

Supprime une branche par son nom. Lève `RuntimeException` si la branche est extraite ou n'existe pas. Contrairement à `git branch -d`, cette méthode ne vérifie pas si la branche a été fusionnée.

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

#### `createLightweightTag`

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

Crée un tag léger. Si `$target` est nul, le tag pointe directement vers le HEAD actuel.

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

Crée un tag annoté. Si `$target` est nul, le tag pointe vers le HEAD actuel. Renvoie le `ObjectId` de l'objet tag.

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

#### `deleteTag`

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

Supprime un tag par son nom.

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

#### `packRefs`

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

Réécrit le fichier partagé `packed-refs` à partir de l’ensemble actuel de références libres et supprime les références libres partagées correspondantes. Pour les espaces de travail liés, écrit dans le répertoire git commun.

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

#### `defaultBranch`

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

Résout la branche par défaut du dépôt. Vérifie d'abord la référence symbolique HEAD distante, puis HEAD local, et enfin utilise `main` ou `master` comme valeur de repli.

#### `isBranchMerged`

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

Vérifie si une branche a été entièrement fusionnée dans une autre (la branche par défaut est utilisée par défaut).

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

#### `checkout`

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

Change de branche ou détache HEAD. Met à jour HEAD, l'index et l'arborescence de travail. `$target` peut être un nom de branche, un tag, un hash complet ou une expression de révision ; si ce n'est pas une branche, HEAD est détaché.

Lève `RuntimeException` lorsqu'une modification locale (préparée ou non) ou un fichier non suivi se trouve dans un chemin différent entre l'arborescence actuelle et celle de la cible.

Les modifications non validées dans les autres fichiers suivis ne sont pas conservées : l'index et l'arborescence de travail sont reconstruits à partir de celle de la cible. Les modifications apportées à des fichiers identiques des deux côtés sont donc perdues, alors que git les conserverait ([#2](https://github.com/inline0/pitmaster/issues/2)). Validez-les ou mettez-les de côté d'abord. `merge()`, `cherryPick()`, `revert()`, `rebase()` et les méthodes de bissection reconstruisent l'arborescence de travail de la même façon.

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

---

### Index

#### `index`

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

Lit et renvoie l'index actuel (zone de préparation) depuis `.git/index`.

#### `add`

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

Prépare les fichiers. Lit chaque fichier dans l'arborescence de travail, crée un objet blob et met à jour l'index. Chaque argument doit être un chemin de fichier (ou de lien symbolique) relatif à la racine de l'arborescence de travail ; les répertoires et les motifs glob ne sont pas développés, et tout fichier manquant provoque une erreur `RuntimeException`.

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

#### `remove`

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

Supprime les chemins suivis de l'index et de l'arborescence de travail. Accepte `--cached` et `-r` / `--recursive` dans la liste d'arguments pour un comportement de type 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
```

Déplace/renomme un fichier à la fois dans l'arborescence de travail et dans l'index.

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

---

### Commits

#### `commit`

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

Crée un commit à partir de l'index actuel. Construit une hiérarchie d'arborescence à partir des entrées de l'index, crée l'objet commit avec HEAD comme parent (le cas échéant) et met à jour HEAD. Renvoie le `ObjectId` du nouveau commit.

`$message` ne peut être `null` que lorsqu'une fusion, un cherry-pick, une annulation ou un rebase est arrêté à cause d'un conflit ; le message préparé est alors utilisé. Sinon, un message `null` provoque l'erreur `RuntimeException('Commit message required')`. Un commit échoue également si l'index contient des chemins non fusionnés, s'il est vide dans un dépôt sans commit, ou si l'arborescence n'a pas changé depuis HEAD.

Le paramètre `$author` est une chaîne d’auteur git complète (`Name <email> timestamp timezone`). Si sa valeur est nulle, elle est déterminée à partir des variables d’environnement, des constantes et de `.git/config`, dans l’ordre indiqué dans [Premiers pas](https://pitmaster.inline0.dev/fr/docs/getting-started).

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

#### `show`

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

Affiche un commit ou équivalent et ses différences avec son premier parent. Lorsque la révision correspond à un tag annoté, la valeur de retour inclut également `['tag' => Tag]` et `commit` désigne le commit cible déréférencé.

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

Réinitialise HEAD sur un commit. Modes :
- `'soft'` : déplacer HEAD uniquement
- `'mixed'` : déplacer HEAD et réinitialiser l'index (par défaut)
- `'hard'` : déplacer HEAD, réinitialiser l'index et l'arborescence de travail

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

Restaure un chemin depuis l'index ou une révision source précise. Si aucun des deux indicateurs n'est défini, Pitmaster restaure l'arborescence de travail à partir de l'index (ou de `$source`, le cas échéant). `staged: true` restaure l'index à partir de HEAD (ou de `$source`) sans modifier l'arborescence de travail ; transmettez `staged: true, worktree: true` pour restaurer les deux.

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

Applique un commit en créant un nouveau commit sur la branche actuelle, tout en conservant l'auteur d'origine. Si des conflits interrompent l'opération, résolvez-les, préparez les chemins, puis appelez `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
```

Crée un commit qui annule un autre commit. Si des conflits interrompent l'annulation, résolvez-les, préparez les chemins, puis appelez `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
```

Rebase la branche actuelle sur une autre révision. Pitmaster prend actuellement en charge les rebase linéaires, avec un état d'arrêt en cas de conflit de type Git, ainsi que les opérations de poursuite, d'abandon et de saut. HEAD doit pointer vers une branche.

`rebase()`, `rebaseContinue()` et `rebaseSkip()` renvoient `['success' => bool, 'commits' => int, 'conflicts' => list<string>]` : indique si le rebase est terminé, combien de commits ont été appliqués et quels chemins sont en conflit en cas d'arrêt.

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

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

---

### État

#### `status`

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

Calcule l'état de l'arborescence de travail. Renvoie un tableau d'objets `StatusEntry`, contenant chacun :
- `path` : le chemin du fichier
- `index` : une énumération `FileStatus` des modifications entre HEAD et l'index
- `worktree` : une énumération `FileStatus` des modifications entre l'index et l'arborescence de travail
- `origPath`, `renameScore` : définis pour les entrées renommées, sinon `null`

Les fichiers non suivis ont `FileStatus::Untracked` dans les deux colonnes. Les fichiers ignorés ne sont pas signalés.

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

#### `statusPorcelainV2`

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

Format de sortie d'état lisible par machine, au format porcelain 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
```

Compare l'arborescence de travail à l'index (modifications non préparées). Renvoie un tableau d'objets `DiffResult`. `$pathspec` filtre sur un chemin de fichier exact. `$algorithm` vaut `'myers'` (par défaut), `'patience'`, `'histogram'` ou `'minimal'` ; toute autre valeur provoque l'erreur `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
```

Compare l'index à HEAD (modifications préparées). Renvoie `DiffResult[]`, avec détection des renommages parmi les fichiers supprimés et ajoutés.

L'argument `$pathspec` est actuellement ignoré : tous les fichiers préparés sont donc renvoyés, et la méthode renvoie un tableau vide avant le premier 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
```

Compare deux arborescences à partir de leur `ObjectId`. Renvoie `DiffResult[]`. Transmettez des ID d'arborescence, pas des ID de commit : un ID de commit n'est pas interprété comme une arborescence et produit un résultat vide.

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

---

### Journal

#### `log`

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

Parcourt l'historique des commits, du plus récent au plus ancien selon l'horodatage du validateur. Renvoie `Commit[]`. Si `$from` est nul, démarre à partir de HEAD. Renvoie un tableau vide si HEAD ne contient encore aucun commit.

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

#### `logAll`

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

Parcourt les commits accessibles depuis chaque référence et HEAD, du plus récent au plus ancien. Renvoie `Commit[]`.

#### `logOneline`

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

Renvoie les lignes du journal sous forme `git log --oneline` (`abc1234 Subject`), à partir de HEAD, de toutes les références (`$all`) ou en filtrant par chemin.

#### `logPath`

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

Journal filtré par chemin de fichier. Renvoie uniquement les commits dont les différences avec le premier parent ajoutent, modifient, suppriment ou renomment ce fichier précis. Les chemins de répertoire ne correspondent pas.

Seuls les `5 * $limit` commits les plus récents sont examinés. Les commits plus anciens qui modifient le fichier peuvent donc manquer dans le résultat ([#7](https://github.com/inline0/pitmaster/issues/7)).

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

---

### Fusion

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

Fusionne une branche (ou n'importe quelle révision) dans HEAD. Détecte automatiquement les fusions en avance rapide. Pour les autres fusions, détecte les conflits au niveau de l'arborescence et crée un commit de fusion si tout est propre. En cas de conflit, l'opération écrit dans l'index et l'arborescence de travail un état de fusion de type Git ; résolvez les conflits, préparez les chemins, puis appelez `mergeContinue()` ou `mergeAbort()`.

`$strategy` `'ours'` enregistre un commit de fusion qui conserve l'arborescence de HEAD ; toute autre valeur utilise la fusion récursive par défaut. `mergeOctopus()` fusionne deux branches ou plus en un seul commit. Si une branche présente un conflit, un résultat non propre est renvoyé sans écrire d'état de fusion ni créer de commit.

La fusion d’une branche déjà incluse dans HEAD crée un autre commit de fusion au lieu d’indiquer « Already up to date » ([#6](https://github.com/inline0/pitmaster/issues/6)). Les modifications non validées des fichiers suivis sont écrasées, comme décrit sous `checkout()`.

Renvoie un `MergeResult` avec :
- `clean` : indique si la fusion a réussi sans conflit
- `commitId` : l’identifiant du commit de fusion (en cas de fusion sans conflit)
- `conflictPaths` : tableau des chemins des fichiers en conflit (en cas de conflit)
- `mergedContents` : chemin en conflit vers le contenu du fichier avec marqueurs de conflit (en cas de conflit)

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

Trouve la base de fusion (l’ancêtre commun le plus proche) de deux commits. Renvoie null s’il n’existe aucun ancêtre commun.

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

### Bissection

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

Recherche par dichotomie le commit qui a introduit une modification. `bisectStart()` enregistre les révisions mauvaise et bonne, extrait le premier candidat et le renvoie. `bisectGood()` et `bisectBad()` indiquent l’état actuel de HEAD (ou `$revision`), puis extraient et renvoient le candidat suivant. Lorsqu’il ne reste qu’un seul suspect, ce commit est renvoyé et reste le candidat : c’est le premier commit défectueux. `bisectReset()` termine la session et revient à la branche ou au commit de départ.

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

---

### Réseau

#### `fetch`

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

Récupère des données depuis un dépôt distant. Télécharge de nouveaux objets et met à jour les références de suivi distant. Transmettez `$depth` pour approfondir ou limiter un historique superficiel.

`fetch()` et les méthodes `push*()` lèvent `RuntimeException` sur un handle ouvert avec `['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
```

Envoie la branche actuelle (ou une branche spécifiée) vers un dépôt distant. Des variantes avec vérification du bail, atomique et miroir sont disponibles pour des mises à jour de références plus strictes ou plus larges.

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

---

### Arbres de travail

#### `addWorktree`

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

Ajoute un arbre de travail lié. Crée la branche si elle n’existe pas, configure les métadonnées de l’arbre de travail et matérialise les fichiers de l’arbre de travail. Transmettez `$name` si vous avez besoin d’un identifiant de métadonnées déterministe différent du nom de base du répertoire extrait.

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

Supprime un arbre de travail lié. Utilisez `$force = true` pour le supprimer même s’il est verrouillé.

#### `worktrees`

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

Répertorie tous les arbres de travail (principal et liés). Renvoie `Worktree[]`.

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

---

### Configuration

#### `config`

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

Accède à la configuration git du dépôt. Lit et écrit les valeurs de configuration.

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

---

### Accès interne

Ces méthodes exposent des composants internes pour les cas d’utilisation avancés.

#### `objectDatabase`

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

Accède à la base de données brute des objets (stockages libres et packés).

#### `refDatabase`

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

Accède à la base de données brute des références (références libres et packées).

---

## Classes de niveau inférieur

Ces classes ne sont pas encapsulées par `Repository` et sont construites à partir de ses composants internes.

### `Pitmaster\Graph\Blame`

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

Renvoie une entrée par ligne de `$path` à `$headId` : `['line' => int, 'hash' => string, 'author' => string, 'content' => string]`. `author` est la chaîne d’identité complète (`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
```

Recherche dans les blobs d’un arbre. Renvoie `['path' => string, 'line' => int, 'content' => string]` pour chaque ligne correspondante. Options : `regex` (traite le motif comme une expression PCRE) et `ignore_case`. Une expression régulière invalide lève `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
```

Lit et écrit les notes git stockées sous une référence de notes. `listAll()` associe le hachage du commit au contenu de la note.

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