Skip to content

Commit eab774c

Browse files
authored
Merge pull request #2274 from djc/simplify-ls-refs
protocol: split async and blocking APIs for fetching refmaps
2 parents 111b625 + 9d936fb commit eab774c

File tree

11 files changed

+279
-255
lines changed

11 files changed

+279
-255
lines changed

gitoxide-core/src/pack/receive.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,17 @@ where
8787
fetch_refspecs: fetch_refspecs.clone(),
8888
extra_refspecs: vec![],
8989
};
90-
let refmap = handshake
91-
.fetch_or_extract_refmap(
92-
&mut progress,
93-
&mut transport.inner,
94-
user_agent.clone(),
95-
trace_packetlines,
96-
true,
97-
context,
98-
)
90+
91+
let fetch_refmap = handshake.prepare_lsrefs_or_extract_refmap(user_agent.clone(), true, context)?;
92+
93+
#[cfg(feature = "async-client")]
94+
let refmap = fetch_refmap
95+
.fetch_async(&mut progress, &mut transport.inner, trace_packetlines)
9996
.await?;
10097

98+
#[cfg(feature = "blocking-client")]
99+
let refmap = fetch_refmap.fetch_blocking(&mut progress, &mut transport.inner, trace_packetlines)?;
100+
101101
if refmap.mappings.is_empty() && !refmap.remote_refs.is_empty() {
102102
return Err(Error::NoMapping {
103103
refspecs: refmap.refspecs.clone(),

gix-protocol/src/fetch/negotiate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ pub struct Round {
111111
/// * `graph`
112112
/// - The commit-graph for use by the `negotiator` - we populate it with tips to initialize the graph traversal.
113113
/// * `ref_map`
114-
/// - The references known on the remote, as previously obtained with [`RefMap::fetch()`].
114+
/// - The references known on the remote, as previously obtained with [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`].
115115
/// * `shallow`
116116
/// - How to deal with shallow repositories. It does affect how negotiations are performed.
117117
/// * `mapping_is_ignored`
Lines changed: 4 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
use std::{borrow::Cow, collections::HashSet};
2-
3-
use bstr::{BString, ByteSlice, ByteVec};
4-
use gix_features::progress::Progress;
1+
use bstr::{BString, ByteSlice};
52
use gix_transport::client::Capabilities;
63

7-
#[cfg(feature = "async-client")]
8-
use crate::transport::client::async_io::Transport;
9-
#[cfg(feature = "blocking-client")]
10-
use crate::transport::client::blocking_io::Transport;
114
use crate::{
125
fetch::{
136
refmap::{Mapping, Source, SpecIndex},
@@ -16,7 +9,7 @@ use crate::{
169
handshake::Ref,
1710
};
1811

19-
/// The error returned by [`RefMap::fetch()`].
12+
/// The error returned by [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`].
2013
#[derive(Debug, thiserror::Error)]
2114
#[allow(missing_docs)]
2215
pub enum Error {
@@ -28,7 +21,7 @@ pub enum Error {
2821
ListRefs(#[from] crate::ls_refs::Error),
2922
}
3023

31-
/// For use in [`RefMap::fetch()`].
24+
/// For use in [`RefMap::from_refs()`].
3225
#[derive(Debug, Clone)]
3326
pub struct Context {
3427
/// All explicit refspecs to identify references on the remote that you are interested in.
@@ -41,58 +34,14 @@ pub struct Context {
4134
}
4235

4336
impl Context {
44-
fn aggregate_refspecs(&self) -> Vec<gix_refspec::RefSpec> {
37+
pub(crate) fn aggregate_refspecs(&self) -> Vec<gix_refspec::RefSpec> {
4538
let mut all_refspecs = self.fetch_refspecs.clone();
4639
all_refspecs.extend(self.extra_refspecs.iter().cloned());
4740
all_refspecs
4841
}
4942
}
5043

5144
impl RefMap {
52-
/// Create a new instance by obtaining all references on the remote that have been filtered through our remote's specs
53-
/// for _fetching_.
54-
///
55-
/// * `progress` is used if `ls-refs` is invoked on the remote. Always the case when V2 is used.
56-
/// * `capabilities` are the capabilities of the server, obtained by a [handshake](crate::handshake()).
57-
/// * `transport` is a way to communicate with the server to obtain the reference listing.
58-
/// * `user_agent` is passed to the server.
59-
/// * `trace_packetlines` traces all packet lines if `true`, for debugging primarily.
60-
/// * `prefix_from_spec_as_filter_on_remote`
61-
/// - Use a two-component prefix derived from the ref-spec's source, like `refs/heads/` to let the server pre-filter refs
62-
/// with great potential for savings in traffic and local CPU time.
63-
/// * `context` to provide more [configuration](Context).
64-
#[allow(clippy::result_large_err)]
65-
#[maybe_async::maybe_async]
66-
pub async fn fetch<T>(
67-
mut progress: impl Progress,
68-
capabilities: &Capabilities,
69-
transport: &mut T,
70-
user_agent: (&'static str, Option<Cow<'static, str>>),
71-
trace_packetlines: bool,
72-
prefix_from_spec_as_filter_on_remote: bool,
73-
context: Context,
74-
) -> Result<Self, Error>
75-
where
76-
T: Transport,
77-
{
78-
let _span = gix_trace::coarse!("gix_protocol::fetch::RefMap::new()");
79-
let all_refspecs = context.aggregate_refspecs();
80-
let remote_refs = crate::ls_refs(
81-
transport,
82-
capabilities,
83-
|_capabilities, arguments| {
84-
push_prefix_arguments(prefix_from_spec_as_filter_on_remote, arguments, &all_refspecs);
85-
Ok(crate::ls_refs::Action::Continue)
86-
},
87-
&mut progress,
88-
trace_packetlines,
89-
user_agent,
90-
)
91-
.await?;
92-
93-
Self::from_refs(remote_refs, capabilities, context)
94-
}
95-
9645
/// Create a ref-map from already obtained `remote_refs`. Use `context` to pass in refspecs.
9746
/// `capabilities` are used to determine the object format.
9847
pub fn from_refs(remote_refs: Vec<Ref>, capabilities: &Capabilities, context: Context) -> Result<RefMap, Error> {
@@ -161,26 +110,3 @@ impl RefMap {
161110
})
162111
}
163112
}
164-
165-
fn push_prefix_arguments(
166-
prefix_from_spec_as_filter_on_remote: bool,
167-
arguments: &mut Vec<BString>,
168-
all_refspecs: &[gix_refspec::RefSpec],
169-
) {
170-
if !prefix_from_spec_as_filter_on_remote {
171-
return;
172-
}
173-
174-
let mut seen = HashSet::new();
175-
for spec in all_refspecs {
176-
let spec = spec.to_ref();
177-
if seen.insert(spec.instruction()) {
178-
let mut prefixes = Vec::with_capacity(1);
179-
spec.expand_prefixes(&mut prefixes);
180-
for mut prefix in prefixes {
181-
prefix.insert_str(0, "ref-prefix ");
182-
arguments.push(prefix);
183-
}
184-
}
185-
}
186-
}

gix-protocol/src/fetch/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub struct Options<'a> {
1818
pub reject_shallow_remote: bool,
1919
}
2020

21-
/// For use in [`RefMap::fetch()`] and [`fetch`](crate::fetch()).
21+
/// For use in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] and [`fetch`](crate::fetch()).
2222
#[cfg(feature = "handshake")]
2323
pub struct Context<'a, T> {
2424
/// The outcome of the handshake performed with the remote.
@@ -29,7 +29,7 @@ pub struct Context<'a, T> {
2929
///
3030
/// This is always done if the underlying protocol is V2, which is implied by the absence of refs in the `handshake` outcome.
3131
pub transport: &'a mut T,
32-
/// How to self-identify during the `ls-refs` call in [`RefMap::fetch()`] or the `fetch` call in [`fetch()`](crate::fetch()).
32+
/// How to self-identify during the `ls-refs` call in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] or the `fetch` call in [`fetch()`](crate::fetch()).
3333
///
3434
/// This could be read from the `gitoxide.userAgent` configuration variable.
3535
pub user_agent: (&'static str, Option<std::borrow::Cow<'static, str>>),

gix-protocol/src/handshake/mod.rs

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -72,45 +72,86 @@ pub(crate) mod hero {
7272
#[cfg(feature = "fetch")]
7373
mod fetch {
7474
#[cfg(feature = "async-client")]
75-
use crate::transport::client::async_io::Transport;
75+
use crate::transport::client::async_io;
7676
#[cfg(feature = "blocking-client")]
77-
use crate::transport::client::blocking_io::Transport;
78-
use crate::Handshake;
77+
use crate::transport::client::blocking_io;
78+
use crate::{fetch::RefMap, Handshake};
7979
use gix_features::progress::Progress;
8080
use std::borrow::Cow;
8181

82+
/// Intermediate state while potentially fetching a refmap after the handshake.
83+
pub enum ObtainRefMap<'a> {
84+
/// We already got a refmap to use from the V1 handshake, which always sends them.
85+
Existing(RefMap),
86+
/// We need to invoke another `ls-refs` command to retrieve the refmap as part of the V2 protocol.
87+
LsRefsCommand(crate::LsRefsCommand<'a>, crate::fetch::refmap::init::Context),
88+
}
89+
90+
impl ObtainRefMap<'_> {
91+
/// Fetch the refmap, either by returning the existing one or invoking the `ls-refs` command.
92+
#[cfg(feature = "async-client")]
93+
#[allow(clippy::result_large_err)]
94+
pub async fn fetch_async(
95+
self,
96+
mut progress: impl Progress,
97+
transport: &mut impl async_io::Transport,
98+
trace_packetlines: bool,
99+
) -> Result<RefMap, crate::fetch::refmap::init::Error> {
100+
let (cmd, cx) = match self {
101+
ObtainRefMap::Existing(map) => return Ok(map),
102+
ObtainRefMap::LsRefsCommand(cmd, cx) => (cmd, cx),
103+
};
104+
105+
let _span = gix_trace::coarse!("gix_protocol::handshake::ObtainRefMap::fetch_async()");
106+
let capabilities = cmd.capabilities;
107+
let remote_refs = cmd.invoke_async(transport, &mut progress, trace_packetlines).await?;
108+
RefMap::from_refs(remote_refs, capabilities, cx)
109+
}
110+
111+
/// Fetch the refmap, either by returning the existing one or invoking the `ls-refs` command.
112+
#[cfg(feature = "blocking-client")]
113+
#[allow(clippy::result_large_err)]
114+
pub fn fetch_blocking(
115+
self,
116+
mut progress: impl Progress,
117+
transport: &mut impl blocking_io::Transport,
118+
trace_packetlines: bool,
119+
) -> Result<RefMap, crate::fetch::refmap::init::Error> {
120+
let (cmd, cx) = match self {
121+
ObtainRefMap::Existing(map) => return Ok(map),
122+
ObtainRefMap::LsRefsCommand(cmd, cx) => (cmd, cx),
123+
};
124+
125+
let _span = gix_trace::coarse!("gix_protocol::handshake::ObtainRefMap::fetch_blocking()");
126+
let capabilities = cmd.capabilities;
127+
let remote_refs = cmd.invoke_blocking(transport, &mut progress, trace_packetlines)?;
128+
RefMap::from_refs(remote_refs, capabilities, cx)
129+
}
130+
}
131+
82132
impl Handshake {
83-
/// Obtain a [refmap](crate::fetch::RefMap) either from this instance, taking it out in the process, if the handshake was
84-
/// created from a V1 connection, or use `transport` to fetch the refmap as a separate command invocation.
133+
/// Prepare fetching a [refmap](RefMap) if not present in the handshake.
85134
#[allow(clippy::result_large_err)]
86-
#[maybe_async::maybe_async]
87-
pub async fn fetch_or_extract_refmap<T>(
135+
pub fn prepare_lsrefs_or_extract_refmap(
88136
&mut self,
89-
mut progress: impl Progress,
90-
transport: &mut T,
91137
user_agent: (&'static str, Option<Cow<'static, str>>),
92-
trace_packetlines: bool,
93138
prefix_from_spec_as_filter_on_remote: bool,
94139
refmap_context: crate::fetch::refmap::init::Context,
95-
) -> Result<crate::fetch::RefMap, crate::fetch::refmap::init::Error>
96-
where
97-
T: Transport,
98-
{
99-
Ok(match self.refs.take() {
100-
Some(refs) => crate::fetch::RefMap::from_refs(refs, &self.capabilities, refmap_context)?,
101-
None => {
102-
crate::fetch::RefMap::fetch(
103-
&mut progress,
104-
&self.capabilities,
105-
transport,
106-
user_agent,
107-
trace_packetlines,
108-
prefix_from_spec_as_filter_on_remote,
109-
refmap_context,
110-
)
111-
.await?
112-
}
113-
})
140+
) -> Result<ObtainRefMap<'_>, crate::fetch::refmap::init::Error> {
141+
if let Some(refs) = self.refs.take() {
142+
return Ok(ObtainRefMap::Existing(RefMap::from_refs(
143+
refs,
144+
&self.capabilities,
145+
refmap_context,
146+
)?));
147+
}
148+
149+
let all_refspecs = refmap_context.aggregate_refspecs();
150+
let prefix_refspecs = prefix_from_spec_as_filter_on_remote.then_some(&all_refspecs[..]);
151+
Ok(ObtainRefMap::LsRefsCommand(
152+
crate::LsRefsCommand::new(prefix_refspecs, &self.capabilities, user_agent),
153+
refmap_context,
154+
))
114155
}
115156
}
116157
}
@@ -150,6 +191,7 @@ mod error {
150191
}
151192
}
152193
}
194+
153195
#[cfg(feature = "handshake")]
154196
pub use error::Error;
155197

gix-protocol/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! * create a `Transport`, either blocking or async
66
//! * perform a [`handshake()`]
77
//! * execute a [`Command`]
8-
//! - [list references](ls_refs())
8+
//! - [list references](LsRefsCommand)
99
//! - create a mapping between [refspecs and references](fetch::RefMap)
1010
//! - [receive a pack](fetch())
1111
//!
@@ -67,7 +67,7 @@ pub use handshake::hero::Handshake;
6767
///
6868
pub mod ls_refs;
6969
#[cfg(any(feature = "blocking-client", feature = "async-client"))]
70-
pub use ls_refs::function::ls_refs;
70+
pub use ls_refs::function::LsRefsCommand;
7171

7272
mod util;
7373
pub use util::*;

0 commit comments

Comments
 (0)