
# セル単位のマージ

列単位のマージは、両側が同じ列を変更したことを検出します。セル単位のマージでは、さらに踏み込んで列内の内容をマージします。TEXTフィールドの異なる行や、JSONオブジェクトの異なるキーを2人が編集した場合も、競合せずにマージできます。

```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` で[merge plan](https://merql.inline0.dev/ja/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

複数行のテキストを、gitがファイル内容に使うものと同じMyers差分アルゴリズムでマージします。各行を単位として扱うため、異なる行への変更は競合せずにマージできます。

```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()` に処理を委ねます。この処理ではbaseと各側の間でMyers差分を計算し、2つの編集スクリプトを組み合わせます。同じ行領域で編集が重なると、競合として報告します。

## 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` キーを異なる値に変更しました。これは競合です。マージャーはデフォルトでoursを採用し、競合を1件報告します。

### キー単位の規則

| Base | Ours | Theirs | 結果 |
|---|---|---|---|
| キーに変更なし | キーに変更なし | baseを採用 |
| キーに変更なし | キーを変更 | theirsを採用 |
| キーを変更 | キーに変更なし | oursを採用 |
| キーを変更（同じ値） | キーを変更（同じ値） | 採用（合意） |
| キーを変更（異なる値） | キーを変更（異なる値） | **競合**（デフォルト: ours） |
| キーあり | キーあり | キーを削除 | 削除を採用 |
| キーあり | キーを削除 | キーあり | 削除を採用 |
| キーなし | キーを追加 | キーなし | 追加を採用 |
| キーなし | キーなし | キーを追加 | 追加を採用 |

ネストされたオブジェクトは、再帰的に比較せず、不透明な JSON 文字列として比較されます。ネストされたオブジェクトが異なる場合、サブキーではなく、そのキーの値全体を比較します。

### 要件

3つすべての値が有効な 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());
```
