Skip to content

Commit 27a3b3a

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 dfeb139 commit 27a3b3a

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

@@ -57,7 +60,71 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
5760
}
5861
}
5962

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

62129
Ok(())
63130
}

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
@@ -13,7 +13,7 @@ Arguments:
1313
Options:
1414
-o, --org <ORG> The organization ID or slug.
1515
--repo <REPO> The repository name (e.g. owner/repo). Defaults to the git remote.
16-
--default-branch <BRANCH> The default branch name. [default: main]
16+
--default-branch <BRANCH> The default branch name. Defaults to the git remote HEAD or 'main'.
1717
--header <KEY:VALUE> Custom headers that should be attached to all requests
1818
in key:value format.
1919
-p, --project <PROJECT> The project ID or slug.

0 commit comments

Comments
 (0)