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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
use std::{borrow::Cow, collections::BTreeSet};
use gix_ref::{FullName, FullNameRef};
use crate::{
bstr::BStr,
config::{
cache::util::ApplyLeniencyDefault,
tree::{Branch, Push},
},
push, remote,
repository::{
branch_remote_ref_name, branch_remote_tracking_ref_name, upstream_branch_and_remote_name_for_tracking_branch,
},
};
/// Query configuration related to branches.
impl crate::Repository {
/// Return a set of unique short branch names for which custom configuration exists in the configuration,
/// if we deem them [trustworthy][crate::open::Options::filter_config_section()].
///
/// ### Note
///
/// Branch names that have illformed UTF-8 will silently be skipped.
pub fn branch_names(&self) -> BTreeSet<&str> {
self.subsection_str_names_of("branch")
}
/// Returns the validated reference name of the upstream branch on the remote associated with the given `name`,
/// which will be used when *merging*.
/// The returned value corresponds to the `branch.<short_branch_name>.merge` configuration key for [`remote::Direction::Fetch`].
/// For the [push direction](`remote::Direction::Push`) the Git configuration is used for a variety of different outcomes,
/// similar to what would happen when running `git push <name>`.
///
/// Returns `None` if there is nothing configured, or if no remote or remote ref is configured.
///
/// ### Note
///
/// The returned name refers to what Git calls upstream branch (as opposed to upstream *tracking* branch).
/// The value is also fast to retrieve compared to its tracking branch.
///
/// See also [`Reference::remote_ref_name()`](crate::Reference::remote_ref_name()).
#[doc(alias = "branch_upstream_name", alias = "git2")]
pub fn branch_remote_ref_name(
&self,
name: &FullNameRef,
direction: remote::Direction,
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_ref_name::Error>> {
match direction {
remote::Direction::Fetch => {
let short_name = name.shorten();
self.config
.resolved
.string_by("branch", Some(short_name), Branch::MERGE.name)
.map(|name| {
if name.starts_with(b"refs/") {
crate::config::tree::branch::Merge::try_into_fullrefname(name)
} else {
gix_ref::Category::LocalBranch
.to_full_name(name.as_ref())
.map(Cow::Owned)
}
.map_err(Into::into)
})
}
remote::Direction::Push => {
let remote = match self.branch_remote(name.shorten(), direction)? {
Ok(r) => r,
Err(err) => return Some(Err(err.into())),
};
if remote.push_specs.is_empty() {
let push_default =
match self
.config
.resolved
.string(Push::DEFAULT)
.map_or(Ok(Default::default()), |v| {
Push::DEFAULT
.try_into_default(v)
.with_lenient_default(self.config.lenient_config)
}) {
Ok(v) => v,
Err(err) => return Some(Err(err.into())),
};
match push_default {
push::Default::Nothing => None,
push::Default::Current | push::Default::Matching => Some(Ok(Cow::Owned(name.to_owned()))),
push::Default::Upstream => self.branch_remote_ref_name(name, remote::Direction::Fetch),
push::Default::Simple => match self.branch_remote_ref_name(name, remote::Direction::Fetch)? {
Ok(fetch_ref) if fetch_ref.as_ref() == name => Some(Ok(fetch_ref)),
Err(err) => Some(Err(err)),
Ok(_different_fetch_ref) => None,
},
}
} else {
matching_remote(name, remote.push_specs.iter(), self.object_hash())
.map(|res| res.map_err(Into::into))
}
}
}
}
/// Return the validated name of the reference that tracks the corresponding reference of `name` on the remote for
/// `direction`. Note that a branch with that name might not actually exist.
///
/// * with `remote` being [remote::Direction::Fetch], we return the tracking branch that is on the destination
/// side of a `src:dest` refspec. For instance, with `name` being `main` and the default refspec
/// `refs/heads/*:refs/remotes/origin/*`, `refs/heads/main` would match and produce `refs/remotes/origin/main`.
/// * with `remote` being [remote::Direction::Push], we return the tracking branch that corresponds to the remote
/// branch that we would push to. For instance, with `name` being `main` and no setup at all, we
/// would push to `refs/heads/main` on the remote. And that one would be fetched matching the
/// `refs/heads/*:refs/remotes/origin/*` fetch refspec, hence `refs/remotes/origin/main` is returned.
/// Note that `push` refspecs can be used to map `main` to `other` (using a push refspec `refs/heads/main:refs/heads/other`),
/// which would then lead to `refs/remotes/origin/other` to be returned instead.
///
/// Note that if there is an ambiguity, that is if `name` maps to multiple tracking branches, the first matching mapping
/// is returned, according to the order in which the fetch or push refspecs occur in the configuration file.
///
/// See also [`Reference::remote_tracking_ref_name()`](crate::Reference::remote_tracking_ref_name()).
#[doc(alias = "branch_upstream_name", alias = "git2")]
pub fn branch_remote_tracking_ref_name(
&self,
name: &FullNameRef,
direction: remote::Direction,
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_tracking_ref_name::Error>> {
let remote_ref = match self.branch_remote_ref_name(name, direction)? {
Ok(r) => r,
Err(err) => return Some(Err(err.into())),
};
let remote = match self.branch_remote(name.shorten(), direction)? {
Ok(r) => r,
Err(err) => return Some(Err(err.into())),
};
if remote.fetch_specs.is_empty() {
return None;
}
matching_remote(remote_ref.as_ref(), remote.fetch_specs.iter(), self.object_hash())
.map(|res| res.map_err(Into::into))
}
/// Given a local `tracking_branch` name, find the remote that maps to it along with the name of the branch on
/// the side of the remote, also called upstream branch.
///
/// Return `Ok(None)` if there is no remote with fetch-refspecs that would match `tracking_branch` on the right-hand side,
/// or `Err` if the matches were ambiguous.
///
/// ### Limitations
///
/// A single valid mapping is required as fine-grained matching isn't implemented yet. This means that
pub fn upstream_branch_and_remote_for_tracking_branch(
&self,
tracking_branch: &FullNameRef,
) -> Result<Option<(FullName, crate::Remote<'_>)>, upstream_branch_and_remote_name_for_tracking_branch::Error> {
use upstream_branch_and_remote_name_for_tracking_branch::Error;
if tracking_branch.category() != Some(gix_ref::Category::RemoteBranch) {
return Err(Error::BranchCategory {
full_name: tracking_branch.to_owned(),
});
}
let null = self.object_hash().null();
let item_to_search = gix_refspec::match_group::Item {
full_ref_name: tracking_branch.as_bstr(),
target: &null,
object: None,
};
let mut candidates = Vec::new();
let mut ambiguous_remotes = Vec::new();
for remote_name in self.remote_names() {
let remote = self.find_remote(remote_name.as_ref())?;
let match_group = gix_refspec::MatchGroup::from_fetch_specs(
remote
.refspecs(remote::Direction::Fetch)
.iter()
.map(|spec| spec.to_ref()),
);
let out = match_group.match_rhs(Some(item_to_search).into_iter());
match &out.mappings[..] {
[] => {}
[one] => candidates.push((remote.clone(), one.lhs.clone().into_owned())),
[..] => ambiguous_remotes.push(remote),
}
}
if candidates.len() == 1 {
let (remote, candidate) = candidates.pop().expect("just checked for one entry");
let upstream_branch = match candidate {
gix_refspec::match_group::SourceRef::FullName(name) => gix_ref::FullName::try_from(name.into_owned())?,
gix_refspec::match_group::SourceRef::ObjectId(_) => {
unreachable!("Such a reverse mapping isn't ever produced")
}
};
return Ok(Some((upstream_branch, remote)));
}
if ambiguous_remotes.len() + candidates.len() > 1 {
return Err(Error::AmbiguousRemotes {
remotes: ambiguous_remotes
.into_iter()
.map(|r| r.name)
.chain(candidates.into_iter().map(|(r, _)| r.name))
.flatten()
.collect(),
});
}
Ok(None)
}
/// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
/// typically `main` instead of `refs/heads/main`.
/// In some cases, the returned name will be an URL.
/// Returns `None` if the remote was not found or if the name contained illformed UTF-8.
///
/// * if `direction` is [remote::Direction::Fetch], we will query the `branch.<short_name>.remote` configuration.
/// * if `direction` is [remote::Direction::Push], the push remote will be queried by means of `branch.<short_name>.pushRemote`
/// or `remote.pushDefault` as fallback.
///
/// See also [`Reference::remote_name()`](crate::Reference::remote_name()) for a more typesafe version
/// to be used when a `Reference` is available.
///
/// `short_branch_name` can typically be obtained by [shortening a full branch name](FullNameRef::shorten()).
#[doc(alias = "branch_upstream_remote", alias = "git2")]
pub fn branch_remote_name<'a>(
&self,
short_branch_name: impl Into<&'a BStr>,
direction: remote::Direction,
) -> Option<remote::Name<'_>> {
let name = short_branch_name.into();
let config = &self.config.resolved;
(direction == remote::Direction::Push)
.then(|| {
config
.string_by("branch", Some(name), Branch::PUSH_REMOTE.name)
.or_else(|| config.string(crate::config::tree::Remote::PUSH_DEFAULT))
})
.flatten()
.or_else(|| config.string_by("branch", Some(name), Branch::REMOTE.name))
.and_then(|name| name.try_into().ok())
}
/// Like [`branch_remote_name(…)`](Self::branch_remote_name()), but returns a [Remote](crate::Remote).
/// `short_branch_name` is the name to use for looking up `branch.<short_branch_name>.*` values in the
/// configuration.
///
/// See also [`Reference::remote()`](crate::Reference::remote()).
pub fn branch_remote<'a>(
&self,
short_branch_name: impl Into<&'a BStr>,
direction: remote::Direction,
) -> Option<Result<crate::Remote<'_>, remote::find::existing::Error>> {
let name = self.branch_remote_name(short_branch_name, direction)?;
self.try_find_remote(name.as_bstr())
.map(|res| res.map_err(Into::into))
.or_else(|| match name {
remote::Name::Url(url) => gix_url::parse(url.as_ref())
.map_err(Into::into)
.and_then(|url| {
self.remote_at(url)
.map_err(|err| remote::find::existing::Error::Find(remote::find::Error::Init(err)))
})
.into(),
remote::Name::Symbol(_) => None,
})
}
}
fn matching_remote<'a>(
lhs: &FullNameRef,
specs: impl IntoIterator<Item = &'a gix_refspec::RefSpec>,
object_hash: gix_hash::Kind,
) -> Option<Result<Cow<'static, FullNameRef>, gix_validate::reference::name::Error>> {
let search = gix_refspec::MatchGroup {
specs: specs
.into_iter()
.map(gix_refspec::RefSpec::to_ref)
.filter(|spec| spec.source().is_some() && spec.destination().is_some())
.collect(),
};
let null_id = object_hash.null();
let out = search.match_lhs(
Some(gix_refspec::match_group::Item {
full_ref_name: lhs.as_bstr(),
target: &null_id,
object: None,
})
.into_iter(),
);
out.mappings
.into_iter()
.next()
.and_then(|m| m.rhs.map(|name| FullName::try_from(name.into_owned()).map(Cow::Owned)))
}