셀 단위 병합
열 단위 병합은 양쪽에서 같은 열을 변경했는지 감지합니다. 셀 단위 병합은 더 세밀하게 해당 열 내부의 콘텐츠를 병합합니다. TEXT 필드의 서로 다른 줄을 편집하거나 JSON 객체의 서로 다른 키를 편집한 두 사람의 변경 사항은 충돌 없이 병합할 수 있습니다.
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).
파사드 또는 CLI로 저장한 스냅샷을 셀 단위 병합으로 병합하려면 직접 불러와 ThreeWayMerge을 호출하세요. 결과는 일반 MergeResult이므로 파사드에서 계속 적용할 수 있으며, MergePlanBuilder은 이를 병합 계획으로 변환합니다.
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() 팩토리는 적절한 기본값을 제공합니다.
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());
병합기 확인 우선순위:
- 정규화된 열 이름(
table.column). - 정규화되지 않은 열 이름(
column). - 열 유형 패턴 일치(대소문자를 구분하지 않는
str_contains). - 기본값:
OpaqueCellMerger(값이 다르면 항상 충돌로 처리).
TextCellMerger
Myers diff를 사용해 여러 줄의 텍스트를 병합합니다. git이 파일 콘텐츠에 사용하는 것과 같은 알고리즘입니다. 각 줄을 하나의 단위로 취급하며, 서로 다른 줄의 변경 사항은 충돌 없이 병합됩니다.
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 diff로 계산한 다음 두 편집 스크립트를 결합합니다. 같은 줄 영역에서 편집 내용이 겹치면 충돌을 보고합니다.
JsonCellMerger
JSON 객체를 최상위 키별로 비교해 병합합니다. 각 키는 표준 3방향 규칙에 따라 병합됩니다.
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
예: 충돌 없는 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}
서로 다른 키를 변경했습니다. 병합 결과는 다음과 같습니다.
{"name": "Bob", "role": "admin", "active": true}
예: JSON 충돌
// Base
{"color": "red"}
// Ours
{"color": "blue"}
// Theirs
{"color": "green"}
양쪽에서 color 키를 서로 다른 값으로 변경했습니다. 충돌입니다. 기본 병합기는 ours 값을 사용하고 충돌 하나를 보고합니다.
키 단위 규칙
| 기준 | ours | theirs | 결과 |
|---|---|---|---|
| 키 변경 없음 | 키 변경 없음 | 기준값 적용 | |
| 키 변경 없음 | 키 변경 | theirs 적용 | |
| 키 변경 | 키 변경 없음 | ours 적용 | |
| 키 변경(동일) | 키 변경(동일) | 적용(일치) | |
| 키 변경(다름) | 키 변경(다름) | 충돌(기본값: ours) | |
| 키 있음 | 키 있음 | 키 제거됨 | 제거 적용 |
| 키 있음 | 키 제거됨 | 키 있음 | 제거 적용 |
| 키 없음 | 키 추가됨 | 키 없음 | 추가 적용 |
| 키 없음 | 키 없음 | 키 추가됨 | 추가 적용 |
중첩 객체는 재귀적으로 비교하지 않고 불투명한 JSON 문자열로 비교합니다. 중첩 객체가 다르면 하위 키가 아니라 해당 키의 전체 값을 비교합니다.
요구 사항
세 값 모두 유효한 JSON 객체 또는 배열이어야 합니다. 값 중 하나라도 유효한 JSON이 아니거나 JSON 스칼라(문자열, 숫자, 불리언)이면 병합기는 충돌 처리로 대체합니다.
OpaqueCellMerger
셀 단위 전략을 설정하지 않았을 때 사용하는 기본 병합기입니다. 열 값을 불투명한 블롭으로 취급하며 양쪽 값이 다르면 항상 충돌로 처리합니다.
use Merql\CellMerge\OpaqueCellMerger;
$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs
CellMergeResult
모든 셀 병합기는 CellMergeResult을 반환합니다.
$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
팩토리 메서드:
CellMergeResult::resolved($value); // clean merge
CellMergeResult::conflict($oursValue, $n); // n conflicts, defaults to ours
사용자 지정 셀 병합기
자체 병합 전략을 추가하려면 CellMerger 인터페이스를 구현하세요.
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());