
# Злиття

Pitmaster виконує злиття гілок за допомогою тристороннього злиття, як `git 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

Клас `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)).

## Злиття з перемотуванням уперед

Якщо поточний HEAD є предком цільової гілки, Pitmaster виконує злиття з перемотуванням уперед: просто переміщує HEAD до цільового коміту, не створюючи коміту злиття.

```
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, створює новий коміт злиття з незміненим деревом замість повідомлення «Already up to date» ([#6](https://github.com/inline0/pitmaster/issues/6)).

## Тристороннє злиття

Якщо гілки розійшлися, Pitmaster виконує тристороннє злиття:

1. Знаходить базу злиття (найближчого спільного предка)
2. Порівнює дерево від бази до нашої гілки та від бази до їхньої гілки
3. Для кожного файлу, зміненого в обох гілках, виконує злиття на рівні вмісту
4. Створює коміт злиття з двома батьківськими комітами

```
Before:              After merge:

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

## База злиття

База злиття — це найновіший спільний предок двох комітів. Pitmaster знаходить її, проходячи граф комітів. Якщо баз злиття кілька (історія перехресних злиттів), `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`.

### Злиття «наша версія»

Записує коміт злиття, залишаючи дерево HEAD незмінним. Корисно для збереження історії гілки без включення змін.

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

### Злиття «восьминіг»

Зливає дві або більше гілок в один коміт злиття. Якщо в будь-якій гілці є конфлікти, повертає неузгоджений результат, не записуючи стан злиття й не створюючи коміт.

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

Класи `RecursiveMerge`, `OursMerge` і `OctopusMerge` у `Pitmaster\Merge` — це допоміжні засоби для злиття на рівні вмісту; `Repository` самостійно реалізує наведені вище стратегії.

## Rerere (повторне використання записаного вирішення)

Pitmaster містить окремий клас `Rerere`, який зберігає вирішення у форматі `rr-cache` від 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()` не викликає його: вирішення записуються й відтворюються лише тоді, коли це робить ваш код. `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. Якщо всі злиття завершилися без конфліктів: створюється об’єднане дерево й коміт злиття
6. Якщо є конфлікти: повертається `MergeResult` з `clean = false` і шляхами файлів із конфліктами

## Використання CLI

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

Вивід у разі успіху:
```
Merge successful.
```

Вивід за наявності конфлікту:
```
CONFLICT in: src/File.php, src/Other.php
```
