Reconstruct a file's bytes by walking the CRDT layer. This is the alternative to `output::repo::output_file_with_filter`: it derives line order from `iter_trunk_branches_in_file_order` (the CRDT after-chain), filters by `branch.state` for liveness, and pulls each line's bytes via the branch's recorded graph vertex. The byte-graph is consulted *only* to fetch content blob ranges — the linear-edge
(
txn: &T,
changes: &C,
path: &str,
)
| 525 | /// [`CrdtOutputError::OrphanBranch`]. Callers can catch and fall |
| 526 | /// back to the byte-graph walker for that file. |
| 527 | pub fn output_file_via_crdt<T, C>( |
| 528 | txn: &T, |
| 529 | changes: &C, |
| 530 | path: &str, |
| 531 | ) -> Result<Vec<u8>, CrdtOutputError<C::Error>> |
| 532 | where |
| 533 | T: crate::pristine::CrdtTxnT + crate::pristine::GraphTxnT, |
| 534 | C: crate::change::ChangeStore, |
| 535 | { |
| 536 | use crate::types::Hash; |
| 537 | |
| 538 | let trunk_id = match txn.get_trunk_by_path(path)? { |
| 539 | Some(t) => t, |
| 540 | None => return Ok(Vec::new()), |
| 541 | }; |
| 542 | |
| 543 | let mut out: Vec<u8> = Vec::new(); |
| 544 | |
| 545 | for branch_id in iter_trunk_branches_in_file_order(txn, trunk_id)? { |
| 546 | let branch_key = encode_branch_id(&branch_id); |
| 547 | |
| 548 | let branch_data = match txn.get_crdt_branch(&branch_key)? { |
| 549 | Some(b) => b, |
| 550 | None => continue, // No row — branch listed in TRUNK_BRANCHES but |
| 551 | // missing from BRANCHES. Treat as deleted. |
| 552 | }; |
| 553 | if !branch_data.state.is_alive() { |
| 554 | continue; |
| 555 | } |
| 556 | |
| 557 | let graph_node = match txn.get_crdt_branch_vertex(&branch_key)? { |
| 558 | Some(n) => n, |
| 559 | None => return Err(CrdtOutputError::OrphanBranch(branch_id)), |
| 560 | }; |
| 561 | |
| 562 | let len = graph_node.end.get().saturating_sub(graph_node.start.get()) as usize; |
| 563 | if len == 0 { |
| 564 | continue; |
| 565 | } |
| 566 | |
| 567 | let start = out.len(); |
| 568 | out.resize(start + len, 0); |
| 569 | |
| 570 | // hash_fn re-created per call to keep the &txn borrow re-entrant. |
| 571 | let hash_fn = |id: crate::types::NodeId| -> Option<Hash> { |
| 572 | if id.is_root() { |
| 573 | None |
| 574 | } else { |
| 575 | txn.get_external(id).ok().flatten() |
| 576 | } |
| 577 | }; |
| 578 | |
| 579 | changes |
| 580 | .get_contents(hash_fn, graph_node, &mut out[start..]) |
| 581 | .map_err(CrdtOutputError::Store)?; |
| 582 | } |
| 583 | |
| 584 | Ok(out) |