use anyhow::Context;
use super::{GitRemoteUrl, Repository};
impl Repository {
pub fn primary_remote(&self) -> anyhow::Result<String> {
self.cache
.primary_remote
.get_or_init(|| {
if let Ok(default_remote) = self.run_command(&["config", "checkout.defaultRemote"])
{
let default_remote = default_remote.trim();
if !default_remote.is_empty() && self.remote_has_url(default_remote) {
return Some(default_remote.to_string());
}
}
let output = self
.run_command(&["config", "--get-regexp", r"remote\..+\.url"])
.unwrap_or_default();
let first_remote = output.lines().find_map(|line| {
line.strip_prefix("remote.")
.and_then(|s| s.split_once(".url "))
.map(|(name, _)| name)
});
first_remote.map(|s| s.to_string())
})
.clone()
.ok_or_else(|| anyhow::anyhow!("No remotes configured"))
}
fn remote_has_url(&self, remote: &str) -> bool {
self.run_command(&["config", &format!("remote.{}.url", remote)])
.map(|url| !url.trim().is_empty())
.unwrap_or(false)
}
pub fn remote_url(&self, remote: &str) -> Option<String> {
self.run_command(&["config", &format!("remote.{}.url", remote)])
.ok()
.map(|url| url.trim().to_string())
.filter(|url| !url.is_empty())
}
pub fn effective_remote_url(&self, remote: &str) -> Option<String> {
self.cache
.effective_remote_urls
.entry(remote.to_string())
.or_insert_with(|| {
self.run_command(&["remote", "get-url", remote])
.ok()
.map(|url| url.trim().to_string())
.filter(|url| !url.is_empty())
})
.clone()
}
pub fn find_remote_for_repo(
&self,
host: Option<&str>,
owner: &str,
repo: &str,
) -> Option<String> {
let matches = |url: &str| -> bool {
let Some(parsed) = GitRemoteUrl::parse(url) else {
return false;
};
parsed.owner().eq_ignore_ascii_case(owner)
&& parsed.repo().eq_ignore_ascii_case(repo)
&& host.is_none_or(|h| parsed.host().eq_ignore_ascii_case(h))
};
for (remote_name, raw_url) in self.all_remote_urls() {
if matches(&raw_url) {
return Some(remote_name);
}
if let Some(effective_url) = self.effective_remote_url(&remote_name)
&& effective_url != raw_url
&& matches(&effective_url)
{
return Some(remote_name);
}
}
None
}
pub fn find_remote_by_url(&self, target_url: &str) -> Option<String> {
let parsed = GitRemoteUrl::parse(target_url)?;
self.find_remote_for_repo(Some(parsed.host()), parsed.owner(), parsed.repo())
}
pub fn all_remote_urls(&self) -> Vec<(String, String)> {
let output = match self.run_command(&["config", "--get-regexp", r"remote\..+\.url"]) {
Ok(output) => output,
Err(_) => return Vec::new(),
};
output
.lines()
.filter_map(|line| {
let rest = line.strip_prefix("remote.")?;
let (name, url) = rest.split_once(".url ")?;
Some((name.to_string(), url.to_string()))
})
.collect()
}
pub fn primary_remote_url(&self) -> Option<String> {
self.cache
.primary_remote_url
.get_or_init(|| {
self.primary_remote()
.ok()
.and_then(|remote| self.remote_url(&remote))
})
.clone()
}
pub fn project_identifier(&self) -> anyhow::Result<String> {
self.cache
.project_identifier
.get_or_try_init(|| {
if let Some(url) = self.primary_remote_url() {
if let Some(parsed) = GitRemoteUrl::parse(url.trim()) {
return Ok(parsed.project_identifier());
}
let url = url.strip_suffix(".git").unwrap_or(url.as_str());
return Ok(url.to_string());
}
let repo_root = self.repo_path()?;
let canonical =
dunce::canonicalize(repo_root).unwrap_or_else(|_| repo_root.to_path_buf());
let path_str = canonical
.to_str()
.context("Repository path is not valid UTF-8")?;
Ok(path_str.to_string())
})
.cloned()
}
pub fn url_template(&self) -> Option<String> {
self.load_project_config()
.ok()
.flatten()
.and_then(|config| config.list)
.and_then(|list| list.url)
}
pub fn is_remote_tracking_branch(&self, ref_name: &str) -> bool {
self.run_command(&[
"rev-parse",
"--verify",
&format!("refs/remotes/{}", ref_name),
])
.is_ok()
}
pub fn strip_remote_prefix(&self, ref_name: &str) -> Option<String> {
if !self.is_remote_tracking_branch(ref_name) {
return None;
}
let output = self.run_command(&["remote"]).ok()?;
output.lines().find_map(|remote| {
let prefix = format!("{}/", remote.trim());
ref_name
.strip_prefix(&prefix)
.filter(|branch| !branch.is_empty())
.map(|branch| branch.to_string())
})
}
}