Skip to content
Draft
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
70 changes: 68 additions & 2 deletions src/commands/code_mappings/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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'."),
)
}

Expand All @@ -56,7 +59,70 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
}
}

// Resolve repo name and default branch
let explicit_repo = matches.get_one::<String>("repo");
let explicit_branch = matches.get_one::<String>("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(())
}
26 changes: 19 additions & 7 deletions src/utils/vcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,27 +301,39 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result<S
})
}

/// 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<Option<String>> {
/// 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<Option<String>> {
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"
} else {
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<Option<String>> {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Arguments:
Options:
-o, --org <ORG> The organization ID or slug.
--repo <REPO> The repository name (e.g. owner/repo). Defaults to the git remote.
--default-branch <BRANCH> The default branch name. [default: main]
--default-branch <BRANCH> The default branch name. Defaults to the git remote HEAD or 'main'.
--header <KEY:VALUE> Custom headers that should be attached to all requests
in key:value format.
-p, --project <PROJECT> The project ID or slug.
Expand Down
Loading