Skip to content

Commit d22b7fb

Browse files
committed
feat: provide Repository::find_fetch_remote() to obtain a remote just like git would.
1 parent dbf778c commit d22b7fb

File tree

5 files changed

+112
-6
lines changed

5 files changed

+112
-6
lines changed

gix/src/remote/errors.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pub mod find {
33
use crate::{bstr::BString, config, remote};
44

5-
/// The error returned by [`Repository::find_remote(…)`][crate::Repository::find_remote()].
5+
/// The error returned by [`Repository::find_remote(…)`](crate::Repository::find_remote()).
66
#[derive(Debug, thiserror::Error)]
77
#[allow(missing_docs)]
88
pub enum Error {
@@ -30,7 +30,7 @@ pub mod find {
3030
pub mod existing {
3131
use crate::bstr::BString;
3232

33-
/// The error returned by [`Repository::find_remote(…)`][crate::Repository::find_remote()].
33+
/// The error returned by [`Repository::find_remote(…)`](crate::Repository::find_remote()).
3434
#[derive(Debug, thiserror::Error)]
3535
#[allow(missing_docs)]
3636
pub enum Error {
@@ -42,4 +42,23 @@ pub mod find {
4242
NotFound { name: BString },
4343
}
4444
}
45+
46+
///
47+
pub mod for_fetch {
48+
/// The error returned by [`Repository::find_fetch_remote(…)`](crate::Repository::find_fetch_remote()).
49+
#[derive(Debug, thiserror::Error)]
50+
#[allow(missing_docs)]
51+
pub enum Error {
52+
#[error(transparent)]
53+
FindExisting(#[from] super::existing::Error),
54+
#[error(transparent)]
55+
FindExistingReferences(#[from] crate::reference::find::existing::Error),
56+
#[error("Could not initialize a URL remote")]
57+
Init(#[from] crate::remote::init::Error),
58+
#[error("remote name could not be parsed as URL")]
59+
UrlParse(#[from] gix_url::parse::Error),
60+
#[error("No configured remote could be found, or too many were available")]
61+
ExactlyOneRemoteNotAvailable,
62+
}
63+
}
4564
}

gix/src/repository/remote.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ impl crate::Repository {
2828
Remote::from_fetch_url(url, false, self)
2929
}
3030

31-
/// Find the remote with the given `name_or_url` or report an error, similar to [`try_find_remote(…)`][Self::try_find_remote()].
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()].
3233
///
3334
/// Note that we will obtain remotes only if we deem them [trustworthy][crate::open::Options::filter_config_section()].
3435
pub fn find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Result<Remote<'_>, find::existing::Error> {
@@ -42,7 +43,7 @@ impl crate::Repository {
4243

4344
/// Find the default remote as configured, or `None` if no such configuration could be found.
4445
///
45-
/// See [`remote_default_name()`][Self::remote_default_name()] for more information on the `direction` parameter.
46+
/// See [`remote_default_name()`](Self::remote_default_name()) for more information on the `direction` parameter.
4647
pub fn find_default_remote(
4748
&self,
4849
direction: remote::Direction,
@@ -51,8 +52,8 @@ impl crate::Repository {
5152
.map(|name| self.find_remote(name.as_ref()))
5253
}
5354

54-
/// Find the remote with the given `name_or_url` or return `None` if it doesn't exist, for the purpose of fetching or pushing
55-
/// data to a remote.
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.
5657
///
5758
/// There are various error kinds related to partial information or incorrectly formatted URLs or ref-specs.
5859
/// Also note that the created `Remote` may have neither fetch nor push ref-specs set at all.
@@ -65,6 +66,35 @@ impl crate::Repository {
6566
self.try_find_remote_inner(name_or_url, true)
6667
}
6768

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+
})
96+
}
97+
6898
/// Similar to [`try_find_remote()`][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid
6999
/// as it skips rewriting them.
70100
/// Use this in conjunction with [`Remote::rewrite_urls()`] to non-destructively apply the rules and keep the failed urls unchanged.

gix/tests/head/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ mod into_remote {
99
repo.head()?.into_remote(gix::remote::Direction::Fetch).transpose()?,
1010
None
1111
);
12+
assert_eq!(
13+
repo.find_fetch_remote(None)?.name().expect("present").as_ref(),
14+
"origin",
15+
"we can fallback to the only available remote"
16+
);
1217
Ok(())
1318
}
1419

@@ -19,6 +24,11 @@ mod into_remote {
1924
repo.head()?.into_remote(gix::remote::Direction::Fetch).transpose()?,
2025
None
2126
);
27+
assert_eq!(
28+
repo.find_fetch_remote(None)?.name().expect("present").as_ref(),
29+
"origin",
30+
"we can fallback to the only available remote"
31+
);
2232
Ok(())
2333
}
2434
}

gix/tests/reference/remote.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ fn not_configured() -> crate::Result {
7878
assert_eq!(branch.remote_name(gix::remote::Direction::Fetch), None);
7979
assert_eq!(branch.remote(gix::remote::Direction::Fetch).transpose()?, None);
8080
assert_eq!(head.into_remote(gix::remote::Direction::Fetch).transpose()?, None);
81+
assert!(
82+
matches!(
83+
repo.find_fetch_remote(None),
84+
Err(gix::remote::find::for_fetch::Error::ExactlyOneRemoteNotAvailable)
85+
),
86+
"there is no remote to be found"
87+
);
8188

8289
Ok(())
8390
}

gix/tests/repository/remote.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,46 @@ mod find_remote {
291291
}
292292
}
293293

294+
mod find_fetch_remote {
295+
use crate::remote;
296+
297+
#[test]
298+
fn symbol_name() -> crate::Result {
299+
let repo = remote::repo("clone-no-tags");
300+
assert_eq!(
301+
repo.find_fetch_remote(Some("origin".into()))?
302+
.name()
303+
.expect("set")
304+
.as_bstr(),
305+
"origin"
306+
);
307+
Ok(())
308+
}
309+
310+
#[test]
311+
fn urls() -> crate::Result {
312+
let repo = remote::repo("clone-no-tags");
313+
for url in [
314+
"some-path",
315+
"https://example.com/repo",
316+
"other/path",
317+
"ssh://host/ssh-aliased-repo",
318+
] {
319+
let remote = repo.find_fetch_remote(Some(url.into()))?;
320+
assert_eq!(remote.name(), None, "this remote is anonymous");
321+
assert_eq!(
322+
remote
323+
.url(gix::remote::Direction::Fetch)
324+
.expect("url is set")
325+
.to_bstring(),
326+
url,
327+
"if it's not a configured remote, we take it as URL"
328+
);
329+
}
330+
Ok(())
331+
}
332+
}
333+
294334
mod find_default_remote {
295335

296336
use crate::remote;

0 commit comments

Comments
 (0)