
# Mergen

Pitmaster voert branch-merges uit met een three-way merge, net als `git merge`. Het detecteert mogelijkheden voor fast-forward, berekent mergebasissen, voert merges op tree- en inhoudsniveau uit en genereert standaardconflictmarkeringen wanneer bestanden conflicteren.

## Eenvoudige merge

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

if ($result->clean) {
    echo "Merged successfully: {$result->commitId->hex}\n";
} else {
    echo "Conflicts:\n";
    foreach ($result->conflictPaths as $path) {
        echo "  {$path}\n";
    }
}
```

## MergeResult

De klasse `MergeResult` is een alleen-lezen value object:

```php
use Pitmaster\Merge\MergeResult;

$result->clean;           // bool: true if merge completed without conflicts
$result->commitId;        // ?ObjectId: the merge commit (null if conflicts)
$result->conflictPaths;   // array: list of file paths with conflicts
$result->mergedContents;  // array: path => content with conflict markers (if conflicts)
```

Wanneer de merge stopt vanwege conflicten, wordt de conflictstatus naar de index en de werkboom geschreven. Los de bestanden op, `add()` ze en roep `$repo->mergeContinue()` aan, of roep `$repo->mergeAbort()` aan om de status van vóór de merge te herstellen.

Commit of stash lokale wijzigingen voordat u gaat mergen: de merge bouwt de werkboom opnieuw op aan de hand van het resultaat en overschrijft niet-vastgelegde wijzigingen in bijgehouden bestanden ([#2](https://github.com/inline0/pitmaster/issues/2)).

## Fast-forward-merges

Wanneer de huidige HEAD een voorouder is van de doelbranch, voert Pitmaster een fast-forward-merge uit: het verplaatst HEAD simpelweg naar de doelcommit zonder een mergecommit aan te maken.

```
Before:         After fast-forward:

A---B (main)    A---B---C---D (main)
     \
      C---D (feature)
```

```php
$result = $repo->merge('feature');
// $result->clean === true
// $result->commitId points to D
// No merge commit created
```

Het omgekeerde geval wordt nog niet gedetecteerd: een branch mergen die HEAD al bevat, maakt een nieuwe mergecommit met een ongewijzigde tree in plaats van "Already up to date" te melden ([#6](https://github.com/inline0/pitmaster/issues/6)).

## Drieweg-samenvoeging

Wanneer branches uiteen zijn gelopen, voert Pitmaster een three-way merge uit:

1. Zoek de mergebasis (laagste gemeenschappelijke voorouder)
2. Vergelijk de tree van de basis met die van ons en die van hen
3. Voer voor elk bestand dat in beide branches is gewijzigd een merge op inhoudsniveau uit
4. Maak een mergecommit met twee ouders

```
Before:              After merge:

      C---D (feature)         C---D
     /                       /     \
A---B---E---F (main)   A---B---E---F---G (main)
```

## Mergebasis

De mergebasis is de meest recente gemeenschappelijke voorouder van twee commits. Pitmaster vindt deze door de commitgraaf te doorlopen. Als er meerdere mergebasissen zijn (criss-cross-geschiedenis), voegt `merge()` die eerst samen en gebruikt het resultaat als basis.

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

if ($base !== null) {
    echo "Common ancestor: {$base->hex}\n";
}
```

Voor directe toegang tot het algoritme van `MergeBase`:

```php
use Pitmaster\Merge\MergeBase;

$finder = new MergeBase($repo->objectDatabase());
$base = $finder->find($commitA, $commitB);

// All merge bases (criss-cross histories can have more than one)
$bases = $finder->findAll($commitA, $commitB);

// Check ancestry
$isAncestor = $finder->isAncestor($oldCommit, $newCommit);
```

## Branch-mergecontrole

```php
if ($repo->isBranchMerged('feature/done')) {
    // All commits in feature/done are reachable from default branch
    $repo->deleteBranch('feature/done');
}

// Check against a specific target
if ($repo->isBranchMerged('feature/done', 'develop')) {
    // Merged into develop
}
```

## Conflictmarkeringen

