use std::collections::BTreeSet;
use crate::config::ConfigSet;
use crate::error::Result;
use crate::fetch::Progress;
use crate::remote::{
HttpClientFactory, ListRefsOptions, Remote, RemoteError, RemoteRef, DEFAULT_REMOTE,
};
use crate::repo::Repository;
use crate::transfer::{FetchOptions, FetchOutcome, PushOptions, PushOutcome, PushRefSpec};
#[derive(Debug, Copy, Clone)]
pub struct RepositoryRemotes<'repo> {
repo: &'repo Repository,
}
impl<'repo> RepositoryRemotes<'repo> {
pub(crate) fn new(repo: &'repo Repository) -> Self {
Self { repo }
}
#[must_use]
pub fn repository(&self) -> &'repo Repository {
self.repo
}
pub fn list(&self) -> Result<Vec<String>> {
let config = self.repo.config()?;
Ok(configured_remote_names(config.as_ref()))
}
pub fn get(&self, name: &str) -> std::result::Result<Remote, RemoteError> {
let config = self.repo.config().map_err(RemoteError::Library)?;
Remote::from_config(config.as_ref(), name)
}
pub fn get_or_default(&self, name: &str) -> std::result::Result<Remote, RemoteError> {
let trimmed = name.trim();
if trimmed.is_empty() {
self.get(DEFAULT_REMOTE)
} else {
self.get(trimmed)
}
}
pub fn ls_remote(
&self,
remote: &Remote,
options: &ListRefsOptions,
http_factory: Option<&dyn HttpClientFactory>,
) -> std::result::Result<Vec<RemoteRef>, RemoteError> {
remote.list_refs(Some(self.repo), options, http_factory)
}
pub fn fetch(
&self,
remote: &Remote,
options: FetchOptions,
progress: &mut dyn Progress,
http_factory: Option<&dyn HttpClientFactory>,
) -> std::result::Result<FetchOutcome, RemoteError> {
remote.fetch(self.repo, options, progress, http_factory)
}
pub fn push(
&self,
remote: &Remote,
refs: &[PushRefSpec],
options: PushOptions,
progress: &mut dyn Progress,
http_factory: Option<&dyn HttpClientFactory>,
) -> std::result::Result<PushOutcome, RemoteError> {
remote.push(self.repo, refs, options, progress, http_factory)
}
}
#[must_use]
pub fn configured_remote_names(config: &ConfigSet) -> Vec<String> {
let mut names = BTreeSet::new();
for entry in config.entries() {
let Some(rest) = entry.key.strip_prefix("remote.") else {
continue;
};
let Some((name, key)) = rest.rsplit_once('.') else {
continue;
};
if key == "url" && !name.is_empty() {
names.insert(name.to_owned());
}
}
names.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::configured_remote_names;
use crate::config::{ConfigFile, ConfigScope, ConfigSet};
#[test]
fn configured_remote_names_parses_dotted_subsections() {
let file = ConfigFile::parse(
std::path::Path::new("cfg"),
r#"[remote "team.prod"]
url = /tmp/upstream.git
[remote "origin"]
url = /tmp/origin.git
"#,
ConfigScope::Local,
)
.expect("parse");
let mut set = ConfigSet::new();
set.merge(&file);
let names = configured_remote_names(&set);
assert!(names.contains(&"team.prod".to_owned()));
assert!(names.contains(&"origin".to_owned()));
}
}