Compute the set of file paths visible on a view. Visibility includes the view's own changes AND all changes inherited through the parent chain. A draft view parented on dev sees dev's files without requiring an explicit insert. A file is visible on a view when: 1. It appears in the global TREE table (has been `add`ed). 2. Its inode has a graph position in the INODES table (has been `record`ed).
(&self, view_name: &str)
| 312 | /// entry) are NOT returned — they persist across switches as |
| 313 | /// working-copy state. |
| 314 | pub fn visible_file_paths(&self, view_name: &str) -> Result<HashSet<String>, RepositoryError> { |
| 315 | let txn = self |
| 316 | .pristine |
| 317 | .read_txn() |
| 318 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 319 | |
| 320 | let view = match txn |
| 321 | .get_view(view_name) |
| 322 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 323 | { |
| 324 | Some(s) => s, |
| 325 | None => return Ok(HashSet::new()), |
| 326 | }; |
| 327 | |
| 328 | // Use the FULL visible change set (own + parent chain) so that |
| 329 | // draft views parented on dev see dev's files. |
| 330 | let view_change_ids = collect_visible_change_ids(&txn, &view)?; |
| 331 | |
| 332 | // Walk TREE and keep paths whose introducing change is in the log. |
| 333 | let mut paths: HashSet<String> = HashSet::new(); |
| 334 | let tree_iter = txn |
| 335 | .iter_tree() |
| 336 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 337 | |
| 338 | for result in tree_iter { |
| 339 | let (path, inode) = result.map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 340 | if let Some(position) = txn |
| 341 | .inode_position(inode) |
| 342 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 343 | { |
| 344 | if view_change_ids.contains(&position.change) { |
| 345 | paths.insert(path); |
| 346 | } |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | // REV_TREE recovery: a view is a FILTER over the global graph — every |
| 351 | // node it exposes already lives in the graph, and TREE is just a |
| 352 | // single-valued bookkeeping index over that graph. When two inodes |
| 353 | // claim the same path (cross-view creates, a materialization |
| 354 | // name-conflict), iter_tree can only expose the one binding — so a |
| 355 | // switch used to classify a path that the target view's filter DOES |
| 356 | // render as "absent from the new view" and silently DELETED it (see |
| 357 | // `switch_file_loss_tests`). Re-insert any path that the filter |
| 358 | // renders: some REV_TREE-claimed inode whose introducing change is |
| 359 | // visible on the view AND alive under that filter — the same |
| 360 | // predicate the materializer's name-conflict detection uses. |
| 361 | { |
| 362 | use atomic_core::pristine::TreeTxnT; |
| 363 | let mut by_path: std::collections::HashMap<String, Vec<Inode>> = |
| 364 | std::collections::HashMap::new(); |
| 365 | if let Ok(pairs) = txn.iter_rev_tree() { |
| 366 | for (inode, path) in pairs { |
| 367 | by_path.entry(path).or_default().push(inode); |
| 368 | } |
| 369 | } |
| 370 | for (path, inodes) in by_path { |
| 371 | if paths.contains(&path) { |