gix 0.89.0

Interact with git repositories just like git would
Documentation
use gix_error::ResultExt;

use gix_refspec::RefSpec;

use crate::{Error, Remote, Repository, Result, config, remote};

use crate::bstr::BString;

type UrlAliases = Vec<Option<gix_url::Url>>;
type UrlRewriteAliases = (UrlAliases, UrlAliases, UrlAliases);

/// Initialization
impl<'repo> Remote<'repo> {
    #[expect(clippy::too_many_arguments)]
    pub(crate) fn from_preparsed_config(
        name_or_url: Option<BString>,
        urls: Vec<gix_url::Url>,
        push_urls: Vec<gix_url::Url>,
        fetch_specs: Vec<RefSpec>,
        push_specs: Vec<RefSpec>,
        should_rewrite_urls: bool,
        fetch_tags: remote::fetch::Tags,
        repo: &'repo Repository,
    ) -> Result<Self> {
        let (url_aliases, url_push_aliases, push_url_aliases) = if should_rewrite_urls {
            rewrite_urls(&repo.config, &urls, &push_urls)
        } else {
            Ok((
                vec![None; urls.len()],
                vec![None; urls.len()],
                vec![None; push_urls.len()],
            ))
        }?;
        Ok(Remote {
            name: name_or_url.map(Into::into),
            urls,
            url_aliases,
            url_push_aliases,
            push_urls,
            push_url_aliases,
            fetch_specs,
            push_specs,
            fetch_tags,
            repo,
        })
    }

    pub(crate) fn from_fetch_url<Url, E>(url: Url, should_rewrite_urls: bool, repo: &'repo Repository) -> Result<Self>
    where
        Url: TryInto<gix_url::Url, Error = E>,
        E: std::error::Error + Send + Sync + 'static,
    {
        Self::from_fetch_url_inner(url.try_into().or_error()?, should_rewrite_urls, repo)
    }

    fn from_fetch_url_inner(url: gix_url::Url, should_rewrite_urls: bool, repo: &'repo Repository) -> Result<Self> {
        let urls = vec![url];
        let (url_aliases, url_push_aliases, _) = if should_rewrite_urls {
            rewrite_urls(&repo.config, &urls, &[])
        } else {
            Ok((vec![None; urls.len()], vec![None; urls.len()], Vec::new()))
        }?;
        Ok(Remote {
            name: None,
            urls,
            url_aliases,
            url_push_aliases,
            push_urls: Vec::new(),
            push_url_aliases: Vec::new(),
            fetch_specs: Vec::new(),
            push_specs: Vec::new(),
            fetch_tags: Default::default(),
            repo,
        })
    }
}

pub(crate) fn rewrite_url(
    config: &config::Cache,
    url: &gix_url::Url,
    direction: remote::Direction,
    error_kind: remote::Direction,
) -> Result<Option<gix_url::Url>> {
    config
        .url_rewrite()
        .longest(url, direction)
        .map(|url| {
            gix_url::parse(&url).or_raise(|| {
                let kind = match error_kind {
                    remote::Direction::Fetch => "fetch",
                    remote::Direction::Push => "push",
                };
                gix_error::message!("The rewritten {kind} url {url:?} failed to parse")
            })
        })
        .transpose()
}

pub(crate) fn rewrite_url_aliases(
    config: &config::Cache,
    urls: &[gix_url::Url],
    direction: remote::Direction,
) -> Result<Vec<Option<gix_url::Url>>> {
    rewrite_url_aliases_with_error_kind(config, urls, direction, direction)
}

pub(crate) fn rewrite_url_aliases_with_error_kind(
    config: &config::Cache,
    urls: &[gix_url::Url],
    direction: remote::Direction,
    error_kind: remote::Direction,
) -> Result<Vec<Option<gix_url::Url>>> {
    urls.iter()
        .map(|url| rewrite_url(config, url, direction, error_kind))
        .collect()
}

pub(crate) fn rewrite_url_aliases_non_destructive(
    config: &config::Cache,
    urls: &[gix_url::Url],
    direction: remote::Direction,
) -> (Vec<Option<gix_url::Url>>, Option<Error>) {
    rewrite_url_aliases_non_destructive_with_error_kind(config, urls, direction, direction)
}

pub(crate) fn rewrite_url_aliases_non_destructive_with_error_kind(
    config: &config::Cache,
    urls: &[gix_url::Url],
    direction: remote::Direction,
    error_kind: remote::Direction,
) -> (Vec<Option<gix_url::Url>>, Option<Error>) {
    let mut first_error = None;
    let aliases = urls
        .iter()
        .map(|url| match rewrite_url(config, url, direction, error_kind) {
            Ok(alias) => alias,
            Err(err) => {
                first_error.get_or_insert(err);
                None
            }
        })
        .collect();
    (aliases, first_error)
}

pub(crate) fn rewrite_url_aliases_with_fallback_non_destructive(
    config: &config::Cache,
    urls: &[gix_url::Url],
    direction: remote::Direction,
    fallback: remote::Direction,
) -> (Vec<Option<gix_url::Url>>, Option<Error>) {
    let mut first_error = None;
    let aliases = urls
        .iter()
        .map(|url| match rewrite_url(config, url, direction, direction) {
            Ok(Some(alias)) => Some(alias),
            Ok(None) => match rewrite_url(config, url, fallback, direction) {
                Ok(alias) => alias,
                Err(err) => {
                    first_error.get_or_insert(err);
                    None
                }
            },
            Err(err) => {
                first_error.get_or_insert(err);
                None
            }
        })
        .collect();
    (aliases, first_error)
}

pub(crate) fn rewrite_urls(
    config: &config::Cache,
    urls: &[gix_url::Url],
    push_urls: &[gix_url::Url],
) -> Result<UrlRewriteAliases> {
    let url_aliases = rewrite_url_aliases(config, urls, remote::Direction::Fetch)?;
    let url_push_aliases = if push_urls.is_empty() {
        rewrite_url_aliases_with_fallback_non_destructive(
            config,
            urls,
            remote::Direction::Push,
            remote::Direction::Fetch,
        )
        .0
    } else {
        vec![None; urls.len()]
    };
    let push_url_aliases =
        rewrite_url_aliases_with_error_kind(config, push_urls, remote::Direction::Fetch, remote::Direction::Push)?;

    Ok((url_aliases, url_push_aliases, push_url_aliases))
}