Wanneer een bestand in beide branches is gewijzigd en de wijzigingen elkaar overlappen, genereert Pitmaster standaardconflictmarkeringen. `merge()` labelt de kanten met `HEAD` en de naam van de samengevoegde branch:

```
<<<<<<< HEAD
    return $this->value + 1;
=======
    return $this->value * 2;
>>>>>>> feature
```

De klasse `ConflictMarker` plaatst twee versies tussen markeringen:

```php
use Pitmaster\Merge\ConflictMarker;

$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature');

// diff3 style, with the base section
$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature', $baseContent, 'base');
```

## Three-way merge op inhoudsniveau

De klasse `ThreeWayMerge` voegt blob-inhoud samen (geen bestanden):

```php
use Pitmaster\Merge\ThreeWayMerge;

$result = ThreeWayMerge::merge($baseContent, $oursContent, $theirsContent, 'HEAD', 'feature');

$result['content'];   // merged text, with conflict markers if needed
$result['clean'];     // true when nothing conflicted
$result['conflicts']; // number of conflict regions
```

Deze neemt drie strings (de basisversie, onze versie en hun versie), plus optionele labels en een conflictstijl (standaard `'merge'`, of `'diff3'` om het basisgedeelte op te nemen).

## Mergestrategieën

### Recursieve merge

De standaardstrategie (`$repo->merge($branch)`) verwerkt criss-cross-mergegeschiedenissen met meerdere mergebasissen. De CLI accepteert hiervoor ook `-s ort` en `-s recursive`.

### Ours-merge

Legt een mergecommit vast die de tree van HEAD ongewijzigd laat. Handig om de branchgeschiedenis bij te houden zonder wijzigingen op te nemen.

```php
$result = $repo->merge('feature/old', 'ours');
```

### Octopus-merge

Voegt twee of meer branches samen in één mergecommit. Als een branch conflicten oplevert, retourneert deze een niet-schone uitkomst zonder de mergestatus op te slaan of een commit aan te maken.

```php
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
```

De klassen `RecursiveMerge`, `OursMerge` en `OctopusMerge` in `Pitmaster\Merge` zijn helpers voor merges op inhoudsniveau; `Repository` implementeert de bovenstaande strategieën zelf.

## Rerere (opgeslagen oplossing opnieuw gebruiken)

Pitmaster bevat een zelfstandige klasse `Rerere` die oplossingen opslaat in de `rr-cache`-indeling van git:

```php
use Pitmaster\Merge\Rerere;

$rerere = new Rerere($repo->gitDir());

if ($rerere->isEnabled()) {                     // rerere.enabled = true in .git/config
    $rerere->record($conflicted, $resolved);    // remember a resolution
    $resolved = $rerere->resolve($conflicted);  // replay it, or null
}
```

`merge()` roept deze niet aan: oplossingen worden alleen vastgelegd en opnieuw afgespeeld wanneer uw code dat doet. `isEnabled()` zoekt naar de letterlijke tekst `rerere.enabled = true` in `.git/config` en herkent het gedeelte `[rerere]` niet dat `git config rerere.enabled true` schrijft ([#20](https://github.com/inline0/pitmaster/issues/20)).

## Mergeverloop op tree-niveau

Het volledige mergeproces:

1. `MergeBase::findAll()` zoekt de gemeenschappelijke voorouders
2. De trees van de basis, ons en hen worden pad voor pad vergeleken
3. Voor bestanden die slechts in één branch zijn gewijzigd: neem die wijziging over
4. Voor bestanden die in beide branches zijn gewijzigd:
   - Als beide op dezelfde manier zijn gewijzigd: neem een van beide (ze komen overeen)
   - Als de wijzigingen verschillen: voer `ThreeWayMerge::merge()` uit op de inhoud
5. Als alle merges zonder conflicten verlopen: bouw de samengevoegde tree op en maak een mergecommit
6. Bij conflicten: retourneer `MergeResult` met `clean = false` en de conflictpaden

## Gebruik van CLI

```bash
./vendor/bin/pitmaster merge feature/login
```

Uitvoer bij succes:
```
Merge successful.
```

Uitvoer bij conflict:
```
CONFLICT in: src/File.php, src/Other.php
```
