Skip to content

Commit 9064d5e

Browse files
authored
feat(mobile-app): Add default vcs base_ref parsing for mobile-app subcommand (#2706)
Adds default handling of `head_ref` (branch name) to the `mobile-app upload` subcommand. This will leverage the `head.shorthand()` value if no value is provided by the user. Also handles the detached HEAD state with some logging that will require the user to specify using the command arguments.
1 parent 7f5cb5d commit 9064d5e

File tree

3 files changed

+91
-10
lines changed

3 files changed

+91
-10
lines changed

src/api/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2525,7 +2525,7 @@ struct LogsResponse {
25252525
data: Vec<LogEntry>,
25262526
}
25272527

2528-
/// VCS information for mobile app uploads
2528+
/// VCS information for build app uploads
25292529
#[derive(Debug)]
25302530
pub struct VcsInfo<'a> {
25312531
pub head_sha: Option<&'a str>,

src/commands/build/upload.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::utils::fs::TempDir;
2222
use crate::utils::fs::TempFile;
2323
use crate::utils::progress::ProgressBar;
2424
use crate::utils::vcs::{
25-
self, get_provider_from_remote, get_repo_from_remote, git_repo_remote_url,
25+
self, get_provider_from_remote, get_repo_from_remote, git_repo_head_ref, git_repo_remote_url,
2626
};
2727

2828
pub fn make_command(command: Command) -> Command {
@@ -106,12 +106,13 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
106106

107107
let cached_remote = config.get_cached_vcs_remote();
108108
// Try to open the git repository and find the remote, but handle errors gracefully.
109-
let (vcs_provider, head_repo_name) = {
109+
let (vcs_provider, head_repo_name, head_ref) = {
110110
// Try to open the repo and get the remote URL, but don't fail if not in a repo.
111111
let repo = git2::Repository::open_from_env().ok();
112-
let remote_url = repo.and_then(|repo| git_repo_remote_url(&repo, &cached_remote).ok());
112+
let repo_ref = repo.as_ref();
113+
let remote_url = repo_ref.and_then(|repo| git_repo_remote_url(repo, &cached_remote).ok());
113114

114-
let vcs_provider: Option<Cow<'_, str>> = matches
115+
let vcs_provider = matches
115116
.get_one("vcs_provider")
116117
.map(String::as_str)
117118
.map(Cow::Borrowed)
@@ -122,7 +123,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
122123
.map(Cow::Owned)
123124
});
124125

125-
let head_repo_name: Option<Cow<'_, str>> = matches
126+
let head_repo_name = matches
126127
.get_one("head_repo_name")
127128
.map(String::as_str)
128129
.map(Cow::Borrowed)
@@ -133,13 +134,37 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
133134
.map(Cow::Owned)
134135
});
135136

136-
(vcs_provider, head_repo_name)
137+
let head_ref = matches
138+
.get_one("head_ref")
139+
.map(String::as_str)
140+
.map(Cow::Borrowed)
141+
.or_else(|| {
142+
// Try to get the current ref from the VCS if not provided
143+
// Note: git_repo_head_ref will return an error for detached HEAD states,
144+
// which the error handling converts to None - this prevents sending "HEAD" as a branch name
145+
// In that case, the user will need to provide a valid branch name.
146+
repo_ref
147+
.and_then(|r| match git_repo_head_ref(r) {
148+
Ok(ref_name) => {
149+
debug!("Found current branch reference: {}", ref_name);
150+
Some(ref_name)
151+
}
152+
Err(e) => {
153+
debug!(
154+
"No valid branch reference found (likely detached HEAD): {}",
155+
e
156+
);
157+
None
158+
}
159+
})
160+
.map(Cow::Owned)
161+
});
162+
163+
(vcs_provider, head_repo_name, head_ref)
137164
};
138165

139166
let base_repo_name = matches.get_one("base_repo_name").map(String::as_str);
140-
141167
let base_sha = matches.get_one("base_sha").map(String::as_str);
142-
let head_ref = matches.get_one("head_ref").map(String::as_str);
143168
let base_ref = matches.get_one("base_ref").map(String::as_str);
144169
let pr_number = matches.get_one::<u32>("pr_number");
145170

@@ -203,7 +228,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
203228
vcs_provider: vcs_provider.as_deref(),
204229
head_repo_name: head_repo_name.as_deref(),
205230
base_repo_name,
206-
head_ref,
231+
head_ref: head_ref.as_deref(),
207232
base_ref,
208233
pr_number,
209234
};

src/utils/vcs.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,23 @@ pub fn git_repo_remote_url(
232232
.ok_or_else(|| git2::Error::from_str("No remote URL found"))
233233
}
234234

235+
pub fn git_repo_head_ref(repo: &git2::Repository) -> Result<String> {
236+
let head = repo.head()?;
237+
238+
// Only return a reference name if we're not in a detached HEAD state
239+
// In detached HEAD state, head.shorthand() returns "HEAD" which is not a valid branch name
240+
if head.is_branch() {
241+
head.shorthand()
242+
.map(|s| s.to_owned())
243+
.ok_or_else(|| anyhow::anyhow!("No HEAD reference found"))
244+
} else {
245+
// In detached HEAD state, return an error to indicate no valid branch reference
246+
Err(anyhow::anyhow!(
247+
"HEAD is detached - no branch reference available"
248+
))
249+
}
250+
}
251+
235252
fn find_reference_url(repo: &str, repos: &[Repo]) -> Result<Option<String>> {
236253
let mut non_git = false;
237254
for configured_repo in repos {
@@ -881,6 +898,14 @@ fn git_initialize_repo() -> TempDir {
881898
.wait()
882899
.expect("Failed to wait on `git init`.");
883900

901+
Command::new("git")
902+
.args(["branch", "-M", "main"])
903+
.current_dir(&dir)
904+
.spawn()
905+
.expect("Failed to execute `git branch`.")
906+
.wait()
907+
.expect("Failed to wait on `git branch`.");
908+
884909
Command::new("git")
885910
.args(["config", "--local", "user.name", "test"])
886911
.current_dir(&dir)
@@ -1188,3 +1213,34 @@ fn test_generate_patch_ignore_missing() {
11881213
".*.timestamp" => "[timestamp]"
11891214
});
11901215
}
1216+
1217+
#[test]
1218+
fn test_git_repo_head_ref() {
1219+
let dir = git_initialize_repo();
1220+
1221+
// Create initial commit
1222+
git_create_commit(
1223+
dir.path(),
1224+
"foo.js",
1225+
b"console.log(\"Hello, world!\");",
1226+
"\"initial commit\"",
1227+
);
1228+
1229+
let repo = git2::Repository::open(dir.path()).expect("Failed");
1230+
1231+
// Test on a branch (should succeed)
1232+
let head_ref = git_repo_head_ref(&repo).expect("Should get branch reference");
1233+
assert_eq!(head_ref, "main");
1234+
1235+
// Test in detached HEAD state (should fail)
1236+
let head_commit = repo.head().unwrap().target().unwrap();
1237+
repo.set_head_detached(head_commit)
1238+
.expect("Failed to detach HEAD");
1239+
1240+
let head_ref_result = git_repo_head_ref(&repo);
1241+
assert!(head_ref_result.is_err());
1242+
assert_eq!(
1243+
head_ref_result.unwrap_err().to_string(),
1244+
"HEAD is detached - no branch reference available"
1245+
);
1246+
}

0 commit comments

Comments
 (0)