You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -12,21 +12,21 @@ The merge driver implements a three-way merge algorithm specifically designed fo
12
12
13
13
The merge logic uses the **Strategy Pattern** to handle different merge scenarios. Each scenario (based on which versions have content) has its own strategy implementation.
@@ -146,32 +144,34 @@ The `ConflictMarkerBuilder` constructs these markers, and `ConflictMarkerFormatt
146
144
147
145
## Data Flow
148
146
149
-
```
150
-
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
151
-
│ XML Input │────▶│ JSON Parse │────▶│ Merge │
152
-
│ (3 files) │ │ (fast-xml) │ │ Orchestrator│
153
-
└─────────────┘ └─────────────┘ └──────┬──────┘
154
-
│
155
-
┌──────────────────────────┘
156
-
▼
157
-
┌─────────────────────┐
158
-
│ Scenario Strategy │
159
-
│ (based on content) │
160
-
└──────────┬──────────┘
161
-
│
162
-
┌──────────┴──────────┐
163
-
▼ ▼
164
-
┌─────────────────┐ ┌─────────────────┐
165
-
│ MergeNode │ │ ConflictMarker │
166
-
│ (recursive) │ │ Builder │
167
-
└────────┬────────┘ └────────┬────────┘
168
-
│ │
169
-
└──────────┬──────────┘
170
-
▼
171
-
┌─────────────────────┐
172
-
│ XML Output │
173
-
│ (merged file) │
174
-
└─────────────────────┘
147
+
```mermaid
148
+
flowchart TD
149
+
subgraph Input
150
+
XML["XML Input (3 files)"]
151
+
end
152
+
153
+
subgraph Parse
154
+
JSON["JSON Parse (fast-xml-parser)"]
155
+
end
156
+
157
+
subgraph Merge
158
+
Orchestrator["MergeOrchestrator"]
159
+
Strategy["ScenarioStrategy"]
160
+
Nodes["MergeNode (recursive)"]
161
+
Conflict["ConflictMarkerBuilder"]
162
+
end
163
+
164
+
subgraph Output
165
+
Result["XML Output (merged file)"]
166
+
end
167
+
168
+
XML --> JSON
169
+
JSON --> Orchestrator
170
+
Orchestrator --> Strategy
171
+
Strategy --> Nodes
172
+
Strategy --> Conflict
173
+
Nodes --> Result
174
+
Conflict --> Result
175
175
```
176
176
177
177
## Key Design Decisions
@@ -198,3 +198,201 @@ The `MergeContext` is immutable, ensuring strategies cannot accidentally modify
198
198
### 5. Configurable Conflict Markers
199
199
200
200
Conflict marker size and labels are configurable via Git's standard parameters (`-L`, `-S`, `-X`, `-Y` flags), allowing integration with existing Git workflows.
201
+
202
+
## Deterministic Ordering Algorithm
203
+
204
+
For ordered metadata types (e.g., `GlobalValueSet`, `StandardValueSet`), the driver implements a deterministic three-way merge algorithm that preserves element ordering while detecting and merging compatible changes.
205
+
206
+
### Core Principles
207
+
208
+
1.**User decides order** — never auto-resolve ambiguous ordering conflicts
209
+
2.**Value-based comparison** — elements are compared by their key field, not position
210
+
3.**Disjoint change detection** — non-overlapping reorderings can be merged automatically
211
+
4.**Conflict on overlap** — when both sides move the same elements differently, conflict
212
+
5.**Positional conflict detection** — concurrent additions at different positions trigger conflict
213
+
214
+
### Algorithm Overview
215
+
216
+
The `OrderedKeyedArrayMergeStrategy` handles ordered arrays through these steps:
Position -->|No| Disjoint["Compute merged key order"]
242
+
```
243
+
244
+
### Moved Element Detection
245
+
246
+
An element is considered "moved" if its relative order with any other element changed between ancestor and modified version. Uses upper-triangle optimization to avoid redundant pair comparisons:
247
+
248
+
```typescript
249
+
// For each pair (a, b) where a comes before b in ancestor:
250
+
// If a comes after b in modified → both a and b are "moved"
251
+
for (i=0; i<ancestorKeys.length; i++) {
252
+
for (j=i+1; j<ancestorKeys.length; j++) {
253
+
if (modifiedPos[a] >modifiedPos[b]) {
254
+
moved.add(a); moved.add(b)
255
+
}
256
+
}
257
+
}
258
+
```
259
+
260
+
### Positional Conflict Detection (C6)
261
+
262
+
When both sides add the same element but at different relative positions, a conflict is triggered. This is detected by comparing the relative order of added elements against all common elements:
263
+
264
+
```typescript
265
+
// For element added by both sides:
266
+
// Check if its position relative to any common element differs
267
+
if (addedLocalPos<keyLocalPos!==addedOtherPos<keyOtherPos) {
268
+
returntrue// Positional conflict
269
+
}
270
+
```
271
+
272
+
### Merge Scenarios
273
+
274
+
| ID | Scenario | Behavior |
275
+
|----|----------|----------|
276
+
| M1-M9 | Standard merges | Additions, deletions, modifications handled by spine algorithm |
277
+
| M10 | Disjoint swaps | Local swaps {A,B}, other swaps {C,D} → merge both |
278
+
| C4 | Divergent moves | Both sides move same element differently → conflict |
279
+
| C6 | Positional conflict | Both sides add same element at different positions → conflict |
280
+
| C7 | Concurrent addition with diverged orderings | Both sides add different elements while orderings diverge → conflict |
281
+
282
+
### Example: M10 Disjoint Swaps
283
+
284
+
```
285
+
Ancestor: [A, B, C, D]
286
+
Local: [B, A, C, D] ← swapped A↔B
287
+
Other: [A, B, D, C] ← swapped C↔D
288
+
289
+
Analysis:
290
+
- localMoved = {A, B} (A and B changed relative order)
291
+
- otherMoved = {C, D} (C and D changed relative order)
292
+
- Intersection = ∅ (disjoint changes)
293
+
294
+
Merge:
295
+
- Apply local's order for {A,B}: [B, A]
296
+
- Apply other's order for {C,D}: [D, C]
297
+
- Result: [B, A, D, C]
298
+
```
299
+
300
+
### Example: C6 Positional Conflict
301
+
302
+
```
303
+
Ancestor: [A, B]
304
+
Local: [A, X, B] ← added X between A and B
305
+
Other: [X, A, B] ← added X before A
306
+
307
+
Analysis:
308
+
- X added by both, but at different positions
309
+
- In local: X is after A
310
+
- In other: X is before A
311
+
- Relative order conflict → full array conflict
312
+
```
313
+
314
+
### Example: C7 Concurrent Addition with Diverged Orderings
315
+
316
+
```
317
+
Ancestor: [A, B]
318
+
Local: [B, A, X] ← swapped A↔B, added X
319
+
Other: [A, B, Y] ← added Y
320
+
321
+
Analysis:
322
+
- localMoved = {A, B} (swapped)
323
+
- Both sides added different elements (X vs Y)
324
+
- Ambiguous: should result be [B, A, X, Y] or [B, A, Y, X]?
325
+
- Concurrent additions with diverged orderings → full array conflict
326
+
```
327
+
328
+
### Implementation
329
+
330
+
Key methods in `OrderedKeyedArrayMergeStrategy` ([KeyedArrayMergeNode.ts](src/merger/nodes/KeyedArrayMergeNode.ts)):
331
+
332
+
| Method | Purpose |
333
+
|--------|---------|
334
+
|`buildMergeContext()`| Extracts keys and builds position/object maps for O(1) lookups |
|`processWithSpine(config, ctx)`| Uses LCS for spine-based merge |
341
+
|`processSpine(config, spine, ctx)`| Iterates spine anchors, processes gaps between them |
342
+
343
+
### Spine-Based Merge Algorithm
344
+
345
+
The spine-based merge uses the [Longest Common Subsequence (LCS)](https://en.wikipedia.org/wiki/Longest_common_subsequence) algorithm to identify stable anchor points between versions.
346
+
347
+
#### Spine Computation
348
+
349
+
The **spine** is the stable backbone of elements present in all versions with preserved relative order:
0 commit comments