fix(import): a duplicate with a capture time skips its sidecar (#2589)

Since a content duplicate carries its file's capture time, the duplicate
check ran after the sidecar read, so a duplicate whose file already has
a capture time read and parsed a sidecar it doesn't use (more I/O on a
network share). The check now happens first and the sidecar is read for
a duplicate only when the file has no capture time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chad Elliott
2026-10-30 02:25:53 -05:00
co-authored by Claude Opus 5.5
parent d9476bc16d
commit a57bd69d98
+14 -11
View File
@@ -324,12 +324,19 @@ impl ImportJob {
break;
}
}
// same bytes as a library photo: skipped below, once its capture time is known
let duplicate_of =
info.content_hash.as_ref().filter(|_| !opts.local).and_then(|h| self.by_hash.get(h).map(|existing| (h.clone(), *existing)));
// the XMP sidecar (or a raw's embedded XMP), read before copying: its capture time files
// and names the copy when the file itself has none
// and names the copy when the file itself has none (a duplicate needs only that time)
let raw = info.kind == MediaKind::Raw;
let packet = crate::sidecar::find_sidecar_for_import(&stems, self.naming, &path)
.and_then(|f| std::fs::read_to_string(f).ok())
.or_else(|| info.xmp.clone().filter(|_| raw));
let packet = if duplicate_of.is_some() && info.captured.is_some() {
None
} else {
crate::sidecar::find_sidecar_for_import(&stems, self.naming, &path)
.and_then(|f| std::fs::read_to_string(f).ok())
.or_else(|| info.xmp.clone().filter(|_| raw))
};
let target = crate::crs::Target::for_file(info.kind, &info.format, info.preview_only.is_some(), &info.meta.camera);
let sidecar = packet.and_then(|x| match crate::sidecar::parse_sidecar(&x, target) {
Ok(sc) => Some(sc),
@@ -345,17 +352,13 @@ impl ImportJob {
if info.captured.is_none() {
info.captured = info.file_modified.clone();
}
// same bytes as a library photo: skipped, with what its file says (its capture time too)
if let Some(h) = &info.content_hash
&& !opts.local
&& let Some(existing) = self.by_hash.get(h)
{
if let Some((hash, existing)) = duplicate_of {
self.done.fetch_add(1, Relaxed);
plan.slots.push(Slot::Item(PreparedItem::Duplicate {
path,
existing: *existing,
existing,
reason: "content",
hash: Some(h.clone()),
hash: Some(hash),
file: Some(Box::new(FileFacts { meta: info.meta, captured: info.captured })),
}));
continue;