Skip to content

Commit 684caab

Browse files
committed
feat(cli): Add git inference for repo and branch in code-mappings upload
Automatically detect repository name and default branch from the local git repo when --repo or --default-branch are not provided. Respects SENTRY_VCS_REMOTE config, falling back to best-effort remote detection. Extract find_best_remote() into vcs.rs to deduplicate remote selection logic shared with git_repo_base_repo_name_preserve_case().
1 parent fb49d43 commit 684caab

File tree

3 files changed

+89
-10
lines changed

3 files changed

+89
-10
lines changed

src/commands/code_mappings/upload.rs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ use std::fs;
22

33
use anyhow::{bail, Context as _, Result};
44
use clap::{Arg, ArgMatches, Command};
5+
use log::debug;
56
use serde::{Deserialize, Serialize};
67

8+
use crate::config::Config;
9+
use crate::utils::vcs;
10+
711
#[derive(Debug, Deserialize, Serialize)]
812
#[serde(rename_all = "camelCase")]
913
struct CodeMapping {
@@ -30,8 +34,7 @@ pub fn make_command(command: Command) -> Command {
3034
Arg::new("default_branch")
3135
.long("default-branch")
3236
.value_name("BRANCH")
33-
.default_value("main")
34-
.help("The default branch name."),
37+
.help("The default branch name. Defaults to the git remote HEAD or 'main'."),
3538
)
3639
}
3740

@@ -56,7 +59,71 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
5659
}
5760
}
5861

62+
// Resolve repo name and default branch
63+
let explicit_repo = matches.get_one::<String>("repo");
64+
let explicit_branch = matches.get_one::<String>("default_branch");
65+
66+
let (repo_name, default_branch) = match (explicit_repo, explicit_branch) {
67+
(Some(r), Some(b)) => (r.to_owned(), b.to_owned()),
68+
_ => {
69+
let git_repo = git2::Repository::open_from_env().map_err(|e| {
70+
anyhow::anyhow!(
71+
"Could not open git repository: {e}. \
72+
Use --repo and --default-branch to specify manually."
73+
)
74+
})?;
75+
// Prefer explicit config (SENTRY_VCS_REMOTE / ini), then inspect
76+
// the repo for the best remote (upstream > origin > first).
77+
let config = Config::current();
78+
let configured_remote = config.get_cached_vcs_remote();
79+
let remote_name =
80+
if vcs::git_repo_remote_url(&git_repo, &configured_remote).is_ok() {
81+
debug!("Using configured VCS remote: {configured_remote}");
82+
configured_remote
83+
} else if let Some(best) = vcs::find_best_remote(&git_repo)? {
84+
debug!("Configured remote '{configured_remote}' not found, using: {best}");
85+
best
86+
} else {
87+
bail!(
88+
"No remotes found in the git repository. \
89+
Use --repo and --default-branch to specify manually."
90+
);
91+
};
92+
93+
let repo_name = match explicit_repo {
94+
Some(r) => r.to_owned(),
95+
None => {
96+
let remote_url = vcs::git_repo_remote_url(&git_repo, &remote_name)?;
97+
debug!("Found remote '{remote_name}': {remote_url}");
98+
let inferred = vcs::get_repo_from_remote(&remote_url);
99+
if inferred.is_empty() {
100+
bail!("Could not parse repository name from remote URL: {remote_url}");
101+
}
102+
println!("Inferred repository: {inferred}");
103+
inferred
104+
}
105+
};
106+
107+
let default_branch = match explicit_branch {
108+
Some(b) => b.to_owned(),
109+
None => {
110+
let inferred =
111+
vcs::git_repo_base_ref(&git_repo, &remote_name).unwrap_or_else(|e| {
112+
debug!("Could not infer default branch, falling back to 'main': {e}");
113+
"main".to_owned()
114+
});
115+
println!("Inferred default branch: {inferred}");
116+
inferred
117+
}
118+
};
119+
120+
(repo_name, default_branch)
121+
}
122+
};
123+
59124
println!("Found {} code mapping(s) in {path}", mappings.len());
125+
println!("Repository: {repo_name}");
126+
println!("Default branch: {default_branch}");
60127

61128
Ok(())
62129
}

src/utils/vcs.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,27 +301,39 @@ pub fn git_repo_base_ref(repo: &git2::Repository, remote_name: &str) -> Result<S
301301
})
302302
}
303303

304-
/// Like git_repo_base_repo_name but preserves the original case of the repository name.
305-
/// This is used specifically for build upload where case preservation is important.
306-
pub fn git_repo_base_repo_name_preserve_case(repo: &git2::Repository) -> Result<Option<String>> {
304+
/// Finds the best remote in a git repository.
305+
/// Prefers "upstream" if it exists, then "origin", otherwise uses the first remote.
306+
pub fn find_best_remote(repo: &git2::Repository) -> Result<Option<String>> {
307307
let remotes = repo.remotes()?;
308308
let remote_names: Vec<&str> = remotes.iter().flatten().collect();
309309

310310
if remote_names.is_empty() {
311-
warn!("No remotes found in repository");
312311
return Ok(None);
313312
}
314313

315-
// Prefer "upstream" if it exists, then "origin", otherwise use the first one
316-
let chosen_remote = if remote_names.contains(&"upstream") {
314+
let chosen = if remote_names.contains(&"upstream") {
317315
"upstream"
318316
} else if remote_names.contains(&"origin") {
319317
"origin"
320318
} else {
321319
remote_names[0]
322320
};
323321

324-
match git_repo_remote_url(repo, chosen_remote) {
322+
Ok(Some(chosen.to_owned()))
323+
}
324+
325+
/// Like git_repo_base_repo_name but preserves the original case of the repository name.
326+
/// This is used specifically for build upload where case preservation is important.
327+
pub fn git_repo_base_repo_name_preserve_case(repo: &git2::Repository) -> Result<Option<String>> {
328+
let chosen_remote = match find_best_remote(repo)? {
329+
Some(remote) => remote,
330+
None => {
331+
warn!("No remotes found in repository");
332+
return Ok(None);
333+
}
334+
};
335+
336+
match git_repo_remote_url(repo, &chosen_remote) {
325337
Ok(remote_url) => {
326338
debug!("Found remote '{chosen_remote}': {remote_url}");
327339
let repo_name = get_repo_from_remote_preserve_case(&remote_url);

tests/integration/_cases/code_mappings/code-mappings-upload-help.trycmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Arguments:
1111
Options:
1212
-o, --org <ORG> The organization ID or slug.
1313
--repo <REPO> The repository name (e.g. owner/repo). Defaults to the git remote.
14-
--default-branch <BRANCH> The default branch name. [default: main]
14+
--default-branch <BRANCH> The default branch name. Defaults to the git remote HEAD or 'main'.
1515
--header <KEY:VALUE> Custom headers that should be attached to all requests
1616
in key:value format.
1717
-p, --project <PROJECT> The project ID or slug.

0 commit comments

Comments
 (0)