Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Files co-owned by Emerge Tools team
/apple-catalog-parsing @getsentry/emerge-tools @getsentry/owners-sentry-cli
/src/api/data_types/snapshots.rs @getsentry/emerge-tools @getsentry/owners-sentry-cli
/src/commands/build @getsentry/emerge-tools @getsentry/owners-sentry-cli
/src/utils/build @getsentry/emerge-tools @getsentry/owners-sentry-cli
/tests/integration/build @getsentry/emerge-tools @getsentry/owners-sentry-cli
16 changes: 2 additions & 14 deletions src/api/data_types/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use serde_json::Value;

use super::VcsInfo;

const IMAGE_FILE_NAME_FIELD: &str = "image_file_name";
const WIDTH_FIELD: &str = "width";
const HEIGHT_FIELD: &str = "height";

Expand Down Expand Up @@ -42,16 +41,7 @@ pub struct ImageMetadata {
}

impl ImageMetadata {
pub fn new(
image_file_name: String,
width: u32,
height: u32,
mut extra: HashMap<String, Value>,
) -> Self {
extra.insert(
IMAGE_FILE_NAME_FIELD.to_owned(),
Value::String(image_file_name),
);
pub fn new(width: u32, height: u32, mut extra: HashMap<String, Value>) -> Self {
extra.insert(WIDTH_FIELD.to_owned(), Value::from(width));
extra.insert(HEIGHT_FIELD.to_owned(), Value::from(height));

Expand All @@ -68,18 +58,16 @@ mod tests {
#[test]
fn cli_managed_fields_override_sidecar_fields() {
let extra = serde_json::from_value(json!({
(IMAGE_FILE_NAME_FIELD): "from-sidecar.png",
(WIDTH_FIELD): 1,
(HEIGHT_FIELD): 2,
"custom": "keep-me"
}))
.unwrap();

let metadata = ImageMetadata::new("from-cli.png".to_owned(), 100, 200, extra);
let metadata = ImageMetadata::new(100, 200, extra);
let serialized = serde_json::to_value(metadata).unwrap();

let expected = json!({
(IMAGE_FILE_NAME_FIELD): "from-cli.png",
(WIDTH_FIELD): 100,
(HEIGHT_FIELD): 200,
"custom": "keep-me"
Expand Down
54 changes: 39 additions & 15 deletions src/commands/build/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,28 @@ fn upload_images(

let mut many_builder = session.many();
let mut manifest_entries = HashMap::new();
let image_count = images.len();

let mut collisions: HashMap<String, Vec<String>> = HashMap::new();
let mut kept_paths: HashMap<String, String> = HashMap::new();
for image in images {
debug!("Processing image: {}", image.path.display());

let image_file_name = image
.relative_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();

let relative_path = crate::utils::fs::path_as_url(&image.relative_path);

if manifest_entries.contains_key(&image_file_name) {
collisions
.entry(image_file_name)
.or_default()
.push(relative_path);
continue;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Uploading" count includes collision-skipped images

Low Severity

The new collision detection in upload_images() can skip images, making the actual upload count (manifest_entries.len()) smaller than images.len(). The "Uploaded" and error messages inside upload_images() were updated to use manifest_entries.len(), but the "Uploading N image files" message in execute() (line 118) still uses images.len(). When collisions occur, users see e.g. "Uploading 5 image files" followed by "Uploaded 3 image files" — a confusing mismatch that didn't exist before this PR since hash-based keying had no collisions.

Fix in Cursor Fix in Web


let hash = compute_sha256_hash(&image.path)?;
let file = runtime
.block_on(tokio::fs::File::open(&image.path))
Expand All @@ -353,33 +370,40 @@ fn upload_images(
.expiration_policy(expiration),
);

let image_file_name = image
.relative_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();

let extra = read_sidecar_metadata(&image.path).unwrap_or_else(|err| {
let mut extra = read_sidecar_metadata(&image.path).unwrap_or_else(|err| {
warn!("Error reading sidecar metadata, ignoring it instead: {err:#}");
HashMap::new()
});
extra.insert("content_hash".to_owned(), serde_json::Value::String(hash));

kept_paths.insert(image_file_name.clone(), relative_path);
manifest_entries.insert(
hash,
ImageMetadata::new(image_file_name, image.width, image.height, extra),
image_file_name,
ImageMetadata::new(image.width, image.height, extra),
);
}

if !collisions.is_empty() {
let mut details = String::new();
for (name, excluded_paths) in &collisions {
let mut all_paths = vec![kept_paths[name].as_str()];
all_paths.extend(excluded_paths.iter().map(|s| s.as_str()));
details.push_str(&format!("\n {name}: {}", all_paths.join(", ")));
}
warn!("Some images share identical file names. Only the first occurrence of each is included:{details}");
}

let result = runtime.block_on(async { many_builder.send().error_for_failures().await });

let uploaded_count = manifest_entries.len();

match result {
Ok(()) => {
println!(
"{} Uploaded {} image {}",
style(">").dim(),
style(image_count).yellow(),
if image_count == 1 { "file" } else { "files" }
style(uploaded_count).yellow(),
if uploaded_count == 1 { "file" } else { "files" }
);
Ok(manifest_entries)
}
Expand All @@ -391,7 +415,7 @@ fn upload_images(
eprintln!(" {}", style(format!("{error:#}")).red());
error_count += 1;
}
anyhow::bail!("Failed to upload {error_count} out of {image_count} images")
anyhow::bail!("Failed to upload {error_count} out of {uploaded_count} images")
}
}
}
Expand Down
Loading