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