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
use gix_refspec::RefSpec;
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
use crate::types::RemoteDetached;
use crate::{Remote, bstr::BStr, remote};
/// Access
impl<'repo> Remote<'repo> {
/// Return the name of this remote or `None` if it wasn't persisted to disk yet.
pub fn name(&self) -> Option<&remote::Name<'static>> {
self.name.as_ref()
}
/// Return our repository reference.
pub fn repo(&self) -> &'repo crate::Repository {
self.repo
}
/// Return the set of ref-specs used for `direction`, which may be empty, in order of occurrence in the configuration.
pub fn refspecs(&self, direction: remote::Direction) -> &[RefSpec] {
match direction {
remote::Direction::Fetch => &self.fetch_specs,
remote::Direction::Push => &self.push_specs,
}
}
/// Return how we handle tags when fetching the remote.
pub fn fetch_tags(&self) -> remote::fetch::Tags {
self.fetch_tags
}
/// Return the first url used for the given `direction` with rewrites from `url.<base>.insteadOf|pushInsteadOf`, unless the instance
/// was created with one of the `_without_url_rewrite()` methods.
/// See [`urls()`](Self::urls()) for how rewrite rules differ between fetch URLs, explicit push URLs, and push fallbacks.
/// For pushing, this is the first `remote.<name>.pushUrl` or the first `remote.<name>.url` used for fetching, and for
/// fetching it's the first `remote.<name>.url`. Unlike `git remote get-url`, a missing fetch URL doesn't fall back to a
/// symbolic remote name, but a URL-shaped remote name may itself be used as the fallback URL.
/// Note that it's possible to only have the push url set, in which case there will be no way to fetch from the remote as
/// the push-url isn't used for that.
pub fn url(&self, direction: remote::Direction) -> Option<&gix_url::Url> {
self.urls(direction).next()
}
/// Return all urls used for the given `direction` with rewrites from `url.<base>.insteadOf|pushInsteadOf`, unless the
/// instance was created with one of the `_without_url_rewrite()` methods.
///
/// Fetch URLs are rewritten with `url.<base>.insteadOf`. Explicit `remote.<name>.pushUrl` values are also rewritten with
/// `insteadOf`, and `pushInsteadOf` is ignored for them. If no explicit push URL is configured, the fetch URLs are used
/// as push fallbacks: matching `pushInsteadOf` rules take precedence, with `insteadOf` used when none match.
///
/// Values are returned in configuration order.
pub fn urls(&self, direction: remote::Direction) -> impl Iterator<Item = &gix_url::Url> + '_ {
let (urls, aliases) = self.urls_and_aliases(direction);
debug_assert_eq!(
urls.len(),
aliases.len(),
"each URL should have a corresponding rewrite slot"
);
urls.iter()
.zip(aliases)
.map(|(url, alias)| alias.as_ref().unwrap_or(url))
}
fn urls_and_aliases(&self, direction: remote::Direction) -> (&[gix_url::Url], &[Option<gix_url::Url>]) {
match direction {
remote::Direction::Fetch => (&self.urls, &self.url_aliases),
remote::Direction::Push if self.push_urls.is_empty() => (&self.urls, &self.url_push_aliases),
remote::Direction::Push => (&self.push_urls, &self.push_url_aliases),
}
}
/// Return a clone of this remote without its repository reference.
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
pub(crate) fn detached(&self) -> RemoteDetached {
self.clone().into()
}
}
/// Access
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
impl RemoteDetached {
/// Return the name of this remote or `None` if it wasn't persisted to disk yet.
pub(crate) fn name(&self) -> Option<&remote::Name<'static>> {
self.name.as_ref()
}
/// Return the set of ref-specs used for fetching, which may be empty, in order of occurrence in the configuration.
pub(crate) fn fetch_refspecs(&self) -> &[RefSpec] {
&self.fetch_specs
}
}
/// Modification
impl Remote<'_> {
/// Re-read `url.<base>.insteadOf|pushInsteadOf` and recompute the effective URLs returned by [`url()`](Self::url()) and
/// [`urls()`](Self::urls()). This may be called repeatedly to refresh rewrite rules after configuration changes.
///
/// Every URL is attempted non-destructively: successful rewrites remain effective if another rewritten URL is malformed,
/// while a failed entry keeps using its original URL. The first error is returned in fetch, push-fallback, explicit-push
/// order. See [`urls()`](Self::urls()) for which rules apply to each category.
pub fn rewrite_urls(&mut self) -> Result<&mut Self, remote::init::Error> {
let (url_aliases, url_err) =
remote::init::rewrite_url_aliases_non_destructive(&self.repo.config, &self.urls, remote::Direction::Fetch);
self.url_aliases = url_aliases;
let url_push_err = if self.push_urls.is_empty() {
let (url_push_aliases, err) = remote::init::rewrite_url_aliases_with_fallback_non_destructive(
&self.repo.config,
&self.urls,
remote::Direction::Push,
remote::Direction::Fetch,
);
self.url_push_aliases = url_push_aliases;
err
} else {
self.url_push_aliases = vec![None; self.urls.len()];
None
};
let (push_url_aliases, push_url_err) = remote::init::rewrite_url_aliases_non_destructive_with_error_kind(
&self.repo.config,
&self.push_urls,
remote::Direction::Fetch,
remote::Direction::Push,
);
self.push_url_aliases = push_url_aliases;
url_err
.or(url_push_err)
.or(push_url_err)
.map(Err::<&mut Self, _>)
.transpose()?;
Ok(self)
}
/// Replace all currently set refspecs, typically from configuration, with the given `specs` for `direction`,
/// or `None` if one of the input specs could not be parsed.
pub fn replace_refspecs<Spec>(
&mut self,
specs: impl IntoIterator<Item = Spec>,
direction: remote::Direction,
) -> Result<(), gix_refspec::parse::Error>
where
Spec: AsRef<BStr>,
{
use remote::Direction::*;
let specs: Vec<_> = specs
.into_iter()
.map(|spec| {
gix_refspec::parse(
spec.as_ref(),
match direction {
Push => gix_refspec::parse::Operation::Push,
Fetch => gix_refspec::parse::Operation::Fetch,
},
)
.map(|url| url.to_owned())
})
.collect::<Result<_, _>>()?;
let dst = match direction {
Push => &mut self.push_specs,
Fetch => &mut self.fetch_specs,
};
*dst = specs;
Ok(())
}
}
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
impl From<Remote<'_>> for RemoteDetached {
fn from(
Remote {
name,
urls,
url_aliases,
url_push_aliases: _,
fetch_specs,
fetch_tags,
push_urls: _,
push_url_aliases: _,
push_specs: _,
repo: _,
}: Remote<'_>,
) -> Self {
RemoteDetached {
name,
urls,
url_aliases,
fetch_specs,
fetch_tags,
}
}
}