
# 单元格级合并

列级合并可检测双方是否都修改了同一列。单元格级合并会进一步深入，合并该列内部的内容。两个人编辑 TEXT 字段中的不同文本行，或编辑 JSON 对象中的不同键时，都可以顺利合并。

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;

$config = CellMergeConfig::auto();
$merge = new ThreeWayMerge($config);
$result = $merge->merge($base, $ours, $theirs);
```

## 启用单元格级合并

单元格级合并需显式启用。它只会在使用 `CellMergeConfig` 构造的 `ThreeWayMerge` 中运行。若未配置，双方都改成不同值的每一列都会产生 `update_update` 冲突。

`Merql` 门面不接受配置：`Merql::merge()`、`Merql::patch()` 和 `Merql::plan()` 无需配置即可构造 `new ThreeWayMerge()`，而 CLI 的 `merge` 命令会通过 `Merql::merge()` 执行。它们始终在列级别合并（[#7](https://github.com/inline0/merql/issues/7)）。

若要使用单元格级合并来合并由门面或 CLI 保存的快照，请自行加载快照并调用 `ThreeWayMerge`。结果是普通的 `MergeResult`，因此门面仍可应用它；`MergePlanBuilder` 会将其转换为[合并计划](https://merql.inline0.dev/zh/docs/merge/plans)：

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Merql;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base   = SnapshotStore::load('base');
$ours   = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);

$plan = (new MergePlanBuilder())->build('plan-1', $base, $ours, $theirs, $result);
```

只有双方都修改了某列时，才会运行单元格合并器。如果只有一方修改了某列，则直接采用该方的值。

## CellMergeConfig

`CellMergeConfig` 将列或列类型映射到单元格合并器实现。`auto()` 工厂提供了合理的默认值：

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
use Merql\CellMerge\TextCellMerger;

// Auto: column types containing "text" (TEXT, MEDIUMTEXT, LONGTEXT, ...) use
// TextCellMerger, column types containing "json" use JsonCellMerger.
$config = CellMergeConfig::auto();

// Custom: assign mergers to specific columns.
$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('settings', new JsonCellMerger())
    ->forType('json', new JsonCellMerger());
```

合并器的匹配优先级：

1. 限定列名（`table.column`）。
2. 非限定列名（`column`）。
3. 匹配列类型模式（不区分大小写的 `str_contains`）。
4. 默认值：`OpaqueCellMerger`（只要值不同就始终报告冲突）。

## TextCellMerger

使用 Myers 差异算法合并多行文本，与 git 用于文件内容的算法相同。每一行都视作一个单元，修改不同文本行时可以顺利合并。

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### 示例：无冲突的文本合并

```
Base:    "line one\nline two\nline three"
Ours:    "LINE ONE\nline two\nline three"    // changed line 1
Theirs:  "line one\nline two\nLINE THREE"    // changed line 3
```

修改的是不同文本行。合并结果为 `"LINE ONE\nline two\nLINE THREE"`，没有冲突。

### 示例：文本冲突

```
Base:    "line one\nline two"
Ours:    "changed by us\nline two"     // changed line 1
Theirs:  "changed by them\nline two"   // also changed line 1
```

双方将同一行改成了不同的值。合并器会返回冲突结果。合并后的内容包含类似 git 的冲突标记。

### 工作原理

`TextCellMerger` 会调用 pitmaster 的 `ThreeWayMerge::merge()`，后者会计算基线与双方状态之间的 Myers 差异，然后合并两组编辑脚本。如果编辑在同一行区域重叠，就会报告冲突。

## JsonCellMerger

通过分别比较顶层键，合并 JSON 对象。每个键都按照标准三方规则进行合并。

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### 示例：无冲突的 JSON 合并

```json
// Base
{"name": "Alice", "role": "editor", "active": true}

// Ours: changed "role"
{"name": "Alice", "role": "admin", "active": true}

// Theirs: changed "name"
{"name": "Bob", "role": "editor", "active": true}
```

修改了不同的键。合并结果为：

```json
{"name": "Bob", "role": "admin", "active": true}
```

### 示例：JSON 冲突

```json
// Base
{"color": "red"}

// Ours
{"color": "blue"}

// Theirs
{"color": "green"}
```

双方将 `color` 键改成了不同的值。这会产生冲突。合并器默认采用我方的值，并报告一个冲突。

### 键级规则

| 基线 | 我方 | 对方 | 结果 |
|---|---|---|---|
| 键未变 | 键未变 | 接受基线 |
| 键未变 | 键已变 | 接受对方 |
| 键已变 | 键未变 | 接受我方 |
| 键已变（相同） | 键已变（相同） | 接受（双方一致） |
| 键已变（不同） | 键已变（不同） | **冲突**（默认：我方） |
| 键存在 | 键存在 | 键已删除 | 接受删除 |
| 键存在 | 键已删除 | 键存在 | 接受删除 |
| 键缺失 | 键已添加 | 键缺失 | 接受添加 |
| 键缺失 | 键缺失 | 键已添加 | 接受添加 |

嵌套对象会被视为不透明的 JSON 字符串，不会递归比较。如果嵌套对象不同，就比较该键的整个值，而非其子键。

### 要求

三个值都必须是有效的 JSON 对象或数组。如果任一值不是有效的 JSON，或者是 JSON 标量（字符串、数字、布尔值），合并器就会退回到冲突处理。

## OpaqueCellMerger

未配置单元格级策略时使用的默认合并器。它会将列值视为不透明数据块，只要双方的值不同就始终报告冲突。

```php
use Merql\CellMerge\OpaqueCellMerger;

$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs
```

## CellMergeResult

所有单元格合并器都会返回 `CellMergeResult`：

```php
$result = $merger->merge($base, $ours, $theirs);

$result->clean;     // bool: true if merge resolved cleanly
$result->value;     // mixed: the merged value (or ours on conflict)
$result->conflicts; // int: number of conflicts within the cell
```

工厂方法：

```php
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // n conflicts, defaults to ours
```

## 自定义单元格合并器

实现 `CellMerger` 接口即可添加自定义合并策略：

```php
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

class YamlCellMerger implements CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult
    {
        // Parse YAML, merge keys, return result.
        $merged = $this->mergeYaml($base, $ours, $theirs);

        if ($merged['clean']) {
            return CellMergeResult::resolved($merged['value']);
        }

        return CellMergeResult::conflict($ours);
    }
}

$config = (new CellMergeConfig())
    ->forColumn('config_data', new YamlCellMerger());
```
