diff --git a/src/commands/code_mappings/upload.rs b/src/commands/code_mappings/upload.rs index f0141c7d42..c15da6ca9c 100644 --- a/src/commands/code_mappings/upload.rs +++ b/src/commands/code_mappings/upload.rs @@ -2,8 +2,12 @@ use std::fs; use anyhow::{bail, Context as _, Result}; use clap::{Arg, ArgMatches, Command}; +use log::debug; use serde::{Deserialize, Serialize}; +use crate::config::Config; +use crate::utils::vcs; + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] struct CodeMapping { @@ -30,8 +34,7 @@ pub fn make_command(command: Command) -> Command { Arg::new("default_branch") .long("default-branch") .value_name("BRANCH") - .default_value("main") - .help("The default branch name."), + .help("The default branch name. Defaults to the git remote HEAD or 'main'."), ) } @@ -56,7 +59,70 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { } } + // Resolve repo name and default branch + let explicit_repo = matches.get_one::("repo"); + let explicit_branch = matches.get_one::("default_branch"); + + let (repo_name, default_branch) = match (explicit_repo, explicit_branch) { + (Some(r), Some(b)) => (r.to_owned(), b.to_owned()), + _ => { + let git_repo = git2::Repository::open_from_env().map_err(|e| { + anyhow::anyhow!( + "Could not open git repository: {e}. \ + Use --repo and --default-branch to specify manually." + ) + })?; + // Prefer explicit config (SENTRY_VCS_REMOTE / ini), then inspect + // the repo for the best remote (upstream > origin > first). + let config = Config::current(); + let configured_remote = config.get_cached_vcs_remote(); + let remote_name = if vcs::git_repo_remote_url(&git_repo, &configured_remote).is_ok() { + debug!("Using configured VCS remote: {configured_remote}"); + configured_remote + } else if let Some(best) = vcs::find_best_remote(&git_repo)? { + debug!("Configured remote '{configured_remote}' not found, using: {best}"); + best + } else { + bail!( + "No remotes found in the git repository. \ + Use --repo and --default-branch to specify manually." + ); + }; + + let repo_name = match explicit_repo { + Some(r) => r.to_owned(), + None => { + let remote_url = vcs::git_repo_remote_url(&git_repo, &remote_name)?; + debug!("Found remote '{remote_name}': {remote_url}"); + let inferred = vcs::get_repo_from_remote(&remote_url); + if inferred.is_empty() { + bail!("Could not parse repository name from remote URL: {remote_url}"); + } + println!("Inferred repository: {inferred}"); + inferred + } + }; + + let default_branch = match explicit_branch { + Some(b) => b.to_owned(), + None => { + let inferred = + vcs::git_repo_base_ref(&git_repo, &remote_name).unwrap_or_else(|e| { + debug!("Could not infer default branch, falling back to 'main': {e}"); + "main".to_owned() + }); + println!("Inferred default branch: {inferred}"); + inferred + } + }; + + (repo_name, default_branch) + } + }; + println!("Found {} code mapping(s) in {path}", mappings.len()); + println!("Repository: {repo_name}"); + println!("Default branch: {default_branch}"); Ok(()) } diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 948c35a0f9..2fd89b372d 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -301,19 +301,17 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result Result> { +/// Finds the best remote in a git repository. +/// Prefers "upstream" if it exists, then "origin", otherwise uses the first remote. +pub fn find_best_remote(repo: &git2::Repository) -> Result> { let remotes = repo.remotes()?; let remote_names: Vec<&str> = remotes.iter().flatten().collect(); if remote_names.is_empty() { - warn!("No remotes found in repository"); return Ok(None); } - // Prefer "upstream" if it exists, then "origin", otherwise use the first one - let chosen_remote = if remote_names.contains(&"upstream") { + let chosen = if remote_names.contains(&"upstream") { "upstream" } else if remote_names.contains(&"origin") { "origin" @@ -321,7 +319,21 @@ pub fn git_repo_base_repo_name_preserve_case(repo: &git2::Repository) -> Result< remote_names[0] }; - match git_repo_remote_url(repo, chosen_remote) { + Ok(Some(chosen.to_owned())) +} + +/// Like git_repo_base_repo_name but preserves the original case of the repository name. +/// This is used specifically for build upload where case preservation is important. +pub fn git_repo_base_repo_name_preserve_case(repo: &git2::Repository) -> Result> { + let chosen_remote = match find_best_remote(repo)? { + Some(remote) => remote, + None => { + warn!("No remotes found in repository"); + return Ok(None); + } + }; + + match git_repo_remote_url(repo, &chosen_remote) { Ok(remote_url) => { debug!("Found remote '{chosen_remote}': {remote_url}"); let repo_name = get_repo_from_remote_preserve_case(&remote_url); diff --git a/tests/integration/_cases/code_mappings/code-mappings-upload-help.trycmd b/tests/integration/_cases/code_mappings/code-mappings-upload-help.trycmd index cf46abe2c2..adcf2aa377 100644 --- a/tests/integration/_cases/code_mappings/code-mappings-upload-help.trycmd +++ b/tests/integration/_cases/code_mappings/code-mappings-upload-help.trycmd @@ -11,7 +11,7 @@ Arguments: Options: -o, --org The organization ID or slug. --repo The repository name (e.g. owner/repo). Defaults to the git remote. - --default-branch The default branch name. [default: main] + --default-branch The default branch name. Defaults to the git remote HEAD or 'main'. --header Custom headers that should be attached to all requests in key:value format. -p, --project The project ID or slug.