
# 合并

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 已包含的分支时，会创建一个树未变化的新合并提交，而不是报告“已经是最新状态”（[问题 #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');
```

### Octopus 合并

将两个或更多分支合并到一个合并提交中。如果任何分支发生冲突，则返回未清理的结果，不会写入合并状态或创建提交。

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

`Pitmaster\Merge` 中的 `RecursiveMerge`、`OursMerge` 和 `OctopusMerge` 类是内容级辅助工具；`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()` 会在 `.git/config` 中查找字面文本 `rerere.enabled = true`，并且无法识别 `git config rerere.enabled true` 写入的 `[rerere]` 部分（[问题 #20](https://github.com/inline0/pitmaster/issues/20)）。

## 树级合并流程

完整合并流程：

1. `MergeBase::findAll()` 定位共同祖先
2. 按路径逐一比较基准树、本方树和对方树
3. 对只在一个分支中更改的文件：采用该更改
4. 对在两个分支中都更改的文件：
   - 如果两边的更改相同：采用任一方的更改（两者一致）
   - 如果更改不同：对内容运行 `ThreeWayMerge::merge()`
5. 如果所有合并均无冲突：构建合并后的树并创建合并提交
6. 如果出现任何冲突：返回带有 `clean = false` 和冲突路径的 `MergeResult`

## CLI 用法

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

成功时的输出：
```
Merge successful.
```

冲突时的输出：
```
CONFLICT in: src/File.php, src/Other.php
```
