use crate::git::GitRemoteUrl;
use super::Repository;
#[derive(Debug)]
#[must_use]
pub struct Branch<'a> {
pub(super) repo: &'a Repository,
pub(super) name: String,
}
impl<'a> Branch<'a> {
pub fn name(&self) -> &str {
&self.name
}
pub fn exists_locally(&self) -> anyhow::Result<bool> {
Ok(self
.repo
.run_command(&[
"rev-parse",
"--verify",
&format!("refs/heads/{}", self.name),
])
.is_ok())
}
pub fn exists(&self) -> anyhow::Result<bool> {
if self.exists_locally()? {
return Ok(true);
}
Ok(!self.remotes()?.is_empty())
}
pub fn remotes(&self) -> anyhow::Result<Vec<String>> {
let output = self.repo.run_command(&[
"for-each-ref",
"--format=%(refname:strip=2)",
&format!("refs/remotes/*/{}", self.name),
])?;
let suffix = format!("/{}", self.name);
let remotes: Vec<String> = output
.lines()
.filter_map(|line| {
let line = line.trim();
line.strip_suffix(&suffix).map(String::from)
})
.collect();
Ok(remotes)
}
pub fn upstream(&self) -> anyhow::Result<Option<String>> {
let upstreams = self
.repo
.cache
.upstreams
.get_or_try_init(|| self.repo.fetch_all_upstreams())?;
Ok(upstreams.get(&self.name).cloned().unwrap_or(None))
}
pub fn upstream_single(&self) -> anyhow::Result<Option<String>> {
let output = self.repo.run_command(&[
"for-each-ref",
"--format=%(upstream:short)\t%(upstream:track)",
&format!("refs/heads/{}", self.name),
])?;
let Some(line) = output.lines().next() else {
return Ok(None);
};
let (upstream, track) = line
.split_once('\t')
.expect("for-each-ref emits a tab between the two format fields");
if upstream.is_empty() || track == "[gone]" {
Ok(None)
} else {
Ok(Some(upstream.to_string()))
}
}
pub fn unset_upstream(&self) -> anyhow::Result<()> {
self.repo
.run_command(&["branch", "--unset-upstream", &self.name])?;
Ok(())
}
pub fn push_remote(&self) -> Option<String> {
let push_ref = self
.repo
.run_command(&[
"rev-parse",
"--abbrev-ref",
&format!("{}@{{push}}", self.name),
])
.ok()?;
let remote = push_ref.trim().split('/').next()?;
(!remote.is_empty()).then(|| remote.to_string())
}
fn push_remote_url(&self) -> Option<String> {
let push_remote = self
.repo
.run_command(&[
"for-each-ref",
"--format=%(push:remotename)",
&format!("refs/heads/{}", self.name),
])
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())?;
if push_remote.contains("://") || push_remote.starts_with("git@") {
Some(push_remote)
} else {
self.repo.effective_remote_url(&push_remote)
}
}
pub fn github_push_url(&self) -> Option<String> {
let url = self.push_remote_url()?;
let parsed = GitRemoteUrl::parse(&url)?;
parsed.is_github().then_some(url)
}
}