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,
)
| 896 | /// resolves to just the new span. Genuine conflict forks and delete-throughs |
| 897 | /// return [`LinearStep::Bail`] so the caller defers to the full graph walk. |
| 898 | fn select_linear_successor<T>( |
| 899 | txn: &T, |
| 900 | inode: Inode, |
| 901 | adj: &mut atomic_core::pristine::InodeAdjState, |
| 902 | options: &atomic_core::output::alive::RetrieveOptions, |
| 903 | ) -> atomic_core::record::RecordResult<LinearStep> |
| 904 | where |
| 905 | T: atomic_core::pristine::GraphTxnT + atomic_core::pristine::InodeGraphOps, |
| 906 | { |
| 907 | use atomic_core::types::EdgeFlags; |
| 908 | |
| 909 | let mut alive: Vec<atomic_core::types::GraphNode<NodeId>> = Vec::new(); |
| 910 | let mut deleted: std::collections::HashSet<atomic_core::types::GraphNode<NodeId>> = |
| 911 | std::collections::HashSet::new(); |
| 912 | |
| 913 | while let Some(edge_result) = txn.next_inode_adj(adj) { |
| 914 | let edge = match edge_result { |
| 915 | Ok(edge) => edge, |
| 916 | Err(_) => return Ok(LinearStep::Bail), |
| 917 | }; |
| 918 | let flags = edge.flag(); |
| 919 | if flags.contains(EdgeFlags::PARENT) |
| 920 | || flags.contains(EdgeFlags::PSEUDO) |
| 921 | || flags.contains(EdgeFlags::FOLDER) |
| 922 | { |
| 923 | continue; |
| 924 | } |
| 925 | if !options.passes_filter(edge.introduced_by()) { |
| 926 | continue; |
| 927 | } |
| 928 | let dest = match txn.find_block_in_inode(inode, edge.dest()) { |
| 929 | Ok(Some(d)) => d, |
| 930 | Ok(None) | Err(_) => return Ok(LinearStep::Bail), |
| 931 | }; |
| 932 | if !options.passes_filter(dest.change) { |
| 933 | continue; |
| 934 | } |
| 935 | if flags.contains(EdgeFlags::DELETED) { |
| 936 | deleted.insert(dest); |
| 937 | } else if !alive.contains(&dest) { |
| 938 | alive.push(dest); |
| 939 | } |
| 940 | } |
| 941 | |
| 942 | // A destination that is both alive (original edge) and deleted (superseding |
| 943 | // edge) is dead from this view's perspective. |
| 944 | alive.retain(|d| !deleted.contains(d)); |
| 945 | |
| 946 | match alive.len() { |
| 947 | 0 => { |
| 948 | if deleted.is_empty() { |
| 949 | Ok(LinearStep::End) |
| 950 | } else { |
| 951 | // The only successors here were deleted; the live continuation |
| 952 | // lies past a dead vertex — defer to the full graph walk. |
| 953 | Ok(LinearStep::Bail) |
| 954 | } |
| 955 | } |
no test coverage detected