From 9458e2c59a9a1b6586e1adebe8d339adf226ba4a Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:41:53 +0200 Subject: [PATCH] Take photos removed by Synchronize Folder out of their stacks Found while testing crates/engine/src/sync.rs:851 (SyncStep::Remove; compare mark_deleted in crates/engine/src/cmd/library.rs:168) Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/sync.rs | 9 ++++----- crates/engine/src/tests_sync.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/sync.rs b/crates/engine/src/sync.rs index 713c697a7..f20ad9c95 100644 --- a/crates/engine/src/sync.rs +++ b/crates/engine/src/sync.rs @@ -845,13 +845,12 @@ impl SyncCommit { } } SyncStep::Remove(gone) => { - let ops: Vec = gone - .iter() - .filter(|id| s.catalog.photo(**id).is_some_and(|p| p.in_library())) - .map(|id| Op::SetDeleted { id: *id, deleted: true }) - .collect(); + let ids: Vec = gone.into_iter().filter(|id| s.catalog.photo(*id).is_some_and(|p| p.in_library())).collect(); + let mut ops: Vec = ids.iter().map(|id| Op::SetDeleted { id: *id, deleted: true }).collect(); let n = ops.len(); if n > 0 { + // Like `photo.delete`, take them out of their stacks. + ops.extend(s.catalog.remove_from_stacks_ops(&ids)); match s.commit("Remove Missing Photos", Op::Batch { ops }) { Ok(()) => report.removed += n, Err(e) => report.failed.push((String::new(), e.to_string())), diff --git a/crates/engine/src/tests_sync.rs b/crates/engine/src/tests_sync.rs index 88c184335..8b1ec4f67 100644 --- a/crates/engine/src/tests_sync.rs +++ b/crates/engine/src/tests_sync.rs @@ -739,3 +739,21 @@ fn a_cancelled_scan_does_not_check_the_cached_probes_against_the_disk() { let new = out.candidates.iter().find(|c| c.name == "new.png").expect("new.png is a candidate"); assert!(new.error.is_some(), "the cached probe was used after ✕: {new:?}"); } + +#[test] +fn removing_missing_photos_takes_them_out_of_their_stacks() { + let dir = Scratch::new("remove-stacked"); + let mut s = library(&dir); + let id = |s: &Session, name: &str| s.catalog.photos().find(|p| p.file_name == name).unwrap().id; + let (a, b) = (id(&s, "a.png"), id(&s, "b.png")); + let group = s.catalog.group_ops(a, &[b], true).unwrap(); + s.catalog.apply(group).unwrap(); + assert!(s.catalog.stack_of(b).is_some()); + std::fs::remove_file(dir.path("trip/day1/b.png")).unwrap(); + let r = s.execute("folder.synchronize", &json!({"path": dir.path("trip"), "removeMissing": true})).unwrap(); + assert_eq!(r["removed"].as_u64(), Some(1), "{r}"); + assert!(s.catalog.photo(b).unwrap().deleted); + assert!(s.catalog.stack_of(b).is_none(), "like photo.delete, a removed photo leaves its stack: {:?}", s.catalog.stack_of(b)); + s.execute("edit.undo", &json!({})).unwrap(); + assert!(s.catalog.stack_of(b).is_some(), "one undo step puts b back in its stack"); +}