From a5ae50502fa9756bf076c7a5df95eefd845f79ef Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 Aug 2025 17:45:28 +0200 Subject: [PATCH 01/14] feat(build): Auto-detect base_ref from git merge-base Add automatic detection of base reference for build uploads by finding the merge-base between current HEAD and remote tracking branch. This works by: - Finding the remote tracking branch (origin/HEAD, main, master, develop) - Calculating git merge-base between HEAD and remote branch - Returning the branch name pointing to merge-base or commit SHA This mirrors the existing head_ref auto-detection behavior. --- src/commands/build/upload.rs | 30 ++++++++++-- src/utils/vcs.rs | 48 +++++++++++++++++++ .../build/build-upload-help-macos.trycmd | 4 +- .../build/build-upload-help-not-macos.trycmd | 4 +- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index dd425cc262..7f6f952c39 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -24,7 +24,8 @@ use crate::utils::fs::TempDir; use crate::utils::fs::TempFile; use crate::utils::progress::ProgressBar; use crate::utils::vcs::{ - self, get_provider_from_remote, get_repo_from_remote, git_repo_head_ref, git_repo_remote_url, + self, get_provider_from_remote, get_repo_from_remote, git_repo_base_ref, git_repo_head_ref, + git_repo_remote_url, }; pub fn make_command(command: Command) -> Command { @@ -79,7 +80,7 @@ pub fn make_command(command: Command) -> Command { .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.") + .help("The base reference (branch) to use for the upload. If not provided, the merge-base with the remote tracking branch will be used.") ) .arg( Arg::new("pr_number") @@ -172,7 +173,28 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let base_repo_name = matches.get_one("base_repo_name").map(String::as_str); let base_sha = matches.get_one("base_sha").map(String::as_str); - let base_ref = matches.get_one("base_ref").map(String::as_str); + let base_ref = matches + .get_one("base_ref") + .map(String::as_str) + .map(Cow::Borrowed) + .or_else(|| { + // Try to get the base ref from the VCS if not provided + // This attempts to find the merge-base with the remote tracking branch + if let Ok(repo) = git2::Repository::open_from_env() { + match git_repo_base_ref(&repo, &cached_remote) { + Ok(base_ref_name) => { + debug!("Found base branch reference: {}", base_ref_name); + Some(Cow::Owned(base_ref_name)) + } + Err(e) => { + debug!("No base branch reference found: {}", e); + None + } + } + } else { + None + } + }); let pr_number = matches.get_one::("pr_number"); let build_configuration = matches.get_one("build_configuration").map(String::as_str); @@ -237,7 +259,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { head_repo_name: head_repo_name.as_deref(), base_repo_name, head_ref: head_ref.as_deref(), - base_ref, + base_ref: base_ref.as_deref(), pr_number, }; match upload_file( diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index e92d0e5084..83da321ff0 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -249,6 +249,54 @@ pub fn git_repo_head_ref(repo: &git2::Repository) -> Result { } } +pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result { + // Get the current HEAD commit + let head_commit = repo.head()?.peel_to_commit()?; + + // Try to find the remote tracking branch + let remote_branch_name = format!("refs/remotes/{remote_name}/HEAD"); + let remote_ref = match repo.find_reference(&remote_branch_name) { + Ok(r) => r, + Err(_) => { + // If remote/HEAD doesn't exist, try to find the default branch + // First try common default branch names + for branch in &["main", "master", "develop"] { + let remote_branch = format!("refs/remotes/{remote_name}/{branch}"); + if let Ok(r) = repo.find_reference(&remote_branch) { + return find_merge_base_ref(repo, &head_commit, &r); + } + } + bail!("Could not find remote tracking branch for {}", remote_name); + } + }; + + find_merge_base_ref(repo, &head_commit, &remote_ref) +} + +fn find_merge_base_ref( + repo: &git2::Repository, + head_commit: &git2::Commit, + remote_ref: &git2::Reference, +) -> Result { + let remote_commit = remote_ref.peel_to_commit()?; + let merge_base_oid = repo.merge_base(head_commit.id(), remote_commit.id())?; + + // Try to find a branch name that points to this commit + let branches = repo.branches(Some(git2::BranchType::Local))?; + for (branch, _) in branches.flatten() { + if let Ok(branch_commit) = branch.get().peel_to_commit() { + if branch_commit.id() == merge_base_oid { + if let Some(branch_name) = branch.name()? { + return Ok(branch_name.to_owned()); + } + } + } + } + + // If no branch name found, return the commit SHA + Ok(merge_base_oid.to_string()) +} + fn find_reference_url(repo: &str, repos: &[Repo]) -> Result> { let mut non_git = false; for configured_repo in repos { diff --git a/tests/integration/_cases/build/build-upload-help-macos.trycmd b/tests/integration/_cases/build/build-upload-help-macos.trycmd index 2aeba20894..42bd371b76 100644 --- a/tests/integration/_cases/build/build-upload-help-macos.trycmd +++ b/tests/integration/_cases/build/build-upload-help-macos.trycmd @@ -42,8 +42,8 @@ Options: 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. + The base reference (branch) to use for the upload. If not provided, the merge-base with + the remote tracking branch 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. 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 5f6ee57c84..25518253d5 100644 --- a/tests/integration/_cases/build/build-upload-help-not-macos.trycmd +++ b/tests/integration/_cases/build/build-upload-help-not-macos.trycmd @@ -41,8 +41,8 @@ Options: 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. + The base reference (branch) to use for the upload. If not provided, the merge-base with + the remote tracking branch 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. From 23bb29bd09c4b0d5d7850fbd19c419af28b5c539 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 29 Aug 2025 11:40:17 +0200 Subject: [PATCH 02/14] Address review comments --- src/commands/build/upload.rs | 51 +++++++++++++++++++----------------- src/utils/vcs.rs | 10 +++---- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index 7f6f952c39..870fc64798 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -114,7 +114,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let cached_remote = config.get_cached_vcs_remote(); // Try to open the git repository and find the remote, but handle errors gracefully. - let (vcs_provider, head_repo_name, head_ref) = { + let (vcs_provider, head_repo_name, head_ref, base_ref) = { // Try to open the repo and get the remote URL, but don't fail if not in a repo. let repo = git2::Repository::open_from_env().ok(); let repo_ref = repo.as_ref(); @@ -168,33 +168,36 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { .map(Cow::Owned) }); - (vcs_provider, head_repo_name, head_ref) + let base_ref = matches + .get_one("base_ref") + .map(String::as_str) + .map(Cow::Borrowed) + .or_else(|| { + // Try to get the base ref from the VCS if not provided + // This attempts to find the merge-base with the remote tracking branch + repo_ref + .and_then(|r| match git_repo_base_ref(r, &cached_remote) { + Ok(Some(base_ref_name)) => { + debug!("Found base branch reference: {}", base_ref_name); + Some(base_ref_name) + } + Ok(None) => { + debug!("No base branch reference found (no local branch points to merge-base)"); + None + } + Err(e) => { + debug!("Error getting base branch reference: {}", e); + None + } + }) + .map(Cow::Owned) + }); + + (vcs_provider, head_repo_name, head_ref, base_ref) }; let base_repo_name = matches.get_one("base_repo_name").map(String::as_str); let base_sha = matches.get_one("base_sha").map(String::as_str); - let base_ref = matches - .get_one("base_ref") - .map(String::as_str) - .map(Cow::Borrowed) - .or_else(|| { - // Try to get the base ref from the VCS if not provided - // This attempts to find the merge-base with the remote tracking branch - if let Ok(repo) = git2::Repository::open_from_env() { - match git_repo_base_ref(&repo, &cached_remote) { - Ok(base_ref_name) => { - debug!("Found base branch reference: {}", base_ref_name); - Some(Cow::Owned(base_ref_name)) - } - Err(e) => { - debug!("No base branch reference found: {}", e); - None - } - } - } else { - None - } - }); let pr_number = matches.get_one::("pr_number"); let build_configuration = matches.get_one("build_configuration").map(String::as_str); diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 83da321ff0..84487df2b7 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -249,7 +249,7 @@ pub fn git_repo_head_ref(repo: &git2::Repository) -> Result { } } -pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result { +pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result> { // Get the current HEAD commit let head_commit = repo.head()?.peel_to_commit()?; @@ -277,7 +277,7 @@ fn find_merge_base_ref( repo: &git2::Repository, head_commit: &git2::Commit, remote_ref: &git2::Reference, -) -> Result { +) -> Result> { let remote_commit = remote_ref.peel_to_commit()?; let merge_base_oid = repo.merge_base(head_commit.id(), remote_commit.id())?; @@ -287,14 +287,14 @@ fn find_merge_base_ref( if let Ok(branch_commit) = branch.get().peel_to_commit() { if branch_commit.id() == merge_base_oid { if let Some(branch_name) = branch.name()? { - return Ok(branch_name.to_owned()); + return Ok(Some(branch_name.to_owned())); } } } } - // If no branch name found, return the commit SHA - Ok(merge_base_oid.to_string()) + // If no branch name found, return None (only return branch names) + Ok(None) } fn find_reference_url(repo: &str, repos: &[Repo]) -> Result> { From b3fc61e63297e1d817158da861bc37f501753f2f Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 29 Aug 2025 14:02:42 +0200 Subject: [PATCH 03/14] Address review comments by querying for default branch --- src/utils/vcs.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 84487df2b7..4763a0e3cf 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -255,20 +255,29 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result r, - Err(_) => { - // If remote/HEAD doesn't exist, try to find the default branch - // First try common default branch names - for branch in &["main", "master", "develop"] { - let remote_branch = format!("refs/remotes/{remote_name}/{branch}"); - if let Ok(r) = repo.find_reference(&remote_branch) { - return find_merge_base_ref(repo, &head_commit, &r); - } - } - bail!("Could not find remote tracking branch for {}", remote_name); - } - }; + let remote_ref = repo + .find_reference(&remote_branch_name) + .or_else(|_| { + // If remote/HEAD doesn't exist, try to query the remote for its actual default branch + let mut remote = repo.find_remote(remote_name)?; + remote.connect(git2::Direction::Fetch)?; + let default_branch_buf = remote.default_branch()?; + let default_branch = default_branch_buf.as_str().unwrap(); + + // Convert "refs/heads/main" to "refs/remotes/origin/main" + let branch_name = default_branch + .strip_prefix("refs/heads/") + .unwrap_or(default_branch); + let remote_branch = format!("refs/remotes/{remote_name}/{branch_name}"); + repo.find_reference(&remote_branch) + }) + .map_err(|e| { + anyhow::anyhow!( + "Could not find remote tracking branch for {}: {}", + remote_name, + e + ) + })?; find_merge_base_ref(repo, &head_commit, &remote_ref) } From daa4c0046a0c7b21e7fab91f0ec8fdaa53f002e5 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 2 Sep 2025 19:44:11 +0200 Subject: [PATCH 04/14] feat(build): Improve base_ref logging visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add debug logging when base_ref is found - Use info level logging when base_ref cannot be detected for CI visibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/commands/build/upload.rs | 4 ++-- src/utils/vcs.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index 870fc64798..a66db35451 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -182,11 +182,11 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { Some(base_ref_name) } Ok(None) => { - debug!("No base branch reference found (no local branch points to merge-base)"); + info!("No base branch reference found (no local branch points to merge-base)"); None } Err(e) => { - debug!("Error getting base branch reference: {}", e); + info!("Could not detect base branch reference: {}", e); None } }) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 4763a0e3cf..0a2f087086 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -296,6 +296,7 @@ fn find_merge_base_ref( if let Ok(branch_commit) = branch.get().peel_to_commit() { if branch_commit.id() == merge_base_oid { if let Some(branch_name) = branch.name()? { + debug!("Found base branch reference: {}", branch_name); return Ok(Some(branch_name.to_owned())); } } From 65075c1c24e34e44186076e792dbffe2c1d4554b Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 3 Sep 2025 11:11:09 +0200 Subject: [PATCH 05/14] feat(vcs): Address PR review comments for base_ref detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix UTF-8 error handling with proper anyhow::Error conversion - Add explicit type annotations for closure error handling - Use anyhow::Error consistently instead of git2::Error for better error messages - Implement cleaner error propagation as suggested in review 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 0a2f087086..444fc7e893 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -257,21 +257,23 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result Result { // If remote/HEAD doesn't exist, try to query the remote for its actual default branch let mut remote = repo.find_remote(remote_name)?; remote.connect(git2::Direction::Fetch)?; let default_branch_buf = remote.default_branch()?; - let default_branch = default_branch_buf.as_str().unwrap(); + let default_branch = default_branch_buf + .as_str() + .ok_or_else(|| anyhow::anyhow!("Default branch contains invalid UTF-8"))?; // Convert "refs/heads/main" to "refs/remotes/origin/main" let branch_name = default_branch .strip_prefix("refs/heads/") .unwrap_or(default_branch); let remote_branch = format!("refs/remotes/{remote_name}/{branch_name}"); - repo.find_reference(&remote_branch) + Ok(repo.find_reference(&remote_branch)?) }) - .map_err(|e| { + .map_err(|e: anyhow::Error| { anyhow::anyhow!( "Could not find remote tracking branch for {}: {}", remote_name, From 6d75d7f877a7efd3d6b83bf2937da2e8230035d4 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 3 Sep 2025 15:16:15 +0200 Subject: [PATCH 06/14] feat(build): Log base ref detection failures at warning level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated logging for base ref detection failures from info to warning level to make these important failure cases more visible to users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/commands/build/upload.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index a66db35451..d269992474 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -182,11 +182,11 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { Some(base_ref_name) } Ok(None) => { - info!("No base branch reference found (no local branch points to merge-base)"); + warn!("No base branch reference found (no local branch points to merge-base)"); None } Err(e) => { - info!("Could not detect base branch reference: {}", e); + warn!("Could not detect base branch reference: {}", e); None } }) From 0fc25d00655cb83e3ead3f3afef04d3582e2b371 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 3 Sep 2025 17:46:47 +0200 Subject: [PATCH 07/14] fix: Update test expectations to include base ref detection warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new base ref auto-detection feature logs warnings when it cannot find a local branch that points to the merge-base. Updated integration tests to expect these warning messages in their output. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../_cases/build/build-upload-apk-all-uploaded.trycmd | 1 + tests/integration/_cases/build/build-upload-apk-no-token.trycmd | 1 + tests/integration/_cases/build/build-upload-apk.trycmd | 1 + tests/integration/_cases/build/build-upload-ipa.trycmd | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd b/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd index 095710d2e5..f33434954f 100644 --- a/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd +++ b/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd @@ -2,6 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/apk.apk ? success [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. + WARN [..] No base branch reference found (no local branch points to merge-base) Successfully uploaded 1 file to Sentry - tests/integration/_fixtures/build/apk.apk (http[..]/wat-org/preprod/wat-project/42) diff --git a/tests/integration/_cases/build/build-upload-apk-no-token.trycmd b/tests/integration/_cases/build/build-upload-apk-no-token.trycmd index a29ebd4323..4cdfac895d 100644 --- a/tests/integration/_cases/build/build-upload-apk-no-token.trycmd +++ b/tests/integration/_cases/build/build-upload-apk-no-token.trycmd @@ -2,6 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/apk.apk ? failed [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. + WARN [..] No base branch reference found (no local branch points to merge-base) error: Auth token is required for this request. Please run `sentry-cli login` and try again! Add --log-level=[info|debug] or export SENTRY_LOG_LEVEL=[info|debug] to see more output. diff --git a/tests/integration/_cases/build/build-upload-apk.trycmd b/tests/integration/_cases/build/build-upload-apk.trycmd index 97868f4ae6..3f0ee93dfa 100644 --- a/tests/integration/_cases/build/build-upload-apk.trycmd +++ b/tests/integration/_cases/build/build-upload-apk.trycmd @@ -2,6 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/apk.apk --head-sha test_head_sha ? success [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. + WARN [..] No base branch reference found (no local branch points to merge-base) Successfully uploaded 1 file to Sentry - tests/integration/_fixtures/build/apk.apk (http[..]/wat-org/preprod/wat-project/42) diff --git a/tests/integration/_cases/build/build-upload-ipa.trycmd b/tests/integration/_cases/build/build-upload-ipa.trycmd index 8d0bde6566..311e1b6185 100644 --- a/tests/integration/_cases/build/build-upload-ipa.trycmd +++ b/tests/integration/_cases/build/build-upload-ipa.trycmd @@ -2,6 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/ipa.ipa --head-sha test_head_sha ? success [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. + WARN [..] No base branch reference found (no local branch points to merge-base) Successfully uploaded 1 file to Sentry - tests/integration/_fixtures/build/ipa.ipa (http[..]/wat-org/preprod/wat-project/some-text-id) From 27c41db85d2bd0c13a548d313572bf7b82567b92 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 4 Sep 2025 09:43:06 +0200 Subject: [PATCH 08/14] fix: Update test snapshots to match CI environment error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CI environment gets SSL/TLS errors when trying to connect to git remotes, resulting in "Could not detect base branch reference: ..." messages instead of the simpler local messages. Updated test expectations to use flexible patterns that match both local and CI error scenarios. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../_cases/build/build-upload-apk-all-uploaded.trycmd | 2 +- tests/integration/_cases/build/build-upload-apk-no-token.trycmd | 2 +- tests/integration/_cases/build/build-upload-apk.trycmd | 2 +- tests/integration/_cases/build/build-upload-ipa.trycmd | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd b/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd index f33434954f..3a5a317409 100644 --- a/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd +++ b/tests/integration/_cases/build/build-upload-apk-all-uploaded.trycmd @@ -2,7 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/apk.apk ? success [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. - WARN [..] No base branch reference found (no local branch points to merge-base) + WARN [..] Could not detect base branch reference: [..] Successfully uploaded 1 file to Sentry - tests/integration/_fixtures/build/apk.apk (http[..]/wat-org/preprod/wat-project/42) diff --git a/tests/integration/_cases/build/build-upload-apk-no-token.trycmd b/tests/integration/_cases/build/build-upload-apk-no-token.trycmd index 4cdfac895d..448173f456 100644 --- a/tests/integration/_cases/build/build-upload-apk-no-token.trycmd +++ b/tests/integration/_cases/build/build-upload-apk-no-token.trycmd @@ -2,7 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/apk.apk ? failed [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. - WARN [..] No base branch reference found (no local branch points to merge-base) + WARN [..] Could not detect base branch reference: [..] error: Auth token is required for this request. Please run `sentry-cli login` and try again! Add --log-level=[info|debug] or export SENTRY_LOG_LEVEL=[info|debug] to see more output. diff --git a/tests/integration/_cases/build/build-upload-apk.trycmd b/tests/integration/_cases/build/build-upload-apk.trycmd index 3f0ee93dfa..9b869af97a 100644 --- a/tests/integration/_cases/build/build-upload-apk.trycmd +++ b/tests/integration/_cases/build/build-upload-apk.trycmd @@ -2,7 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/apk.apk --head-sha test_head_sha ? success [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. - WARN [..] No base branch reference found (no local branch points to merge-base) + WARN [..] Could not detect base branch reference: [..] Successfully uploaded 1 file to Sentry - tests/integration/_fixtures/build/apk.apk (http[..]/wat-org/preprod/wat-project/42) diff --git a/tests/integration/_cases/build/build-upload-ipa.trycmd b/tests/integration/_cases/build/build-upload-ipa.trycmd index 311e1b6185..c259620e4f 100644 --- a/tests/integration/_cases/build/build-upload-ipa.trycmd +++ b/tests/integration/_cases/build/build-upload-ipa.trycmd @@ -2,7 +2,7 @@ $ sentry-cli build upload tests/integration/_fixtures/build/ipa.ipa --head-sha test_head_sha ? success [..]WARN[..]EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. - WARN [..] No base branch reference found (no local branch points to merge-base) + WARN [..] Could not detect base branch reference: [..] Successfully uploaded 1 file to Sentry - tests/integration/_fixtures/build/ipa.ipa (http[..]/wat-org/preprod/wat-project/some-text-id) From f4d5564dd2647d73d4314791a0b9016700075302 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 4 Sep 2025 09:55:59 +0200 Subject: [PATCH 09/14] refactor: Use iterator chain for branch searching instead of explicit loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the explicit for loop with a more idiomatic iterator chain using find_map(). This addresses the PR review comment about simplifying the branching code with method chaining. The new implementation: - Uses find_map() to search and transform in one step - Eliminates the explicit for loop and early returns - Maintains the same functionality with cleaner, more idiomatic Rust 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 444fc7e893..f2d6117e4f 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -293,20 +293,21 @@ fn find_merge_base_ref( let merge_base_oid = repo.merge_base(head_commit.id(), remote_commit.id())?; // Try to find a branch name that points to this commit - let branches = repo.branches(Some(git2::BranchType::Local))?; - for (branch, _) in branches.flatten() { - if let Ok(branch_commit) = branch.get().peel_to_commit() { + let branch_name = repo + .branches(Some(git2::BranchType::Local))? + .flatten() + .find_map(|(branch, _)| { + let branch_commit = branch.get().peel_to_commit().ok()?; if branch_commit.id() == merge_base_oid { - if let Some(branch_name) = branch.name()? { - debug!("Found base branch reference: {}", branch_name); - return Ok(Some(branch_name.to_owned())); - } + let branch_name = branch.name().ok()??; + debug!("Found base branch reference: {}", branch_name); + Some(branch_name.to_owned()) + } else { + None } - } - } + }); - // If no branch name found, return None (only return branch names) - Ok(None) + Ok(branch_name) } fn find_reference_url(repo: &str, repos: &[Repo]) -> Result> { From d396e78613ec25e2360af2f49e8f2356bfa08761 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 5 Sep 2025 13:12:59 +0200 Subject: [PATCH 10/14] fix(build): Use merge-base commit SHA as base reference instead of branch name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the base ref detection would only work if a local branch pointed to the exact merge-base commit. This implementation now returns the merge-base commit SHA directly, enabling automatic base reference detection in all cases where a merge-base can be determined. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/commands/build/upload.rs | 4 ++-- src/utils/vcs.rs | 20 ++++---------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index d269992474..b0777a05b1 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -178,11 +178,11 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { repo_ref .and_then(|r| match git_repo_base_ref(r, &cached_remote) { Ok(Some(base_ref_name)) => { - debug!("Found base branch reference: {}", base_ref_name); + debug!("Found base reference: {}", base_ref_name); Some(base_ref_name) } Ok(None) => { - warn!("No base branch reference found (no local branch points to merge-base)"); + warn!("No base reference found (could not determine merge-base)"); None } Err(e) => { diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index f2d6117e4f..8138ec20b2 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -292,22 +292,10 @@ fn find_merge_base_ref( let remote_commit = remote_ref.peel_to_commit()?; let merge_base_oid = repo.merge_base(head_commit.id(), remote_commit.id())?; - // Try to find a branch name that points to this commit - let branch_name = repo - .branches(Some(git2::BranchType::Local))? - .flatten() - .find_map(|(branch, _)| { - let branch_commit = branch.get().peel_to_commit().ok()?; - if branch_commit.id() == merge_base_oid { - let branch_name = branch.name().ok()??; - debug!("Found base branch reference: {}", branch_name); - Some(branch_name.to_owned()) - } else { - None - } - }); - - Ok(branch_name) + // Return the merge-base commit SHA as the base reference + let merge_base_sha = merge_base_oid.to_string(); + debug!("Found merge-base commit as base reference: {}", merge_base_sha); + Ok(Some(merge_base_sha)) } fn find_reference_url(repo: &str, repos: &[Repo]) -> Result> { From ed9a90359dc969d8f29385053ab7fbef62ab0cf2 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 5 Sep 2025 14:04:16 +0200 Subject: [PATCH 11/14] style: Apply rustfmt formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 8138ec20b2..7d508b50a2 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -294,7 +294,10 @@ fn find_merge_base_ref( // Return the merge-base commit SHA as the base reference let merge_base_sha = merge_base_oid.to_string(); - debug!("Found merge-base commit as base reference: {}", merge_base_sha); + debug!( + "Found merge-base commit as base reference: {}", + merge_base_sha + ); Ok(Some(merge_base_sha)) } From 321c1188f58c9cf15ad14322ce0177a9914e1b48 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 8 Sep 2025 08:58:17 +0200 Subject: [PATCH 12/14] fix(build): Remove remote.connect fallback to avoid SSL/TLS issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the network fallback that queries remote default branch when origin/HEAD is not set locally. This eliminates SSL/TLS errors that occur when libgit2 lacks proper SSL support. Users without origin/HEAD set up will get a clear error message and can fix it with: git remote set-head origin --auto 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 7d508b50a2..09035fbe55 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -257,23 +257,7 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result Result { - // If remote/HEAD doesn't exist, try to query the remote for its actual default branch - let mut remote = repo.find_remote(remote_name)?; - remote.connect(git2::Direction::Fetch)?; - let default_branch_buf = remote.default_branch()?; - let default_branch = default_branch_buf - .as_str() - .ok_or_else(|| anyhow::anyhow!("Default branch contains invalid UTF-8"))?; - - // Convert "refs/heads/main" to "refs/remotes/origin/main" - let branch_name = default_branch - .strip_prefix("refs/heads/") - .unwrap_or(default_branch); - let remote_branch = format!("refs/remotes/{remote_name}/{branch_name}"); - Ok(repo.find_reference(&remote_branch)?) - }) - .map_err(|e: anyhow::Error| { + .map_err(|e| { anyhow::anyhow!( "Could not find remote tracking branch for {}: {}", remote_name, From 1b74949b25039f00b7cc3959f8e0980704d1ef55 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 8 Sep 2025 09:05:26 +0200 Subject: [PATCH 13/14] style: Apply rustfmt formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 09035fbe55..6be9b66c76 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -255,15 +255,13 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result Date: Mon, 8 Sep 2025 13:20:48 +0200 Subject: [PATCH 14/14] fix: Simplify return types from Result> to Result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The git_repo_base_ref and find_merge_base_ref functions never return Ok(None), so simplified return types and removed unreachable code paths. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/commands/build/upload.rs | 6 +----- src/utils/vcs.rs | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index b0777a05b1..7be5a2d0a8 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -177,14 +177,10 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { // This attempts to find the merge-base with the remote tracking branch repo_ref .and_then(|r| match git_repo_base_ref(r, &cached_remote) { - Ok(Some(base_ref_name)) => { + Ok(base_ref_name) => { debug!("Found base reference: {}", base_ref_name); Some(base_ref_name) } - Ok(None) => { - warn!("No base reference found (could not determine merge-base)"); - None - } Err(e) => { warn!("Could not detect base branch reference: {}", e); None diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 6be9b66c76..5e84a10a36 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -249,7 +249,7 @@ pub fn git_repo_head_ref(repo: &git2::Repository) -> Result { } } -pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result> { +pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result { // Get the current HEAD commit let head_commit = repo.head()?.peel_to_commit()?; @@ -270,7 +270,7 @@ fn find_merge_base_ref( repo: &git2::Repository, head_commit: &git2::Commit, remote_ref: &git2::Reference, -) -> Result> { +) -> Result { let remote_commit = remote_ref.peel_to_commit()?; let merge_base_oid = repo.merge_base(head_commit.id(), remote_commit.id())?; @@ -280,7 +280,7 @@ fn find_merge_base_ref( "Found merge-base commit as base reference: {}", merge_base_sha ); - Ok(Some(merge_base_sha)) + Ok(merge_base_sha) } fn find_reference_url(repo: &str, repos: &[Repo]) -> Result> {