1- use std:: collections:: HashSet ;
1+ use std:: { borrow :: Cow , collections:: HashSet } ;
22
3- use bstr:: { BString , ByteVec } ;
3+ use bstr:: { BString , ByteSlice , ByteVec } ;
44use gix_features:: progress:: Progress ;
5+ use gix_transport:: client:: Capabilities ;
56
67#[ cfg( feature = "async-client" ) ]
78use crate :: transport:: client:: async_io:: Transport ;
89#[ cfg( feature = "blocking-client" ) ]
910use crate :: transport:: client:: blocking_io:: Transport ;
1011use 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) ]
2122pub 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
5151impl 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}
0 commit comments