use super::{Direction, RefspecError, Refspecs};
use crate::Config;
#[derive(Debug, Clone)]
pub struct Remote {
name: Vec<u8>,
urls: Vec<Vec<u8>>,
push_urls: Vec<Vec<u8>>,
fetch: Refspecs,
push: Refspecs,
}
#[derive(Clone)]
pub struct RemoteUrls {
name: Vec<u8>,
urls: Vec<Vec<u8>>,
push_urls: Vec<Vec<u8>>,
}
impl std::fmt::Debug for RemoteUrls {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RemoteUrls")
.field("url_count", &self.urls.len())
.field("push_url_count", &self.push_urls.len())
.finish_non_exhaustive()
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RemoteError {
#[error("remote key {key}, occurrence {occurrence}, requires a value")]
MissingValue {
key: &'static str,
occurrence: usize,
},
#[error("remote key {key}, occurrence {occurrence}")]
Refspec {
key: &'static str,
occurrence: usize,
#[source]
source: RefspecError,
},
}
impl Remote {
pub fn names(config: &Config) -> Vec<&[u8]> {
RemoteUrls::names(config)
}
pub fn find(config: &Config, name: &[u8]) -> Result<Option<Self>, RemoteError> {
let Some(remote_urls) = RemoteUrls::find(config, name)? else {
return Ok(None);
};
Ok(Some(Self {
name: remote_urls.name,
urls: remote_urls.urls,
push_urls: remote_urls.push_urls,
fetch: refspecs(config, name, "fetch", Direction::Fetch)?,
push: refspecs(config, name, "push", Direction::Push)?,
}))
}
pub fn name(&self) -> &[u8] {
&self.name
}
pub fn urls(&self) -> &[Vec<u8>] {
&self.urls
}
pub fn fetch_url(&self) -> Option<&[u8]> {
self.urls.first().map(Vec::as_slice)
}
pub fn configured_push_urls(&self) -> &[Vec<u8>] {
&self.push_urls
}
pub fn push_urls(&self) -> &[Vec<u8>] {
if self.push_urls.is_empty() {
&self.urls
} else {
&self.push_urls
}
}
pub fn fetch_refspecs(&self) -> &Refspecs {
&self.fetch
}
pub fn push_refspecs(&self) -> &Refspecs {
&self.push
}
}
impl RemoteUrls {
pub fn names(config: &Config) -> Vec<&[u8]> {
let mut names = Vec::new();
let mut seen = std::collections::HashSet::new();
for entry in config.entries() {
if entry.section.eq_ignore_ascii_case(b"remote")
&& let Some(name) = entry.subsection.as_deref()
&& seen.insert(name)
{
names.push(name);
}
}
names
}
pub fn find(config: &Config, name: &[u8]) -> Result<Option<Self>, RemoteError> {
if !Self::names(config).contains(&name) {
return Ok(None);
}
Ok(Some(Self {
name: name.to_vec(),
urls: urls(config, name, "url")?,
push_urls: urls(config, name, "pushurl")?,
}))
}
pub fn name(&self) -> &[u8] {
&self.name
}
pub fn urls(&self) -> &[Vec<u8>] {
&self.urls
}
pub fn fetch_url(&self) -> Option<&[u8]> {
self.urls.first().map(Vec::as_slice)
}
pub fn configured_push_urls(&self) -> &[Vec<u8>] {
&self.push_urls
}
pub fn push_urls(&self) -> &[Vec<u8>] {
if self.push_urls.is_empty() {
&self.urls
} else {
&self.push_urls
}
}
}
fn urls(config: &Config, name: &[u8], key: &'static str) -> Result<Vec<Vec<u8>>, RemoteError> {
let mut urls = Vec::new();
for (index, value) in config.values("remote", Some(name), key).enumerate() {
let value = value.ok_or(RemoteError::MissingValue {
key,
occurrence: index + 1,
})?;
if value.is_empty() {
urls.clear();
} else {
urls.push(value.to_vec());
}
}
Ok(urls)
}
fn refspecs(
config: &Config,
name: &[u8],
key: &'static str,
direction: Direction,
) -> Result<Refspecs, RemoteError> {
let values = config
.values("remote", Some(name), key)
.enumerate()
.map(|(index, value)| {
value.ok_or(RemoteError::MissingValue {
key,
occurrence: index + 1,
})
})
.collect::<Result<Vec<_>, _>>()?;
Refspecs::parse(direction, values).map_err(|error| RemoteError::Refspec {
key,
occurrence: error.index + 1,
source: error.source,
})
}
#[cfg(test)]
#[path = "config_tests.rs"]
mod tests;