From 010e024626828e2f4c2fc5243fc8e2fd4f328b69 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Tue, 20 Jan 2026 14:33:36 -0800 Subject: [PATCH 1/4] feat(build): Add --distribution-group parameter to build upload Add support for distribution groups in build uploads. This parameter allows specifying one or more groups that control update visibility. Builds with at least one matching distribution group will be shown updates for each other. Co-Authored-By: Claude Opus 4.5 --- CHANGELOG.md | 6 +++ src/api/data_types/chunking/build.rs | 6 +++ src/commands/build/upload.rs | 51 +++++++++++++------ .../build/build-upload-help-macos.trycmd | 4 ++ .../build/build-upload-help-not-macos.trycmd | 4 ++ 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ed6d65a09..ee6f90b371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Add `--install-group` parameter to `sentry-cli build upload` for controlling update visibility between builds ([#3094](https://github.com/getsentry/sentry-cli/pull/3094)) + ## 3.1.0 ### New Features diff --git a/src/api/data_types/chunking/build.rs b/src/api/data_types/chunking/build.rs index cc94ab2fa1..21bc80dc77 100644 --- a/src/api/data_types/chunking/build.rs +++ b/src/api/data_types/chunking/build.rs @@ -5,6 +5,10 @@ use sha1_smol::Digest; use super::ChunkedFileState; +fn is_empty_slice(slice: &[T]) -> bool { + slice.is_empty() +} + #[derive(Debug, Serialize)] pub struct ChunkedBuildRequest<'a> { pub checksum: Digest, @@ -13,6 +17,8 @@ pub struct ChunkedBuildRequest<'a> { pub build_configuration: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] pub release_notes: Option<&'a str>, + #[serde(skip_serializing_if = "is_empty_slice")] + pub install_groups: &'a [String], #[serde(flatten)] pub vcs_info: &'a VcsInfo<'a>, } diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index 611a8ebd56..d55b2388ad 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -106,6 +106,16 @@ pub fn make_command(command: Command) -> Command { .long("release-notes") .help("The release notes to use for the upload.") ) + .arg( + Arg::new("install_group") + .long("install-group") + .action(ArgAction::Append) + .help( + "The install group(s) for this build. Can be specified multiple times. \ + Builds with at least one matching install group will be shown updates \ + for each other.", + ) + ) .arg( Arg::new("force_git_metadata") .long("force-git-metadata") @@ -175,6 +185,10 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let build_configuration = matches.get_one("build_configuration").map(String::as_str); let release_notes = matches.get_one("release_notes").map(String::as_str); + let install_groups: Vec = matches + .get_many::("install_group") + .map(|vals| vals.cloned().collect()) + .unwrap_or_default(); let (plugin_name, plugin_version) = parse_plugin_from_pipeline(config.get_pipeline_env()); @@ -237,15 +251,13 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { for (path, zip) in normalized_zips { info!("Uploading file: {}", path.display()); let bytes = ByteView::open(zip.path())?; - match upload_file( - &authenticated_api, - &bytes, - &org, - &project, + let metadata = BuildMetadata { build_configuration, release_notes, - &vcs_info, - ) { + install_groups: &install_groups, + vcs_info: &vcs_info, + }; + match upload_file(&authenticated_api, &bytes, &org, &project, &metadata) { Ok(artifact_url) => { info!("Successfully uploaded file: {}", path.display()); uploaded_paths_and_urls.push((path.to_path_buf(), artifact_url)); @@ -588,19 +600,27 @@ fn handle_directory( normalize_directory(path, temp_dir.path(), plugin_name, plugin_version) } +/// Metadata for a build upload. +struct BuildMetadata<'a> { + build_configuration: Option<&'a str>, + release_notes: Option<&'a str>, + install_groups: &'a [String], + vcs_info: &'a VcsInfo<'a>, +} + /// Returns artifact url if upload was successful. fn upload_file( api: &AuthenticatedApi, bytes: &[u8], org: &str, project: &str, - build_configuration: Option<&str>, - release_notes: Option<&str>, - vcs_info: &VcsInfo<'_>, + metadata: &BuildMetadata<'_>, ) -> Result { debug!( - "Uploading file to organization: {org}, project: {project}, build_configuration: {}, vcs_info: {vcs_info:?}", - build_configuration.unwrap_or("unknown"), + "Uploading file to organization: {org}, project: {project}, build_configuration: {}, install_groups: {:?}, vcs_info: {:?}", + metadata.build_configuration.unwrap_or("unknown"), + metadata.install_groups, + metadata.vcs_info, ); let chunk_upload_options = api.get_chunk_upload_options(org)?; @@ -641,9 +661,10 @@ fn upload_file( &ChunkedBuildRequest { checksum, chunks: &checksums, - build_configuration, - release_notes, - vcs_info, + build_configuration: metadata.build_configuration, + release_notes: metadata.release_notes, + install_groups: metadata.install_groups, + vcs_info: metadata.vcs_info, }, )?; chunks.retain(|Chunk((digest, _))| response.missing_chunks.contains(digest)); diff --git a/tests/integration/_cases/build/build-upload-help-macos.trycmd b/tests/integration/_cases/build/build-upload-help-macos.trycmd index d1a0a51c4e..2b5d6e35f7 100644 --- a/tests/integration/_cases/build/build-upload-help-macos.trycmd +++ b/tests/integration/_cases/build/build-upload-help-macos.trycmd @@ -74,6 +74,10 @@ Options: --release-notes The release notes to use for the upload. + --install-group + The install group(s) for this build. Can be specified multiple times. Builds with at least + one matching install group will be shown updates for each other. + --force-git-metadata Force collection and sending of git metadata (branch, commit, etc.). If neither this nor --no-git-metadata is specified, git metadata is automatically collected when running in diff --git a/tests/integration/_cases/build/build-upload-help-not-macos.trycmd b/tests/integration/_cases/build/build-upload-help-not-macos.trycmd index ab4df7233e..c1e0a67926 100644 --- a/tests/integration/_cases/build/build-upload-help-not-macos.trycmd +++ b/tests/integration/_cases/build/build-upload-help-not-macos.trycmd @@ -73,6 +73,10 @@ Options: --release-notes The release notes to use for the upload. + --install-group + The install group(s) for this build. Can be specified multiple times. Builds with at + least one matching install group will be shown updates for each other. + --force-git-metadata Force collection and sending of git metadata (branch, commit, etc.). If neither this nor --no-git-metadata is specified, git metadata is automatically collected when running in From 9b482b8a0c6f6b2d08e37b3348eb77ca7969be17 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Thu, 22 Jan 2026 16:01:14 -0800 Subject: [PATCH 2/4] fix(test): Update help text line wrapping in build upload tests Co-Authored-By: Claude Opus 4.5 --- .../_cases/build/build-upload-help-not-macos.trycmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/_cases/build/build-upload-help-not-macos.trycmd b/tests/integration/_cases/build/build-upload-help-not-macos.trycmd index c1e0a67926..e9d9461e89 100644 --- a/tests/integration/_cases/build/build-upload-help-not-macos.trycmd +++ b/tests/integration/_cases/build/build-upload-help-not-macos.trycmd @@ -74,8 +74,8 @@ Options: The release notes to use for the upload. --install-group - The install group(s) for this build. Can be specified multiple times. Builds with at - least one matching install group will be shown updates for each other. + The install group(s) for this build. Can be specified multiple times. Builds with at least + one matching install group will be shown updates for each other. --force-git-metadata Force collection and sending of git metadata (branch, commit, etc.). If neither this nor From a8a7f725f26a71bc79cb1b88527dd0419d93af45 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Thu, 22 Jan 2026 16:28:11 -0800 Subject: [PATCH 3/4] ref(build): Remove redundant is_empty_slice helper function Use standard <[_]>::is_empty pattern for skip_serializing_if attribute, consistent with artifact.rs. Co-Authored-By: Claude Opus 4.5 --- src/api/data_types/chunking/build.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/api/data_types/chunking/build.rs b/src/api/data_types/chunking/build.rs index 21bc80dc77..b76a9fcb38 100644 --- a/src/api/data_types/chunking/build.rs +++ b/src/api/data_types/chunking/build.rs @@ -5,10 +5,6 @@ use sha1_smol::Digest; use super::ChunkedFileState; -fn is_empty_slice(slice: &[T]) -> bool { - slice.is_empty() -} - #[derive(Debug, Serialize)] pub struct ChunkedBuildRequest<'a> { pub checksum: Digest, @@ -17,7 +13,7 @@ pub struct ChunkedBuildRequest<'a> { pub build_configuration: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] pub release_notes: Option<&'a str>, - #[serde(skip_serializing_if = "is_empty_slice")] + #[serde(skip_serializing_if = "<[_]>::is_empty")] pub install_groups: &'a [String], #[serde(flatten)] pub vcs_info: &'a VcsInfo<'a>, From fb6d54615a8aa3d4c56070fed64b76a111c19e6e Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Mon, 26 Jan 2026 06:24:44 -0800 Subject: [PATCH 4/4] Update src/commands/build/upload.rs Co-authored-by: Daniel Szoke --- src/commands/build/upload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index d55b2388ad..7d5c3a284a 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -186,7 +186,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let build_configuration = matches.get_one("build_configuration").map(String::as_str); let release_notes = matches.get_one("release_notes").map(String::as_str); let install_groups: Vec = matches - .get_many::("install_group") + .get_many("install_group") .map(|vals| vals.cloned().collect()) .unwrap_or_default();