Choose the single live successor of `current` for a linear content walk. In the additive graph model a superseded span keeps its original alive `BLOCK` edge *alongside* a new `DELETED` edge. A walk that merely skipped `DELETED` edges would therefore still follow the alive edge into the superseded span and resurrect stale content. This helper instead treats any destination that has a visible `DELE
(
txn: &T,
inode: Inode,
adj: &mut atomic_core::pristine::InodeAdjState,
options: &atomic_core::output::alive::RetrieveOptions,
)
| 922 | /// resolves to just the new span. Genuine conflict forks and delete-throughs |
| 923 | /// return [`LinearStep::Bail`] so the caller defers to the full graph walk. |
| 924 | fn select_linear_successor<T>( |
| 925 | txn: &T, |
| 926 | inode: Inode, |
| 927 | adj: &mut atomic_core::pristine::InodeAdjState, |
| 928 | options: &atomic_core::output::alive::RetrieveOptions, |
| 929 | ) -> atomic_core::record::RecordResult<LinearStep> |
| 930 | where |
| 931 | T: atomic_core::pristine::GraphTxnT + atomic_core::pristine::InodeGraphOps, |
| 932 | { |
| 933 | use atomic_core::types::EdgeFlags; |
| 934 | |
| 935 | let mut alive: Vec<atomic_core::types::GraphNode<NodeId>> = Vec::new(); |
| 936 | let mut deleted: std::collections::HashSet<atomic_core::types::GraphNode<NodeId>> = |
| 937 | std::collections::HashSet::new(); |
| 938 | |
| 939 | while let Some(edge_result) = txn.next_inode_adj(adj) { |
| 940 | let edge = match edge_result { |
| 941 | Ok(edge) => edge, |
| 942 | Err(_) => return Ok(LinearStep::Bail), |
| 943 | }; |
| 944 | let flags = edge.flag(); |
| 945 | if flags.contains(EdgeFlags::PARENT) |
| 946 | || flags.contains(EdgeFlags::PSEUDO) |
| 947 | || flags.contains(EdgeFlags::FOLDER) |
| 948 | { |
| 949 | continue; |
| 950 | } |
| 951 | if !options.passes_filter(edge.introduced_by()) { |
| 952 | continue; |
| 953 | } |
| 954 | let dest = match txn.find_block_in_inode(inode, edge.dest()) { |
| 955 | Ok(Some(d)) => d, |
| 956 | Ok(None) | Err(_) => return Ok(LinearStep::Bail), |
| 957 | }; |
| 958 | if !options.passes_filter(dest.change) { |
| 959 | continue; |
| 960 | } |
| 961 | if flags.contains(EdgeFlags::DELETED) { |
| 962 | deleted.insert(dest); |
| 963 | } else if !alive.contains(&dest) { |
| 964 | alive.push(dest); |
| 965 | } |
| 966 | } |
| 967 | |
| 968 | // A destination that is both alive (original edge) and deleted (superseding |
| 969 | // edge) is dead from this view's perspective. |
| 970 | alive.retain(|d| !deleted.contains(d)); |
| 971 | |
| 972 | match alive.len() { |
| 973 | 0 => { |
| 974 | if deleted.is_empty() { |
| 975 | Ok(LinearStep::End) |
| 976 | } else { |
| 977 | // The only successors here were deleted; the live continuation |
| 978 | // lies past a dead vertex — defer to the full graph walk. |
| 979 | Ok(LinearStep::Bail) |
| 980 | } |
| 981 | } |
no test coverage detected