Skip to content

Commit cdcc4a2

Browse files
fix(snapshots): Preserve subdirectory structure in manifest keys (#3269)
Use full relative paths (e.g. `settings/profile.png`) instead of bare filenames (e.g. `profile.png`) as manifest keys when uploading build snapshots. Previously, `upload_images` extracted only the filename from each image's relative path to use as the manifest key. This meant images in different subdirectories with the same filename would collide — only the first encountered would be uploaded, and the rest silently skipped with a warning. Now the manifest key includes the full relative path from the upload directory, preserving the subdirectory structure and eliminating false collisions. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3d7f705 commit cdcc4a2

File tree

2 files changed

+9
-29
lines changed

2 files changed

+9
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
### Fixes
1515

1616
- (snapshots) Chunk image uploads to avoid file descriptor exhaustion and 413 errors when uploading hundreds of images ([#3249](https://github.com/getsentry/sentry-cli/pull/3249))
17+
- (snapshots) Preserve subdirectory structure in snapshot manifest keys instead of flattening to bare filenames ([#3269](https://github.com/getsentry/sentry-cli/pull/3269))
1718
- Replace `eprintln!` with `log::info!` for progress bar completion messages when the progress bar is disabled (e.g. in CI). This avoids spurious stderr output that some CI systems treat as errors ([#3223](https://github.com/getsentry/sentry-cli/pull/3223)).
1819

1920
## 3.3.5

src/commands/build/snapshots.rs

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,7 @@ fn upload_images(
354354
.context("Failed to create tokio runtime")?;
355355

356356
let mut manifest_entries = HashMap::new();
357-
let mut collisions: HashMap<String, Vec<String>> = HashMap::new();
358-
let mut kept_paths = HashMap::new();
357+
let mut duplicates: Vec<String> = Vec::new();
359358
let mut uploads = Vec::with_capacity(images.len());
360359

361360
let hashed_images: Vec<_> = images
@@ -367,20 +366,10 @@ fn upload_images(
367366
.collect::<Result<Vec<_>>>()?;
368367

369368
for (image, hash) in hashed_images {
370-
let image_file_name = image
371-
.relative_path
372-
.file_name()
373-
.unwrap_or_default()
374-
.to_string_lossy()
375-
.into_owned();
376-
377-
let relative_path = crate::utils::fs::path_as_url(&image.relative_path);
369+
let image_key = crate::utils::fs::path_as_url(&image.relative_path);
378370

379-
if manifest_entries.contains_key(&image_file_name) {
380-
collisions
381-
.entry(image_file_name)
382-
.or_default()
383-
.push(relative_path);
371+
if manifest_entries.contains_key(&image_key) {
372+
duplicates.push(image_key);
384373
continue;
385374
}
386375

@@ -392,29 +381,19 @@ fn upload_images(
392381
});
393382
extra.insert("content_hash".to_owned(), serde_json::Value::String(hash));
394383

395-
kept_paths.insert(image_file_name.clone(), relative_path);
396384
uploads.push(PreparedImage {
397385
path: image.path,
398386
key,
399387
});
400388
manifest_entries.insert(
401-
image_file_name,
389+
image_key,
402390
ImageMetadata::new(image.width, image.height, extra),
403391
);
404392
}
405393

406-
if !collisions.is_empty() {
407-
let details: String = collisions
408-
.iter()
409-
.map(|(name, excluded)| {
410-
let kept = &kept_paths[name];
411-
let all = std::iter::once(kept.as_str())
412-
.chain(excluded.iter().map(|s| s.as_str()))
413-
.join(", ");
414-
format!("\n {name}: {all}")
415-
})
416-
.collect();
417-
warn!("Some images share identical file names. Only the first occurrence of each is included:{details}");
394+
if !duplicates.is_empty() {
395+
let paths = duplicates.join(", ");
396+
warn!("Duplicate paths encountered, skipping: {paths}");
418397
}
419398

420399
let total_count = uploads.len();

0 commit comments

Comments
 (0)