diff --git a/src/git/repository/mod.rs b/src/git/repository/mod.rs index 3511179c1..ab45cf455 100644 --- a/src/git/repository/mod.rs +++ b/src/git/repository/mod.rs @@ -70,8 +70,8 @@ struct RepoCache { is_bare: OnceCell, /// Default branch (main, master, etc.) default_branch: OnceCell, - /// Primary remote name (usually "origin") - primary_remote: OnceCell, + /// Primary remote name (None if no remotes configured) + primary_remote: OnceCell>, /// Project identifier derived from remote URL project_identifier: OnceCell, /// Base path for worktrees (repo root for normal repos, bare repo path for bare) @@ -197,14 +197,14 @@ impl Repository { /// Uses the following strategy: /// 1. Use git's [`checkout.defaultRemote`][1] config if set and has a URL /// 2. Otherwise, get the first remote with a configured URL - /// 3. Fall back to "origin" if no remotes exist + /// 3. Return error if no remotes exist /// /// Result is cached in the shared repo cache (shared across all worktrees). /// /// [1]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-checkoutdefaultRemote pub fn primary_remote(&self) -> anyhow::Result { let cache = get_cache(self.compute_git_common_dir()?); - Ok(cache + cache .primary_remote .get_or_init(|| { // Check git's checkout.defaultRemote config @@ -212,7 +212,7 @@ impl Repository { { let default_remote = default_remote.trim(); if !default_remote.is_empty() && self.remote_has_url(default_remote) { - return default_remote.to_string(); + return Some(default_remote.to_string()); } } @@ -230,9 +230,10 @@ impl Repository { .map(|(name, _)| name) }); - first_remote.unwrap_or("origin").to_string() + first_remote.map(|s| s.to_string()) }) - .clone()) + .clone() + .ok_or_else(|| anyhow::anyhow!("No remotes configured")) } /// Check if a remote has a URL configured. @@ -264,8 +265,10 @@ impl Repository { return Ok(true); } - // Try remote branch - let remote = self.primary_remote()?; + // Try remote branch (if remotes exist) + let Ok(remote) = self.primary_remote() else { + return Ok(false); + }; Ok(self .run_command(&[ "rev-parse", @@ -1475,42 +1478,46 @@ impl Repository { let local_branch_names: HashSet = local_branches.iter().map(|(n, _)| n.clone()).collect(); - // Get remote branches with timestamps - let remote = self.primary_remote().unwrap_or_else(|_| "origin".into()); - let remote_ref_path = format!("refs/remotes/{}/", remote); - let remote_prefix = format!("{}/", remote); - - let remote_output = self.run_command(&[ - "for-each-ref", - "--sort=-committerdate", - "--format=%(refname:lstrip=2)\t%(committerdate:unix)", - &remote_ref_path, - ])?; - - let remote_head = format!("{}/HEAD", remote); - let remote_branches: Vec<(String, String, i64)> = remote_output - .lines() - .filter_map(|line| { - let parts: Vec<&str> = line.split('\t').collect(); - if parts.len() == 2 { - let full_name = parts[0]; - // Skip /HEAD - if full_name == remote_head { - return None; - } - // Strip remote prefix to get local name - let local_name = full_name.strip_prefix(&remote_prefix)?; - // Skip if local branch exists (user should use local) - if local_branch_names.contains(local_name) { - return None; + // Get remote branches with timestamps (if remotes exist) + let remote_branches: Vec<(String, String, i64)> = if let Ok(remote) = self.primary_remote() + { + let remote_ref_path = format!("refs/remotes/{}/", remote); + let remote_prefix = format!("{}/", remote); + + let remote_output = self.run_command(&[ + "for-each-ref", + "--sort=-committerdate", + "--format=%(refname:lstrip=2)\t%(committerdate:unix)", + &remote_ref_path, + ])?; + + let remote_head = format!("{}/HEAD", remote); + remote_output + .lines() + .filter_map(|line| { + let parts: Vec<&str> = line.split('\t').collect(); + if parts.len() == 2 { + let full_name = parts[0]; + // Skip /HEAD + if full_name == remote_head { + return None; + } + // Strip remote prefix to get local name + let local_name = full_name.strip_prefix(&remote_prefix)?; + // Skip if local branch exists (user should use local) + if local_branch_names.contains(local_name) { + return None; + } + let timestamp = parts[1].parse().unwrap_or(0); + Some((local_name.to_string(), remote.to_string(), timestamp)) + } else { + None } - let timestamp = parts[1].parse().unwrap_or(0); - Some((local_name.to_string(), remote.to_string(), timestamp)) - } else { - None - } - }) - .collect(); + }) + .collect() + } else { + Vec::new() + }; // Build result: worktrees first, then local, then remote let mut result = Vec::new(); @@ -1841,9 +1848,9 @@ impl Repository { .project_identifier .get_or_try_init(|| { // Try to get the remote URL first - let remote = self.primary_remote()?; - - if let Some(url) = self.remote_url(&remote) { + if let Ok(remote) = self.primary_remote() + && let Some(url) = self.remote_url(&remote) + { if let Some(parsed) = GitRemoteUrl::parse(url.trim()) { return Ok(parsed.project_identifier()); }