1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
#![allow(clippy::result_large_err)]
use std::convert::TryInto;
use crate::{bstr::BStr, config, remote, remote::find, Remote};
impl crate::Repository {
/// Create a new remote available at the given `url`.
///
/// It's configured to fetch included tags by default, similar to git.
/// See [`with_fetch_tags(…)`][Remote::with_fetch_tags()] for a way to change it.
pub fn remote_at<Url, E>(&self, url: Url) -> Result<Remote<'_>, remote::init::Error>
where
Url: TryInto<gix_url::Url, Error = E>,
gix_url::parse::Error: From<E>,
{
Remote::from_fetch_url(url, true, self)
}
/// Create a new remote available at the given `url` similarly to [`remote_at()`][crate::Repository::remote_at()],
/// but don't rewrite the url according to rewrite rules.
/// This eliminates a failure mode in case the rewritten URL is faulty, allowing to selectively [apply rewrite
/// rules][Remote::rewrite_urls()] later and do so non-destructively.
pub fn remote_at_without_url_rewrite<Url, E>(&self, url: Url) -> Result<Remote<'_>, remote::init::Error>
where
Url: TryInto<gix_url::Url, Error = E>,
gix_url::parse::Error: From<E>,
{
Remote::from_fetch_url(url, false, self)
}
/// Find the configured remote with the given `name_or_url` or report an error,
/// similar to [`try_find_remote(…)`][Self::try_find_remote()].
///
/// Note that we will obtain remotes only if we deem them [trustworthy][crate::open::Options::filter_config_section()].
pub fn find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Result<Remote<'_>, find::existing::Error> {
let name_or_url = name_or_url.into();
Ok(self
.try_find_remote(name_or_url)
.ok_or_else(|| find::existing::Error::NotFound {
name: name_or_url.into(),
})??)
}
/// Find the default remote as configured, or `None` if no such configuration could be found.
///
/// See [`remote_default_name()`](Self::remote_default_name()) for more information on the `direction` parameter.
pub fn find_default_remote(
&self,
direction: remote::Direction,
) -> Option<Result<Remote<'_>, find::existing::Error>> {
self.remote_default_name(direction)
.map(|name| self.find_remote(name.as_ref()))
}
/// Find the configured remote with the given `name_or_url` or return `None` if it doesn't exist,
/// for the purpose of fetching or pushing data.
///
/// There are various error kinds related to partial information or incorrectly formatted URLs or ref-specs.
/// Also note that the created `Remote` may have neither fetch nor push ref-specs set at all.
///
/// Note that ref-specs are de-duplicated right away which may change their order. This doesn't affect matching in any way
/// as negations/excludes are applied after includes.
///
/// We will only include information if we deem it [trustworthy][crate::open::Options::filter_config_section()].
pub fn try_find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Option<Result<Remote<'_>, find::Error>> {
self.try_find_remote_inner(name_or_url.into(), true)
}
/// This method emulate what `git fetch <remote>` does in order to obtain a remote to fetch from.
///
/// As such, with `name_or_url` being `Some`, it will:
///
/// * use `name_or_url` verbatim if it is a URL, creating a new remote in memory as needed.
/// * find the named remote if `name_or_url` is a remote name
///
/// If `name_or_url` is `None`:
///
/// * use the current `HEAD` branch to find a configured remote
/// * fall back to either a generally configured remote or the only configured remote.
///
/// Fail if no remote could be found despite all of the above.
pub fn find_fetch_remote(&self, name_or_url: Option<&BStr>) -> Result<Remote<'_>, find::for_fetch::Error> {
Ok(match name_or_url {
Some(name) => match self.try_find_remote(name).and_then(Result::ok) {
Some(remote) => remote,
None => self.remote_at(gix_url::parse(name)?)?,
},
None => self
.head()?
.into_remote(remote::Direction::Fetch)
.transpose()?
.map(Ok)
.or_else(|| self.find_default_remote(remote::Direction::Fetch))
.ok_or_else(|| find::for_fetch::Error::ExactlyOneRemoteNotAvailable)??,
})
}
/// Similar to [`try_find_remote()`][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid
/// as it skips rewriting them.
/// Use this in conjunction with [`Remote::rewrite_urls()`] to non-destructively apply the rules and keep the failed urls unchanged.
pub fn try_find_remote_without_url_rewrite<'a>(
&self,
name_or_url: impl Into<&'a BStr>,
) -> Option<Result<Remote<'_>, find::Error>> {
self.try_find_remote_inner(name_or_url.into(), false)
}
fn try_find_remote_inner<'a>(
&self,
name_or_url: impl Into<&'a BStr>,
rewrite_urls: bool,
) -> Option<Result<Remote<'_>, find::Error>> {
fn config_spec<T: config::tree::keys::Validate>(
specs: Vec<std::borrow::Cow<'_, BStr>>,
name_or_url: &BStr,
key: &'static config::tree::keys::Any<T>,
op: gix_refspec::parse::Operation,
) -> Result<Vec<gix_refspec::RefSpec>, find::Error> {
let kind = key.name;
specs
.into_iter()
.map(|spec| {
key.try_into_refspec(spec, op).map_err(|err| find::Error::RefSpec {
remote_name: name_or_url.into(),
kind,
source: err,
})
})
.collect::<Result<Vec<_>, _>>()
.map(|mut specs| {
specs.sort();
specs.dedup();
specs
})
}
let mut filter = self.filter_config_section();
let name_or_url = name_or_url.into();
let mut config_url = |key: &'static config::tree::keys::Url, kind: &'static str| {
self.config
.resolved
.string_filter("remote", Some(name_or_url), key.name, &mut filter)
.map(|url| {
key.try_into_url(url).map_err(|err| find::Error::Url {
kind,
remote_name: name_or_url.into(),
source: err,
})
})
};
let url = config_url(&config::tree::Remote::URL, "fetch");
let push_url = config_url(&config::tree::Remote::PUSH_URL, "push");
let config = &self.config.resolved;
let fetch_specs = config
.strings_filter("remote", Some(name_or_url), "fetch", &mut filter)
.map(|specs| {
config_spec(
specs,
name_or_url,
&config::tree::Remote::FETCH,
gix_refspec::parse::Operation::Fetch,
)
});
let push_specs = config
.strings_filter("remote", Some(name_or_url), "push", &mut filter)
.map(|specs| {
config_spec(
specs,
name_or_url,
&config::tree::Remote::PUSH,
gix_refspec::parse::Operation::Push,
)
});
let fetch_tags = config
.string_filter("remote", Some(name_or_url), "tagOpt", &mut filter)
.map(|value| {
config::tree::Remote::TAG_OPT
.try_into_tag_opt(value)
.map_err(Into::into)
});
let fetch_tags = match fetch_tags {
Some(Ok(v)) => v,
Some(Err(err)) => return Some(Err(err)),
None => Default::default(),
};
match (url, fetch_specs, push_url, push_specs) {
(None, None, None, None) => None,
(None, _, None, _) => Some(Err(find::Error::UrlMissing)),
(url, fetch_specs, push_url, push_specs) => {
let url = match url {
Some(Ok(v)) => Some(v),
Some(Err(err)) => return Some(Err(err)),
None => None,
};
let push_url = match push_url {
Some(Ok(v)) => Some(v),
Some(Err(err)) => return Some(Err(err)),
None => None,
};
let fetch_specs = match fetch_specs {
Some(Ok(v)) => v,
Some(Err(err)) => return Some(Err(err)),
None => Vec::new(),
};
let push_specs = match push_specs {
Some(Ok(v)) => v,
Some(Err(err)) => return Some(Err(err)),
None => Vec::new(),
};
Some(
Remote::from_preparsed_config(
Some(name_or_url.to_owned()),
url,
push_url,
fetch_specs,
push_specs,
rewrite_urls,
fetch_tags,
self,
)
.map_err(Into::into),
)
}
}
}
}