Output graph content with semantic-merge resolution. This is the merge-aware counterpart of [`output_graph_content`]. For every SCC whose lead vertex appears in `resolved`, the merged bytes are written directly and the remaining vertices are silently skipped. All other SCCs are handled identically to `output_graph_content`. If `resolved` is empty this function behaves exactly like [`output_gra
(
changes: &C,
hash_fn: F,
graph: &AliveGraph,
order: &OrderResult,
buffer: &mut V,
resolved: &ResolvedConflicts,
)
| 513 | /// If `resolved` is empty this function behaves exactly like |
| 514 | /// [`output_graph_content`]. |
| 515 | pub fn output_graph_content_resolved<C, F, V>( |
| 516 | changes: &C, |
| 517 | hash_fn: F, |
| 518 | graph: &AliveGraph, |
| 519 | order: &OrderResult, |
| 520 | buffer: &mut V, |
| 521 | resolved: &ResolvedConflicts, |
| 522 | ) -> OutputResult<()> |
| 523 | where |
| 524 | C: ChangeStore, |
| 525 | F: Fn(NodeId) -> Option<Hash>, |
| 526 | V: VertexBuffer, |
| 527 | { |
| 528 | // Fast path: nothing was resolved and no unresolved forks. |
| 529 | if resolved.is_empty() && resolved.unresolved_forks().is_empty() { |
| 530 | return output_graph_content(changes, hash_fn, graph, order, buffer); |
| 531 | } |
| 532 | |
| 533 | // Track conflict IDs |
| 534 | let mut conflict_id: usize = 0; |
| 535 | |
| 536 | // Track zombie state |
| 537 | let mut in_zombie: Option<usize> = None; |
| 538 | |
| 539 | // Track which fork-conflict vertices have already been emitted. |
| 540 | let mut fork_emitted: std::collections::HashSet<VertexId> = std::collections::HashSet::new(); |
| 541 | |
| 542 | // Debug-only guard: each vertex's content must be emitted at most once |
| 543 | // across the resolved/fork/normal paths. Emitting a vertex twice is the |
| 544 | // silent-duplication signature. Zero cost in release builds. |
| 545 | #[cfg(debug_assertions)] |
| 546 | let mut emitted_content: std::collections::HashSet<VertexId> = std::collections::HashSet::new(); |
| 547 | |
| 548 | for scc in order.sccs.iter().rev() { |
| 549 | if scc.is_empty() { |
| 550 | continue; |
| 551 | } |
| 552 | |
| 553 | let is_cyclic = scc.len() > 1; |
| 554 | |
| 555 | // ── Resolved SCC: emit merged bytes, no conflict markers ────── |
| 556 | if is_cyclic { |
| 557 | if let Some(merged) = resolved.get_merged(scc[0]) { |
| 558 | if !merged.is_empty() { |
| 559 | let synthetic = GraphNode::new( |
| 560 | NodeId::ROOT, |
| 561 | ChangePosition::new(0), |
| 562 | ChangePosition::new(merged.len() as u64), |
| 563 | ); |
| 564 | // Clone into a local so the closure can own it |
| 565 | // (`output_line` takes `FnOnce`). |
| 566 | let bytes = merged.to_vec(); |
| 567 | buffer |
| 568 | .output_line(synthetic, |buf: &mut [u8]| -> Result<(), std::io::Error> { |
| 569 | buf.copy_from_slice(&bytes); |
| 570 | Ok(()) |
| 571 | }) |
| 572 | .map_err(OutputError::io)?; |