diff --git a/src/api/data_types/chunking/mobile_app.rs b/src/api/data_types/chunking/mobile_app.rs index 6223e78c30..5879a87383 100644 --- a/src/api/data_types/chunking/mobile_app.rs +++ b/src/api/data_types/chunking/mobile_app.rs @@ -9,9 +9,24 @@ pub struct ChunkedMobileAppRequest<'a> { pub checksum: Digest, pub chunks: &'a [Digest], #[serde(skip_serializing_if = "Option::is_none")] + pub build_configuration: Option<&'a str>, + // VCS fields + #[serde(skip_serializing_if = "Option::is_none")] pub head_sha: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] - pub build_configuration: Option<&'a str>, + pub base_sha: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub provider: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub head_repo_name: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub base_repo_name: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub head_ref: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub base_ref: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub pr_number: Option<&'a u32>, } #[derive(Debug, Deserialize)] diff --git a/src/api/mod.rs b/src/api/mod.rs index 33c175bfd2..397f3b603a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1036,8 +1036,8 @@ impl<'a> AuthenticatedApi<'a> { project: &str, checksum: Digest, chunks: &[Digest], - head_sha: Option<&str>, build_configuration: Option<&str>, + vcs_info: &VcsInfo<'_>, ) -> ApiResult { let url = format!( "/projects/{}/{}/files/preprodartifacts/assemble/", @@ -1049,8 +1049,15 @@ impl<'a> AuthenticatedApi<'a> { .with_json_body(&ChunkedMobileAppRequest { checksum, chunks, - head_sha, build_configuration, + head_sha: vcs_info.head_sha, + base_sha: vcs_info.base_sha, + provider: vcs_info.vcs_provider, + head_repo_name: vcs_info.head_repo_name, + base_repo_name: vcs_info.base_repo_name, + head_ref: vcs_info.head_ref, + base_ref: vcs_info.base_ref, + pr_number: vcs_info.pr_number, })? .send()? .convert_rnf(ApiErrorKind::ProjectNotFound) @@ -2518,6 +2525,20 @@ struct LogsResponse { data: Vec, } +/// VCS information for mobile app uploads +#[cfg(feature = "unstable-mobile-app")] +#[derive(Debug)] +pub struct VcsInfo<'a> { + pub head_sha: Option<&'a str>, + pub base_sha: Option<&'a str>, + pub vcs_provider: Option<&'a str>, + pub head_repo_name: Option<&'a str>, + pub base_repo_name: Option<&'a str>, + pub head_ref: Option<&'a str>, + pub base_ref: Option<&'a str>, + pub pr_number: Option<&'a u32>, +} + /// Log entry structure from the logs API #[derive(Debug, Deserialize)] pub struct LogEntry { diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index 55ccad7c64..62acb4224e 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -10,7 +10,7 @@ use symbolic::common::ByteView; use zip::write::SimpleFileOptions; use zip::{DateTime, ZipWriter}; -use crate::api::{Api, AuthenticatedApi, ChunkUploadCapability, ChunkedFileState}; +use crate::api::{Api, AuthenticatedApi, ChunkUploadCapability, ChunkedFileState, VcsInfo}; use crate::config::Config; use crate::utils::args::ArgExt as _; use crate::utils::chunks::{upload_chunks, Chunk}; @@ -44,9 +44,45 @@ pub fn make_command(command: Command) -> Command { .required(true), ) .arg( - Arg::new("sha") - .long("sha") - .help("The git commit sha to use for the upload. If not provided, the current commit sha will be used.") + Arg::new("head_sha") + .long("head-sha") + .help("The VCS commit sha to use for the upload. If not provided, the current commit sha will be used.") + ) + .arg( + Arg::new("base_sha") + .long("base-sha") + .help("The VCS commit's base sha to use for the upload. If not provided, the merge-base of the current and remote branch will be used.") + ) + .arg( + Arg::new("vcs_provider") + .long("vcs-provider") + .help("The VCS provider to use for the upload. If not provided, the current provider will be used.") + ) + .arg( + Arg::new("head_repo_name") + .long("head-repo-name") + .help("The name of the git repository to use for the upload (e.g. organization/repository). If not provided, the current repository will be used.") + ) + .arg( + Arg::new("base_repo_name") + .long("base-repo-name") + .help("The name of the git repository to use for the upload (e.g. organization/repository). If not provided, the current repository will be used.") + ) + .arg( + Arg::new("head_ref") + .long("head-ref") + .help("The reference (branch) to use for the upload. If not provided, the current reference will be used.") + ) + .arg( + Arg::new("base_ref") + .long("base-ref") + .help("The reference (branch) to use for the upload. If not provided, the current reference will be used.") + ) + .arg( + Arg::new("pr_number") + .long("pr-number") + .value_parser(clap::value_parser!(u32)) + .help("The pull request number to use for the upload. If not provided, the current pull request number will be used.") ) .arg( Arg::new("build_configuration") @@ -60,12 +96,20 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { .get_many::("paths") .expect("paths argument is required"); - let sha = matches - .get_one("sha") + let head_sha = matches + .get_one("head_sha") .map(String::as_str) .map(Cow::Borrowed) .or_else(|| vcs::find_head().ok().map(Cow::Owned)); + let base_sha = matches.get_one("base_sha").map(String::as_str); + let vcs_provider = matches.get_one("vcs_provider").map(String::as_str); + let head_repo_name = matches.get_one("head_repo_name").map(String::as_str); + let base_repo_name = matches.get_one("base_repo_name").map(String::as_str); + let head_ref = matches.get_one("head_ref").map(String::as_str); + let base_ref = matches.get_one("base_ref").map(String::as_str); + let pr_number = matches.get_one::("pr_number"); + let build_configuration = matches.get_one("build_configuration").map(String::as_str); let api = Api::current(); @@ -124,13 +168,23 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { for (path, zip) in normalized_zips { info!("Uploading file: {}", path.display()); let bytes = ByteView::open(zip.path())?; + let vcs_info = VcsInfo { + head_sha: head_sha.as_deref(), + base_sha, + vcs_provider, + head_repo_name, + base_repo_name, + head_ref, + base_ref, + pr_number, + }; match upload_file( &authenticated_api, &bytes, &org, &project, - sha.as_deref(), build_configuration, + &vcs_info, ) { Ok(artifact_id) => { info!("Successfully uploaded file: {}", path.display()); @@ -289,18 +343,18 @@ fn upload_file( bytes: &[u8], org: &str, project: &str, - sha: Option<&str>, build_configuration: Option<&str>, + vcs_info: &VcsInfo<'_>, ) -> Result { const SELF_HOSTED_ERROR_HINT: &str = "If you are using a self-hosted Sentry server, \ update to the latest version of Sentry to use the mobile-app upload command."; debug!( - "Uploading file to organization: {}, project: {}, sha: {}, build_configuration: {}", + "Uploading file to organization: {}, project: {}, build_configuration: {}, vcs_info: {:?}", org, project, - sha.unwrap_or("unknown"), - build_configuration.unwrap_or("unknown") + build_configuration.unwrap_or("unknown"), + vcs_info, ); let chunk_upload_options = api.get_chunk_upload_options(org)?.ok_or_else(|| { @@ -346,8 +400,14 @@ fn upload_file( // iteration of the loop) we get: // n. state=err, artifact_id unset let result = loop { - let response = - api.assemble_mobile_app(org, project, checksum, &checksums, sha, build_configuration)?; + let response = api.assemble_mobile_app( + org, + project, + checksum, + &checksums, + build_configuration, + vcs_info, + )?; chunks.retain(|Chunk((digest, _))| response.missing_chunks.contains(digest)); if !chunks.is_empty() { diff --git a/tests/integration/_cases/mobile_app/mobile_app-upload-apk.trycmd b/tests/integration/_cases/mobile_app/mobile_app-upload-apk.trycmd index 1f2259717b..1da11a9ee6 100644 --- a/tests/integration/_cases/mobile_app/mobile_app-upload-apk.trycmd +++ b/tests/integration/_cases/mobile_app/mobile_app-upload-apk.trycmd @@ -1,5 +1,5 @@ ``` -$ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/apk.apk --sha test_sha +$ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/apk.apk --head-sha test_head_sha ? success [..]WARN[..]EXPERIMENTAL: The mobile-app subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. Successfully uploaded 1 file to Sentry diff --git a/tests/integration/_cases/mobile_app/mobile_app-upload-help-macos.trycmd b/tests/integration/_cases/mobile_app/mobile_app-upload-help-macos.trycmd index 2364c49980..9d8ff4d33a 100644 --- a/tests/integration/_cases/mobile_app/mobile_app-upload-help-macos.trycmd +++ b/tests/integration/_cases/mobile_app/mobile_app-upload-help-macos.trycmd @@ -19,17 +19,38 @@ Options: The project ID or slug. --auth-token Use the given Sentry auth token. - --sha - The git commit sha to use for the upload. If not provided, the current commit sha will be + --head-sha + The VCS commit sha to use for the upload. If not provided, the current commit sha will be used. - --build-configuration - The build configuration to use for the upload. If not provided, the current version will - be used. + --base-sha + The VCS commit's base sha to use for the upload. If not provided, the merge-base of the + current and remote branch will be used. --log-level Set the log output verbosity. [possible values: trace, debug, info, warn, error] + --vcs-provider + The VCS provider to use for the upload. If not provided, the current provider will be + used. + --head-repo-name + The name of the git repository to use for the upload (e.g. organization/repository). If + not provided, the current repository will be used. --quiet Do not print any output while preserving correct exit code. This flag is currently implemented only for selected subcommands. [aliases: silent] + --base-repo-name + The name of the git repository to use for the upload (e.g. organization/repository). If + not provided, the current repository will be used. + --head-ref + The reference (branch) to use for the upload. If not provided, the current reference will + be used. + --base-ref + The reference (branch) to use for the upload. If not provided, the current reference will + be used. + --pr-number + The pull request number to use for the upload. If not provided, the current pull request + number will be used. + --build-configuration + The build configuration to use for the upload. If not provided, the current version will + be used. -h, --help Print help diff --git a/tests/integration/_cases/mobile_app/mobile_app-upload-help-not-macos.trycmd b/tests/integration/_cases/mobile_app/mobile_app-upload-help-not-macos.trycmd index e77c625a01..92910a2b5b 100644 --- a/tests/integration/_cases/mobile_app/mobile_app-upload-help-not-macos.trycmd +++ b/tests/integration/_cases/mobile_app/mobile_app-upload-help-not-macos.trycmd @@ -1,6 +1,5 @@ ``` $ sentry-cli mobile-app upload --help -? success [EXPERIMENTAL] Upload mobile app files to a project. Usage: sentry-cli[EXE] mobile-app upload [OPTIONS] ... @@ -18,17 +17,38 @@ Options: The project ID or slug. --auth-token Use the given Sentry auth token. - --sha - The git commit sha to use for the upload. If not provided, the current commit sha will be + --head-sha + The VCS commit sha to use for the upload. If not provided, the current commit sha will be used. - --build-configuration - The build configuration to use for the upload. If not provided, the current version will - be used. + --base-sha + The VCS commit's base sha to use for the upload. If not provided, the merge-base of the + current and remote branch will be used. --log-level Set the log output verbosity. [possible values: trace, debug, info, warn, error] + --vcs-provider + The VCS provider to use for the upload. If not provided, the current provider will be + used. + --head-repo-name + The name of the git repository to use for the upload (e.g. organization/repository). If + not provided, the current repository will be used. --quiet Do not print any output while preserving correct exit code. This flag is currently implemented only for selected subcommands. [aliases: silent] + --base-repo-name + The name of the git repository to use for the upload (e.g. organization/repository). If + not provided, the current repository will be used. + --head-ref + The reference (branch) to use for the upload. If not provided, the current reference will + be used. + --base-ref + The reference (branch) to use for the upload. If not provided, the current reference will + be used. + --pr-number + The pull request number to use for the upload. If not provided, the current pull request + number will be used. + --build-configuration + The build configuration to use for the upload. If not provided, the current version will + be used. -h, --help Print help diff --git a/tests/integration/_cases/mobile_app/mobile_app-upload-ipa.trycmd b/tests/integration/_cases/mobile_app/mobile_app-upload-ipa.trycmd index aafd34cafb..fb7a9d5590 100644 --- a/tests/integration/_cases/mobile_app/mobile_app-upload-ipa.trycmd +++ b/tests/integration/_cases/mobile_app/mobile_app-upload-ipa.trycmd @@ -1,5 +1,5 @@ ``` -$ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/ipa.ipa --sha test_sha +$ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/ipa.ipa --head-sha test_head_sha ? success [..]WARN[..]EXPERIMENTAL: The mobile-app subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. Successfully uploaded 1 file to Sentry diff --git a/tests/integration/mobile_app/upload.rs b/tests/integration/mobile_app/upload.rs index 613be3b42d..f9e2c6e4e1 100644 --- a/tests/integration/mobile_app/upload.rs +++ b/tests/integration/mobile_app/upload.rs @@ -159,7 +159,7 @@ fn command_mobile_app_upload_apk_chunked() { "/api/0/projects/wat-org/wat-project/files/preprodartifacts/assemble/", ) .with_header_matcher("content-type", "application/json") - .with_matcher(r#"{"checksum":"18e40e6e932d0b622d631e887be454cc2003dbb5","chunks":["18e40e6e932d0b622d631e887be454cc2003dbb5"],"head_sha":"test_sha"}"#) + .with_matcher(r#"{"checksum":"18e40e6e932d0b622d631e887be454cc2003dbb5","chunks":["18e40e6e932d0b622d631e887be454cc2003dbb5"],"head_sha":"test_head_sha"}"#) .with_response_fn(move |_| { if is_first_assemble_call.swap(false, Ordering::Relaxed) { r#"{ @@ -214,7 +214,7 @@ fn command_mobile_app_upload_ipa_chunked() { "/api/0/projects/wat-org/wat-project/files/preprodartifacts/assemble/", ) .with_header_matcher("content-type", "application/json") - .with_matcher(r#"{"checksum":"ed9da71e3688261875db21b266da84ffe004a8a4","chunks":["ed9da71e3688261875db21b266da84ffe004a8a4"],"head_sha":"test_sha"}"#) + .with_matcher(r#"{"checksum":"ed9da71e3688261875db21b266da84ffe004a8a4","chunks":["ed9da71e3688261875db21b266da84ffe004a8a4"],"head_sha":"test_head_sha"}"#) .with_response_fn(move |_| { if is_first_assemble_call.swap(false, Ordering::Relaxed) { r#"{