Switch to a different view and update the working copy. This is the primary method for switching views. It: 1. Validates the view exists 2. Updates the current view pointer 3. Materializes the working copy to match the new view's state # Arguments `view` - The name of the view to switch to # Returns Statistics about the materialize operation (files written, etc.) # Errors Returns an error i
(&mut self, view: &str)
| 89 | /// println!("Updated {} files", result.files_written); |
| 90 | /// ``` |
| 91 | pub fn switch_view(&mut self, view: &str) -> Result<MaterializeResult, RepositoryError> { |
| 92 | let old_view_name = self.current_view.clone(); |
| 93 | |
| 94 | // Compute files visible on the OLD view. |
| 95 | let old_files = self.visible_file_paths(&old_view_name)?; |
| 96 | |
| 97 | // Apply only the small set of view-scoped TREE operations and publish |
| 98 | // the new pointer while holding the same database write lock. A marker |
| 99 | // makes the transition recoverable if the process exits mid-switch. |
| 100 | let deferred_paths = self.align_deferred_tree_and_publish_view(view)?; |
| 101 | |
| 102 | // Compute files visible on the NEW view. |
| 103 | let new_files = self.visible_file_paths(view)?; |
| 104 | |
| 105 | if std::env::var_os("ATOMIC_TRACE_SWITCH").is_some() { |
| 106 | eprintln!("[switch] {} -> {}", old_view_name, view); |
| 107 | eprintln!( |
| 108 | "[switch] old_files={} new_files={}", |
| 109 | old_files.len(), |
| 110 | new_files.len() |
| 111 | ); |
| 112 | for f in old_files.difference(&new_files) { |
| 113 | eprintln!("[switch] REMOVE (old only): {}", f); |
| 114 | } |
| 115 | for f in new_files.difference(&old_files) { |
| 116 | eprintln!("[switch] ADD (new only): {}", f); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | let working_copy = FileSystem::from_root(&self.root); |
| 121 | |
| 122 | // ── Phase 1: Shelve ignored files into the OLD view's workspace ── |
| 123 | // |
| 124 | // All ignored files are shelved per-view EXCEPT paths listed in |
| 125 | // `[workspace] expose` in `.atomic/config.toml`. Exposed paths |
| 126 | // persist across all views (tool configs like .opencode/, .vscode/). |
| 127 | // |
| 128 | // This uses `rename()` which is O(1) on the same filesystem — |
| 129 | // no data is copied, just inode pointers are updated. |
| 130 | // |
| 131 | // The rule: |
| 132 | // - Tracked files → managed by the graph (phases 2-4) |
| 133 | // - Untracked, ignored, exposed → left alone (persists across views) |
| 134 | // - Untracked, ignored, NOT exposed → shelved/restored per-view (phases 1 & 5) |
| 135 | // - Untracked, novel → user's undecided work, left alone |
| 136 | let old_ws = workspace_path(&self.dot_dir, &old_view_name); |
| 137 | ensure_workspace_dir(&self.dot_dir, &old_view_name)?; |
| 138 | |
| 139 | let repo_expose = atomic_config::RepoConfig::load(&self.config_path()) |
| 140 | .unwrap_or_default() |
| 141 | .workspace |
| 142 | .expose; |
| 143 | let global_expose = atomic_config::GlobalConfig::load() |
| 144 | .map(|c| c.workspace.expose) |
| 145 | .unwrap_or_default(); |
| 146 | |
| 147 | // Merge global + repo-local expose patterns (deduplicated) |
| 148 | let mut expose_patterns = global_expose; |