Skip to content

Commit 0ac3080

Browse files
authored
Merge pull request #2242 from djc/ls-refs
Isolate logic surrounding ls-refs command
2 parents 7ce40ef + aeee982 commit 0ac3080

File tree

17 files changed

+219
-177
lines changed

17 files changed

+219
-177
lines changed

gitoxide-core/src/pack/receive.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
sync::{atomic::AtomicBool, Arc},
55
};
66

7+
use crate::{net, pack::receive::protocol::fetch::negotiate, OutputFormat};
78
#[cfg(feature = "async-client")]
89
use gix::protocol::transport::client::async_io::connect;
910
#[cfg(feature = "blocking-client")]
@@ -23,8 +24,6 @@ pub use gix::{
2324
NestedProgress, Progress,
2425
};
2526

26-
use crate::{net, pack::receive::protocol::fetch::negotiate, OutputFormat};
27-
2827
pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
2928
pub struct Context<W> {
3029
pub thread_limit: Option<usize>,
@@ -65,7 +64,7 @@ where
6564
.is_some();
6665

6766
let agent = gix::protocol::agent(gix::env::agent());
68-
let mut handshake = gix::protocol::fetch::handshake(
67+
let mut handshake: gix::protocol::Handshake = gix::protocol::fetch::handshake(
6968
&mut transport.inner,
7069
gix::protocol::credentials::builtin,
7170
vec![("agent".into(), Some(agent.clone()))],
@@ -82,18 +81,21 @@ where
8281
})
8382
.collect::<Result<_, _>>()?;
8483
let user_agent = ("agent", Some(agent.clone().into()));
85-
let refmap = gix::protocol::fetch::RefMap::new(
86-
&mut progress,
87-
&fetch_refspecs,
88-
gix::protocol::fetch::Context {
89-
handshake: &mut handshake,
90-
transport: &mut transport.inner,
91-
user_agent: user_agent.clone(),
84+
85+
let context = gix::protocol::fetch::refmap::init::Context {
86+
fetch_refspecs: fetch_refspecs.clone(),
87+
extra_refspecs: vec![],
88+
};
89+
let refmap = handshake
90+
.fetch_or_extract_refmap(
91+
&mut progress,
92+
&mut transport.inner,
93+
user_agent.clone(),
9294
trace_packetlines,
93-
},
94-
gix::protocol::fetch::refmap::init::Options::default(),
95-
)
96-
.await?;
95+
true,
96+
context,
97+
)
98+
.await?;
9799

98100
if refmap.mappings.is_empty() && !refmap.remote_refs.is_empty() {
99101
return Err(Error::NoMapping {

gix-protocol/src/fetch/handshake.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ use maybe_async::maybe_async;
66
use crate::transport::client::async_io::Transport;
77
#[cfg(feature = "blocking-client")]
88
use crate::transport::client::blocking_io::Transport;
9-
use crate::{
10-
credentials,
11-
handshake::{Error, Outcome},
12-
};
9+
use crate::{credentials, handshake::Error, Handshake};
1310

1411
/// Perform a handshake with the server on the other side of `transport`, with `authenticate` being used if authentication
1512
/// turns out to be required. `extra_parameters` are the parameters `(name, optional value)` to add to the handshake,
@@ -22,7 +19,7 @@ pub async fn upload_pack<AuthFn, T>(
2219
authenticate: AuthFn,
2320
extra_parameters: Vec<(String, Option<String>)>,
2421
progress: &mut impl Progress,
25-
) -> Result<Outcome, Error>
22+
) -> Result<Handshake, Error>
2623
where
2724
AuthFn: FnMut(credentials::helper::Action) -> credentials::protocol::Result,
2825
T: Transport,

gix-protocol/src/fetch/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
///
66
/// * [handshake](handshake())
77
/// * **ls-refs**
8-
/// * [get available refs by refspecs](RefMap::new())
8+
/// * [get available refs by refspecs](RefMap::fetch())
99
/// * **fetch pack**
1010
/// * `negotiate` until a pack can be received (TBD)
1111
/// * [officially terminate the connection](crate::indicate_end_of_interaction())

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::new()`].
114+
/// - The references known on the remote, as previously obtained with [`RefMap::fetch()`].
115115
/// * `shallow`
116116
/// - How to deal with shallow repositories. It does affect how negotiations are performed.
117117
/// * `mapping_is_ignored`
Lines changed: 92 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
use std::collections::HashSet;
1+
use std::{borrow::Cow, collections::HashSet};
22

3-
use bstr::{BString, ByteVec};
3+
use bstr::{BString, ByteSlice, ByteVec};
44
use gix_features::progress::Progress;
5+
use gix_transport::client::Capabilities;
56

67
#[cfg(feature = "async-client")]
78
use crate::transport::client::async_io::Transport;
89
#[cfg(feature = "blocking-client")]
910
use crate::transport::client::blocking_io::Transport;
1011
use crate::{
11-
fetch,
1212
fetch::{
1313
refmap::{Mapping, Source, SpecIndex},
1414
RefMap,
1515
},
16+
handshake::Ref,
1617
};
1718

18-
/// The error returned by [`RefMap::new()`].
19+
/// The error returned by [`RefMap::fetch()`].
1920
#[derive(Debug, thiserror::Error)]
2021
#[allow(missing_docs)]
2122
pub enum Error {
@@ -27,96 +28,82 @@ pub enum Error {
2728
ListRefs(#[from] crate::ls_refs::Error),
2829
}
2930

30-
/// For use in [`RefMap::new()`].
31+
/// For use in [`RefMap::fetch()`].
3132
#[derive(Debug, Clone)]
32-
pub struct Options {
33-
/// Use a two-component prefix derived from the ref-spec's source, like `refs/heads/` to let the server pre-filter refs
34-
/// with great potential for savings in traffic and local CPU time. Defaults to `true`.
35-
pub prefix_from_spec_as_filter_on_remote: bool,
33+
pub struct Context {
34+
/// All explicit refspecs to identify references on the remote that you are interested in.
35+
/// Note that these are copied to [`RefMap::refspecs`] for convenience, as `RefMap::mappings` refer to them by index.
36+
pub fetch_refspecs: Vec<gix_refspec::RefSpec>,
3637
/// A list of refspecs to use as implicit refspecs which won't be saved or otherwise be part of the remote in question.
3738
///
3839
/// This is useful for handling `remote.<name>.tagOpt` for example.
3940
pub extra_refspecs: Vec<gix_refspec::RefSpec>,
4041
}
4142

42-
impl Default for Options {
43-
fn default() -> Self {
44-
Options {
45-
prefix_from_spec_as_filter_on_remote: true,
46-
extra_refspecs: Vec::new(),
47-
}
43+
impl Context {
44+
fn aggregate_refspecs(&self) -> Vec<gix_refspec::RefSpec> {
45+
let mut all_refspecs = self.fetch_refspecs.clone();
46+
all_refspecs.extend(self.extra_refspecs.iter().cloned());
47+
all_refspecs
4848
}
4949
}
5050

5151
impl RefMap {
52-
/// Create a new instance by obtaining all references on the remote that have been filtered through our remote's
52+
/// Create a new instance by obtaining all references on the remote that have been filtered through our remote's specs
5353
/// for _fetching_.
5454
///
55-
/// A [context](fetch::Context) is provided to bundle what would be additional parameters,
56-
/// and [options](Options) are used to further configure the call.
57-
///
5855
/// * `progress` is used if `ls-refs` is invoked on the remote. Always the case when V2 is used.
59-
/// * `fetch_refspecs` are all explicit refspecs to identify references on the remote that you are interested in.
60-
/// Note that these are copied to [`RefMap::refspecs`] for convenience, as `RefMap::mappings` refer to them by index.
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).
6164
#[allow(clippy::result_large_err)]
6265
#[maybe_async::maybe_async]
63-
pub async fn new<T>(
66+
pub async fn fetch<T>(
6467
mut progress: impl Progress,
65-
fetch_refspecs: &[gix_refspec::RefSpec],
66-
fetch::Context {
67-
handshake,
68-
transport,
69-
user_agent,
70-
trace_packetlines,
71-
}: fetch::Context<'_, T>,
72-
Options {
73-
prefix_from_spec_as_filter_on_remote,
74-
extra_refspecs,
75-
}: Options,
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,
7674
) -> Result<Self, Error>
7775
where
7876
T: Transport,
7977
{
8078
let _span = gix_trace::coarse!("gix_protocol::fetch::RefMap::new()");
81-
let null = gix_hash::ObjectId::null(gix_hash::Kind::Sha1); // OK to hardcode Sha1, it's not supposed to match, ever.
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?;
8292

83-
let all_refspecs = {
84-
let mut s: Vec<_> = fetch_refspecs.to_vec();
85-
s.extend(extra_refspecs.clone());
86-
s
87-
};
88-
let remote_refs = match handshake.refs.take() {
89-
Some(refs) => refs,
90-
None => {
91-
crate::ls_refs(
92-
transport,
93-
&handshake.capabilities,
94-
|_capabilities, arguments, features| {
95-
features.push(user_agent);
96-
if prefix_from_spec_as_filter_on_remote {
97-
let mut seen = HashSet::new();
98-
for spec in &all_refspecs {
99-
let spec = spec.to_ref();
100-
if seen.insert(spec.instruction()) {
101-
let mut prefixes = Vec::with_capacity(1);
102-
spec.expand_prefixes(&mut prefixes);
103-
for mut prefix in prefixes {
104-
prefix.insert_str(0, "ref-prefix ");
105-
arguments.push(prefix);
106-
}
107-
}
108-
}
109-
}
110-
Ok(crate::ls_refs::Action::Continue)
111-
},
112-
&mut progress,
113-
trace_packetlines,
114-
)
115-
.await?
116-
}
117-
};
93+
Self::from_refs(remote_refs, capabilities, context)
94+
}
95+
96+
/// Create a ref-map from already obtained `remote_refs`. Use `context` to pass in refspecs.
97+
/// `capabilities` are used to determine the object format.
98+
pub fn from_refs(remote_refs: Vec<Ref>, capabilities: &Capabilities, context: Context) -> Result<RefMap, Error> {
99+
let all_refspecs = context.aggregate_refspecs();
100+
let Context {
101+
fetch_refspecs,
102+
extra_refspecs,
103+
} = context;
118104
let num_explicit_specs = fetch_refspecs.len();
119105
let group = gix_refspec::MatchGroup::from_fetch_specs(all_refspecs.iter().map(gix_refspec::RefSpec::to_ref));
106+
let null = gix_hash::ObjectId::null(gix_hash::Kind::Sha1); // OK to hardcode Sha1, it's not supposed to match, ever.
120107
let (res, fixes) = group
121108
.match_lhs(remote_refs.iter().map(|r| {
122109
let (full_ref_name, target, object) = r.unpack();
@@ -150,24 +137,9 @@ impl RefMap {
150137
})
151138
.collect();
152139

153-
let object_hash = extract_object_format(handshake)?;
154-
Ok(RefMap {
155-
mappings,
156-
refspecs: fetch_refspecs.to_vec(),
157-
extra_refspecs,
158-
fixes,
159-
remote_refs,
160-
object_hash,
161-
})
162-
}
163-
}
164-
165-
/// Assume sha1 if server says nothing, otherwise configure anything beyond sha1 in the local repo configuration
166-
#[allow(clippy::result_large_err)]
167-
fn extract_object_format(outcome: &crate::handshake::Outcome) -> Result<gix_hash::Kind, Error> {
168-
use bstr::ByteSlice;
169-
let object_hash =
170-
if let Some(object_format) = outcome.capabilities.capability("object-format").and_then(|c| c.value()) {
140+
// Assume sha1 if server says nothing, otherwise configure anything beyond sha1 in the local repo configuration
141+
let object_hash = if let Some(object_format) = capabilities.capability("object-format").and_then(|c| c.value())
142+
{
171143
let object_format = object_format.to_str().map_err(|_| Error::UnknownObjectFormat {
172144
format: object_format.into(),
173145
})?;
@@ -178,5 +150,37 @@ fn extract_object_format(outcome: &crate::handshake::Outcome) -> Result<gix_hash
178150
} else {
179151
gix_hash::Kind::Sha1
180152
};
181-
Ok(object_hash)
153+
154+
Ok(Self {
155+
mappings,
156+
refspecs: fetch_refspecs,
157+
extra_refspecs,
158+
fixes,
159+
remote_refs,
160+
object_hash,
161+
})
162+
}
163+
}
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+
}
182186
}

gix-protocol/src/fetch/response/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ impl Response {
185185
}
186186

187187
/// Append the given `updates` which may have been obtained from a
188-
/// (handshake::Outcome)[crate::handshake::Outcome::v1_shallow_updates].
188+
/// (handshake::Outcome)[crate::Handshake::v1_shallow_updates].
189189
///
190190
/// In V2, these are received as part of the pack, but V1 sends them early, so we
191191
/// offer to re-integrate them here.

gix-protocol/src/fetch/types.rs

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

21-
/// For use in [`RefMap::new()`] and [`fetch`](crate::fetch()).
21+
/// For use in [`RefMap::fetch()`] and [`fetch`](crate::fetch()).
2222
#[cfg(feature = "handshake")]
2323
pub struct Context<'a, T> {
2424
/// The outcome of the handshake performed with the remote.
2525
///
2626
/// Note that it's mutable as depending on the protocol, it may contain refs that have been sent unconditionally.
27-
pub handshake: &'a mut crate::handshake::Outcome,
27+
pub handshake: &'a mut crate::Handshake,
2828
/// The transport to use when making an `ls-refs` or `fetch` call.
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::new()`] or the `fetch` call in [`fetch()`](crate::fetch()).
32+
/// How to self-identify during the `ls-refs` call in [`RefMap::fetch()`] 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>>),
@@ -39,10 +39,7 @@ pub struct Context<'a, T> {
3939

4040
#[cfg(feature = "fetch")]
4141
mod with_fetch {
42-
use crate::{
43-
fetch,
44-
fetch::{negotiate, refmap},
45-
};
42+
use crate::fetch::{self, negotiate, refmap};
4643

4744
/// For use in [`fetch`](crate::fetch()).
4845
pub struct NegotiateContext<'a, 'b, 'c, Objects, Alternates, AlternatesOut, AlternatesErr, Find>
@@ -126,7 +123,7 @@ mod with_fetch {
126123
/// [`refmap::SpecIndex::ExplicitInRemote`] in [`refmap::Mapping`].
127124
pub refspecs: Vec<gix_refspec::RefSpec>,
128125
/// Refspecs which have been added implicitly due to settings of the `remote`, usually pre-initialized from
129-
/// [`extra_refspecs` in RefMap options](refmap::init::Options).
126+
/// [`extra_refspecs` in RefMap options](refmap::init::Context).
130127
/// They are referred to by [`refmap::SpecIndex::Implicit`] in [`refmap::Mapping`].
131128
///
132129
/// They are never persisted nor are they typically presented to the user.

0 commit comments

Comments
 (0)