
# Сливане

Pitmaster слива клонове чрез трипосочно сливане, както `git merge`. Открива възможности за fast-forward, изчислява основи за сливане, извършва сливания на ниво дърво и съдържание и генерира стандартни маркери за конфликт при конфликти във файловете.

## Основно сливане

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

Класът `MergeResult` е обект със стойност само за четене:

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

Когато сливането спре заради конфликти, състоянието на конфликтите се записва в индекса и работното дърво. Разрешете конфликтите във файловете, добавете ги с `add()` и извикайте `$repo->mergeContinue()` или извикайте `$repo->mergeAbort()`, за да възстановите състоянието отпреди сливането.

Преди сливане запишете или скрийте локалните промени: сливането изгражда работното дърво от резултата и презаписва незаписаните промени в проследяваните файлове ([#2](https://github.com/inline0/pitmaster/issues/2)).

## Сливания с fast-forward

Когато текущият HEAD е предшественик на целевия клон, Pitmaster извършва fast-forward сливане: просто премества HEAD към целия commit, без да създава commit за сливане.

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

Обратният случай все още не се открива: сливането на клон, който вече е включен в HEAD, създава нов commit за сливане с непроменено дърво, вместо да изведе „Вече е актуално“ ([#6](https://github.com/inline0/pitmaster/issues/6)).

## Тристранно сливане

Когато клоновете са се разделили, Pitmaster извършва трипосочно сливане:

1. Намира основата за сливане (най-ниския общ предшественик)
2. Сравнява основата с нашия и с техния клон на ниво дърво
3. За всеки файл, променен и в двата клона, извършва сливане на ниво съдържание
4. Създава commit за сливане с два родителя

```
Before:              After merge:

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

## Основа за сливане

Основата за сливане е най-скорошният общ предшественик на два commit-а. Pitmaster я намира, като обхожда графа на commit-ите. Когато има няколко основи за сливане (история с кръстосани разклонения), `merge()` първо ги слива и използва резултата като основа.

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

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

За директен достъп до алгоритъма `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);
```

## Проверка за сливане на клонове

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

## Маркери за конфликти

Когато даден файл е променен и в двата клона и промените се припокриват, Pitmaster генерира стандартни маркери за конфликт. `merge()` обозначава страните като `HEAD` и с името на слетия клон:

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

Класът `ConflictMarker` обгръща две версии с маркери:

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

## Трипосочно сливане на ниво съдържание

Класът `ThreeWayMerge` слива съдържанието на blob-ове (не файлове):

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

Приема три низа (базовата версия, нашата версия и тяхната версия), както и незадължителни етикети и стил на конфликтите (по подразбиране `'merge'` или `'diff3'` за включване на базовия раздел).

## Стратегии за сливане

### Рекурсивно сливане

Стратегията по подразбиране (`$repo->merge($branch)`) обработва истории с кръстосани разклонения и няколко основи за сливане. CLI приема също `-s ort` и `-s recursive` за нея.

### Сливане по стратегията Ours

Записва commit за сливане, който запазва дървото на HEAD без промени. Полезно е за поддържане на историята на клона, без да се включват промени.

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

### Octopus сливане

Слива два или повече клона в един commit за сливане. Ако възникне конфликт в някой клон, връща неуспешен резултат, без да записва състояние на сливане или да създава commit.

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

Класовете `RecursiveMerge`, `OursMerge` и `OctopusMerge` в `Pitmaster\Merge` са помощни средства за сливане на ниво съдържание; `Repository` реализира сам горните стратегии.

## Rerere (повторно използване на записано разрешение)

Pitmaster включва самостоятелен клас `Rerere`, който съхранява разрешенията в структурата на директориите на git `rr-cache`:

```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()` не го извиква: разрешенията се записват и прилагат отново само когато кодът ви го направи. `isEnabled()` търси буквалния текст `rerere.enabled = true` в `.git/config` и не разпознава раздела `[rerere]`, който `git config rerere.enabled true` записва ([#20](https://github.com/inline0/pitmaster/issues/20)).

## Процес на сливане на ниво дърво

Пълният процес на сливане:

1. `MergeBase::findAll()` намира общите предшественици
2. Дърветата на основата, нашия клон и техния клон се сравняват път по път
3. За файловете, променени само в единия клон: прилага се тази промяна
4. За файловете, променени и в двата клона:
   - Ако са променени по един и същ начин: приема се който и да е вариант (те съвпадат)
   - Ако промените са различни: съдържанието се обработва чрез `ThreeWayMerge::merge()`
5. Ако всички сливания са чисти: изгражда се слятото дърво и се създава commit за сливане
6. Ако има конфликти: връща `MergeResult` с `clean = false` и пътищата до файловете с конфликти

## Употреба на CLI

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

Резултат при успешно изпълнение:
```
Merge successful.
```

Изход при конфликт:
```
CONFLICT in: src/File.php, src/Other.php
```
