Split a set of changes out of a view into a new Draft view. This is a pure metadata operation guarded by a reverse-dependency check: a change cannot leave the source while another change that stays behind still depends on it, unless `cascade` moves the dependents too. See [`SplitOptions`] for the options and [`SplitOutcome`] for the result. # Errors - [`RepositoryError::ViewAlreadyExists`] if `
(&mut self, options: SplitOptions)
| 261 | /// - [`RepositoryError::ViewSplitHasDependents`] if changes remaining in the |
| 262 | /// source depend on the split-out set and `cascade` is not set. |
| 263 | pub fn split_view(&mut self, options: SplitOptions) -> Result<SplitOutcome, RepositoryError> { |
| 264 | let db = |e: PristineError| RepositoryError::Database(e.to_string()); |
| 265 | |
| 266 | let from_view_name = options |
| 267 | .from_view |
| 268 | .clone() |
| 269 | .unwrap_or_else(|| self.current_view.clone()); |
| 270 | |
| 271 | if options.changes.is_empty() { |
| 272 | return Err(RepositoryError::InvalidOperation { |
| 273 | message: "no changes specified to split".to_string(), |
| 274 | }); |
| 275 | } |
| 276 | |
| 277 | // ── Dry run: analyze against a read transaction, mutate nothing. ── |
| 278 | if options.dry_run { |
| 279 | let txn = self.pristine.read_txn().map_err(db)?; |
| 280 | let source = txn.get_view(&from_view_name).map_err(db)?.ok_or_else(|| { |
| 281 | RepositoryError::ViewNotFound { |
| 282 | name: from_view_name.clone(), |
| 283 | } |
| 284 | })?; |
| 285 | |
| 286 | if txn.get_view(&options.target_view).map_err(db)?.is_some() { |
| 287 | return Err(RepositoryError::ViewAlreadyExists { |
| 288 | name: options.target_view.clone(), |
| 289 | }); |
| 290 | } |
| 291 | |
| 292 | let analysis = analyze_split(&txn, &source, &options.changes)?; |
| 293 | let blocked = !analysis.dependents.is_empty() && !options.cascade; |
| 294 | let moved = if blocked { |
| 295 | Vec::new() |
| 296 | } else { |
| 297 | analysis.closure.clone() |
| 298 | }; |
| 299 | let target_change_count = moved.len() as u64; |
| 300 | let source_change_count = source.change_count.saturating_sub(moved.len() as u64); |
| 301 | |
| 302 | return Ok(SplitOutcome { |
| 303 | target_view: options.target_view, |
| 304 | from_view: from_view_name, |
| 305 | was_dry_run: true, |
| 306 | blocked, |
| 307 | requested: analysis.requested, |
| 308 | dependents: analysis.dependents, |
| 309 | moved, |
| 310 | source_change_count, |
| 311 | target_change_count, |
| 312 | working_copy_updated: false, |
| 313 | files_written: 0, |
| 314 | files_removed: 0, |
| 315 | }); |
| 316 | } |
| 317 | |
| 318 | // ── Real split: analysis + mutation in one write transaction. ── |
| 319 | let mut txn = self.pristine.write_txn().map_err(db)?; |
| 320 |