Compute the reverse-dependency closure of `requested` within `source`. Bounds are `T: ViewTxnT` (which requires `GraphTxnT`), so this works on both read and write transactions and can be reused for dry-run previews.
(
txn: &T,
source: &ViewState,
requested: &[Hash],
)
| 156 | /// Bounds are `T: ViewTxnT` (which requires `GraphTxnT`), so this works on both |
| 157 | /// read and write transactions and can be reused for dry-run previews. |
| 158 | fn analyze_split<T: ViewTxnT>( |
| 159 | txn: &T, |
| 160 | source: &ViewState, |
| 161 | requested: &[Hash], |
| 162 | ) -> Result<SplitAnalysis, RepositoryError> { |
| 163 | let db = |e: PristineError| RepositoryError::Database(e.to_string()); |
| 164 | |
| 165 | // Resolve each requested hash to an internal id and confirm it is in the |
| 166 | // source view's OWN change log. |
| 167 | let mut requested_ids: HashSet<NodeId> = HashSet::new(); |
| 168 | let mut queue: VecDeque<(Hash, NodeId)> = VecDeque::new(); |
| 169 | for hash in requested { |
| 170 | let id = |
| 171 | txn.get_internal(hash) |
| 172 | .map_err(db)? |
| 173 | .ok_or_else(|| RepositoryError::ChangeNotFound { |
| 174 | hash: hash.to_base32(), |
| 175 | })?; |
| 176 | if txn.get_change_seq(source, id).map_err(db)?.is_none() { |
| 177 | return Err(RepositoryError::ChangeNotInView { |
| 178 | hash: hash.to_base32(), |
| 179 | view: source.name.clone(), |
| 180 | }); |
| 181 | } |
| 182 | if requested_ids.insert(id) { |
| 183 | queue.push_back((*hash, id)); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | // BFS over reverse dependencies, staying inside the source view. |
| 188 | let mut closure_ids: HashSet<NodeId> = requested_ids.clone(); |
| 189 | while let Some((hash, _id)) = queue.pop_front() { |
| 190 | let dependents = txn.get_rev_change_deps(&hash).map_err(db)?; |
| 191 | for dep_id in dependents { |
| 192 | // Only dependents that actually live in the source view matter; |
| 193 | // dependents in other views don't affect this view's coherence. |
| 194 | if txn.get_change_seq(source, dep_id).map_err(db)?.is_none() { |
| 195 | continue; |
| 196 | } |
| 197 | if closure_ids.insert(dep_id) { |
| 198 | let dep_hash = txn.get_external(dep_id).map_err(db)?.ok_or_else(|| { |
| 199 | RepositoryError::ChangeNotFound { |
| 200 | hash: format!("id={}", dep_id.0), |
| 201 | } |
| 202 | })?; |
| 203 | queue.push_back((dep_hash, dep_id)); |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // Build ordered lists keyed by source sequence. |
| 209 | let ordered = |ids: &HashSet<NodeId>| -> Result<Vec<SplitChange>, RepositoryError> { |
| 210 | let mut v = Vec::with_capacity(ids.len()); |
| 211 | for &id in ids { |
| 212 | let seq = txn |
| 213 | .get_change_seq(source, id) |
| 214 | .map_err(db)? |
| 215 | .expect("closure member is in source view"); |
no test coverage detected