Skip to content

Commit 530c15d

Browse files
committed
refactor
1 parent 193ffcd commit 530c15d

File tree

3 files changed

+270
-276
lines changed

3 files changed

+270
-276
lines changed

gix/src/repository/config/branch.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
use std::{borrow::Cow, collections::BTreeSet, convert::TryInto};
2+
3+
use gix_ref::{FullName, FullNameRef};
4+
5+
use crate::bstr::BStr;
6+
use crate::config::cache::util::ApplyLeniencyDefault;
7+
use crate::config::tree::{Branch, Push, Section};
8+
use crate::repository::{branch_remote_ref_name, branch_remote_tracking_ref_name};
9+
use crate::{push, remote};
10+
11+
/// Query configuration related to branches.
12+
impl crate::Repository {
13+
/// Return a set of unique short branch names for which custom configuration exists in the configuration,
14+
/// if we deem them [trustworthy][crate::open::Options::filter_config_section()].
15+
///
16+
/// ### Note
17+
///
18+
/// Branch names that have illformed UTF-8 will silently be skipped.
19+
pub fn branch_names(&self) -> BTreeSet<&str> {
20+
self.subsection_str_names_of("branch")
21+
}
22+
23+
/// Returns the validated reference on the remote associated with the given `name`,
24+
/// which will be used when *merging*.
25+
/// The returned value corresponds to the `branch.<short_branch_name>.merge` configuration key.
26+
///
27+
/// Returns `None` if there is no value at the given key, or if no remote or remote ref is configured.
28+
/// May return an error if the reference name to be returned is invalid.
29+
///
30+
/// ### Note
31+
///
32+
/// This name refers to what Git calls upstream branch (as opposed to upstream *tracking* branch).
33+
/// The value is also fast to retrieve compared to its tracking branch.
34+
/// Also note that a [remote::Direction] isn't used here as Git only supports (and requires) configuring
35+
/// the remote to fetch from, not the one to push to.
36+
///
37+
/// See also [`Reference::remote_ref_name()`](crate::Reference::remote_ref_name()).
38+
#[doc(alias = "branch_upstream_name", alias = "git2")]
39+
pub fn branch_remote_ref_name(
40+
&self,
41+
name: &FullNameRef,
42+
direction: remote::Direction,
43+
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_ref_name::Error>> {
44+
match direction {
45+
remote::Direction::Fetch => {
46+
let short_name = name.shorten();
47+
self.config
48+
.resolved
49+
.string("branch", Some(short_name), Branch::MERGE.name)
50+
.map(|name| crate::config::tree::branch::Merge::try_into_fullrefname(name).map_err(Into::into))
51+
}
52+
remote::Direction::Push => {
53+
let remote = match self.branch_remote(name.shorten(), direction)? {
54+
Ok(r) => r,
55+
Err(err) => return Some(Err(err.into())),
56+
};
57+
if remote.push_specs.is_empty() {
58+
let push_default = match self
59+
.config
60+
.resolved
61+
.string(Push.name(), None, Push::DEFAULT.name)
62+
.map_or(Ok(Default::default()), |v| {
63+
Push::DEFAULT
64+
.try_into_default(v)
65+
.with_lenient_default(self.config.lenient_config)
66+
}) {
67+
Ok(v) => v,
68+
Err(err) => return Some(Err(err.into())),
69+
};
70+
match push_default {
71+
push::Default::Nothing => None,
72+
push::Default::Current | push::Default::Matching => Some(Ok(Cow::Owned(name.to_owned()))),
73+
push::Default::Upstream => self.branch_remote_ref_name(name, remote::Direction::Fetch),
74+
push::Default::Simple => match self.branch_remote_ref_name(name, remote::Direction::Fetch)? {
75+
Ok(fetch_ref) if fetch_ref.as_ref() == name => Some(Ok(fetch_ref)),
76+
Err(err) => Some(Err(err)),
77+
Ok(_different_fetch_ref) => None,
78+
},
79+
}
80+
} else {
81+
matching_remote(name, remote.push_specs.iter(), self.object_hash())
82+
.map(|res| res.map_err(Into::into))
83+
}
84+
}
85+
}
86+
}
87+
88+
/// Return the validated name of the reference that tracks the corresponding reference of `name` on the remote for
89+
/// `direction`. Note that a branch with that name might not actually exist.
90+
///
91+
/// * with `remote` being [remote::Direction::Fetch], we return the tracking branch that is on the destination
92+
/// side of a `src:dest` refspec. For instance, with `name` being `main` and the default refspec
93+
/// `refs/heads/*:refs/remotes/origin/*`, `refs/heads/main` would match and produce `refs/remotes/origin/main`.
94+
/// * with `remote` being [remote::Direction::Push], we return the tracking branch that corresponds to the remote
95+
/// branch that we would push to. For instance, with `name` being `main` and no setup at all, we
96+
/// would push to `refs/heads/main` on the remote. And that one would be fetched matching the
97+
/// `refs/heads/*:refs/remotes/origin/*` fetch refspec, hence `refs/remotes/origin/main` is returned.
98+
/// Note that `push` refspecs can be used to map `main` to `other` (using a push refspec `refs/heads/main:refs/heads/other`),
99+
/// which would then lead to `refs/remotes/origin/other` to be returned instead.
100+
///
101+
/// Note that if there is an ambiguity, that is if `name` maps to multiple tracking branches, the first matching mapping
102+
/// is returned, according to the order in which the fetch or push refspecs occour in the configuration file.
103+
///
104+
/// See also [`Reference::remote_tracking_ref_name()`](crate::Reference::remote_tracking_ref_name()).
105+
#[doc(alias = "branch_upstream_name", alias = "git2")]
106+
pub fn branch_remote_tracking_ref_name(
107+
&self,
108+
name: &FullNameRef,
109+
direction: remote::Direction,
110+
) -> Option<Result<Cow<'_, FullNameRef>, branch_remote_tracking_ref_name::Error>> {
111+
let remote_ref = match self.branch_remote_ref_name(name, direction)? {
112+
Ok(r) => r,
113+
Err(err) => return Some(Err(err.into())),
114+
};
115+
let remote = match self.branch_remote(name.shorten(), direction)? {
116+
Ok(r) => r,
117+
Err(err) => return Some(Err(err.into())),
118+
};
119+
120+
if remote.fetch_specs.is_empty() {
121+
return None;
122+
}
123+
matching_remote(remote_ref.as_ref(), remote.fetch_specs.iter(), self.object_hash())
124+
.map(|res| res.map_err(Into::into))
125+
}
126+
127+
/// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
128+
/// typically `main` instead of `refs/heads/main`.
129+
/// In some cases, the returned name will be an URL.
130+
/// Returns `None` if the remote was not found or if the name contained illformed UTF-8.
131+
///
132+
/// * if `direction` is [remote::Direction::Fetch], we will query the `branch.<short_name>.remote` configuration.
133+
/// * if `direction` is [remote::Direction::Push], the push remote will be queried by means of `branch.<short_name>.pushRemote`
134+
/// or `remote.pushDefault` as fallback.
135+
///
136+
/// See also [`Reference::remote_name()`](crate::Reference::remote_name()) for a more typesafe version
137+
/// to be used when a `Reference` is available.
138+
///
139+
/// `short_branch_name` can typically be obtained by [shortening a full branch name](FullNameRef::shorten()).
140+
#[doc(alias = "branch_upstream_remote", alias = "git2")]
141+
pub fn branch_remote_name<'a>(
142+
&self,
143+
short_branch_name: impl Into<&'a BStr>,
144+
direction: remote::Direction,
145+
) -> Option<remote::Name<'_>> {
146+
let name = short_branch_name.into();
147+
let config = &self.config.resolved;
148+
(direction == remote::Direction::Push)
149+
.then(|| {
150+
config
151+
.string("branch", Some(name), Branch::PUSH_REMOTE.name)
152+
.or_else(|| config.string("remote", None, crate::config::tree::Remote::PUSH_DEFAULT.name))
153+
})
154+
.flatten()
155+
.or_else(|| config.string("branch", Some(name), Branch::REMOTE.name))
156+
.and_then(|name| name.try_into().ok())
157+
}
158+
159+
/// Like [`branch_remote_name(…)`](Self::branch_remote_name()), but returns a [Remote](crate::Remote).
160+
/// `short_branch_name` is the name to use for looking up `branch.<short_branch_name>.*` values in the
161+
/// configuration.
162+
///
163+
/// See also [`Reference::remote()`](crate::Reference::remote()).
164+
pub fn branch_remote<'a>(
165+
&self,
166+
short_branch_name: impl Into<&'a BStr>,
167+
direction: remote::Direction,
168+
) -> Option<Result<crate::Remote<'_>, remote::find::existing::Error>> {
169+
let name = self.branch_remote_name(short_branch_name, direction)?;
170+
self.try_find_remote(name.as_bstr())
171+
.map(|res| res.map_err(Into::into))
172+
.or_else(|| match name {
173+
remote::Name::Url(url) => gix_url::parse(url.as_ref())
174+
.map_err(Into::into)
175+
.and_then(|url| {
176+
self.remote_at(url)
177+
.map_err(|err| remote::find::existing::Error::Find(remote::find::Error::Init(err)))
178+
})
179+
.into(),
180+
remote::Name::Symbol(_) => None,
181+
})
182+
}
183+
}
184+
185+
fn matching_remote<'a>(
186+
lhs: &FullNameRef,
187+
specs: impl IntoIterator<Item = &'a gix_refspec::RefSpec>,
188+
object_hash: gix_hash::Kind,
189+
) -> Option<Result<Cow<'static, FullNameRef>, gix_validate::reference::name::Error>> {
190+
let search = gix_refspec::MatchGroup {
191+
specs: specs
192+
.into_iter()
193+
.map(gix_refspec::RefSpec::to_ref)
194+
.filter(|spec| spec.source().is_some() && spec.destination().is_some())
195+
.collect(),
196+
};
197+
let null_id = object_hash.null();
198+
let out = search.match_remotes(
199+
Some(gix_refspec::match_group::Item {
200+
full_ref_name: lhs.as_bstr(),
201+
target: &null_id,
202+
object: None,
203+
})
204+
.into_iter(),
205+
);
206+
out.mappings.into_iter().next().and_then(|m| {
207+
m.rhs.map(|name| {
208+
FullName::try_from(name.into_owned())
209+
.map(Cow::Owned)
210+
.map_err(Into::into)
211+
})
212+
})
213+
}

0 commit comments

Comments
 (0)