1use std::{
2 path::Path,
3 sync::atomic::{AtomicBool, Ordering},
4};
5
6use gix_features::progress::DynNestedProgress;
7
8use crate::fetch::{
9 Arguments, Context, Error, Negotiate, NegotiateOutcome, Options, Outcome, ProgressId, Shallow, Tags, negotiate,
10};
11#[crate::bisync::only_async]
12use crate::transport::client::async_io::{ExtendedBufRead, HandleProgress, Transport};
13#[crate::bisync::only_sync]
14use crate::transport::client::blocking_io::{ExtendedBufRead, HandleProgress, Transport};
15
16#[crate::bisync::bisync]
39pub async fn fetch<P, T, E>(
40 negotiate: &mut impl Negotiate,
41 consume_pack: impl FnOnce(&mut dyn std::io::BufRead, &mut dyn DynNestedProgress, &AtomicBool) -> Result<bool, E>,
42 mut progress: P,
43 should_interrupt: &AtomicBool,
44 Context {
45 handshake,
46 transport,
47 user_agent,
48 trace_packetlines,
49 }: Context<'_, T>,
50 Options {
51 shallow_file,
52 shallow,
53 tags,
54 reject_shallow_remote,
55 }: Options<'_>,
56) -> Result<Option<Outcome>, Error>
57where
58 P: gix_features::progress::NestedProgress,
59 P::SubProgress: 'static,
60 T: Transport,
61 E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
62{
63 let _span = gix_trace::coarse!("gix_protocol::fetch()");
64 let v1_shallow_updates = handshake.v1_shallow_updates.take();
65 let protocol_version = handshake.server_protocol_version;
66
67 let fetch = crate::Command::Fetch;
68 let fetch_features = {
69 let mut f = fetch.default_features(protocol_version, &handshake.capabilities);
70 f.push(user_agent);
71 f
72 };
73
74 crate::fetch::Response::check_required_features(protocol_version, &fetch_features)?;
75 let sideband_all = fetch_features.iter().any(|(n, _)| *n == "sideband-all");
76 let mut arguments = Arguments::new(protocol_version, fetch_features, trace_packetlines);
77 if matches!(tags, Tags::Included) {
78 if !arguments.can_use_include_tag() {
79 return Err(Error::MissingServerFeature {
80 feature: "include-tag",
81 description:
82 "To make this work we would have to implement another pass to fetch attached tags separately",
84 });
85 }
86 arguments.use_include_tag();
87 }
88 let (shallow_commits, mut shallow_lock) = add_shallow_args(&mut arguments, shallow, &shallow_file)?;
89
90 let negotiate_span = gix_trace::detail!(
91 "negotiate",
92 protocol_version = handshake.server_protocol_version as usize
93 );
94 let action = negotiate.mark_complete_and_common_ref()?;
95 let mut previous_response = None::<crate::fetch::Response>;
96 match &action {
97 negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => Ok(None),
98 negotiate::Action::MustNegotiate {
99 remote_ref_target_known,
100 } => {
101 if !negotiate.add_wants(&mut arguments, remote_ref_target_known) {
102 return Ok(None);
103 }
104 let mut rounds = Vec::new();
105 let is_stateless = arguments.is_stateless(!transport.connection_persists_across_multiple_requests());
106 let mut state = negotiate::one_round::State::new(is_stateless);
107 let reader = 'negotiation: loop {
108 let _round = gix_trace::detail!("negotiate round", round = rounds.len() + 1);
109 progress.step();
110 progress.set_name(format!("negotiate (round {})", rounds.len() + 1));
111 if should_interrupt.load(Ordering::Relaxed) {
112 return Err(Error::Negotiate(negotiate::Error::NegotiationFailed {
113 rounds: rounds.len(),
114 }));
115 }
116
117 let is_done = match negotiate.one_round(&mut state, &mut arguments, previous_response.as_ref()) {
118 Ok((round, is_done)) => {
119 rounds.push(round);
120 is_done
121 }
122 Err(err) => {
123 return Err(err.into());
124 }
125 };
126 let mut reader = arguments.send(transport, is_done).await?;
127 if sideband_all {
128 setup_remote_progress(&mut progress, &mut reader, should_interrupt);
129 }
130 let response =
131 crate::fetch::Response::from_line_reader(protocol_version, &mut reader, is_done, !is_done).await?;
132 let has_pack = response.has_pack();
133 previous_response = Some(response);
134 if has_pack {
135 progress.step();
136 progress.set_name("receiving pack".into());
137 if !sideband_all {
138 setup_remote_progress(&mut progress, &mut reader, should_interrupt);
139 }
140 break 'negotiation reader;
141 }
142 };
143 drop(negotiate_span);
145
146 let mut previous_response = previous_response.expect("knowledge of a pack means a response was received");
147 previous_response.append_v1_shallow_updates(v1_shallow_updates);
148 if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() {
149 if reject_shallow_remote {
150 return Err(Error::RejectShallowRemote);
151 }
152 shallow_lock = acquire_shallow_lock(&shallow_file).map(Some)?;
153 }
154
155 let (mut reader, may_read_to_end) =
156 consume_received_pack(reader, consume_pack, &mut progress, should_interrupt)?;
157
158 if may_read_to_end {
159 let has_read_to_end = reader.stopped_at().is_some();
161 if !has_read_to_end {
162 read_remaining(&mut reader).await.map_err(Error::ReadRemainingBytes)?;
163 }
164 }
165 drop(reader);
166
167 if let Some(shallow_lock) = shallow_lock {
168 if !previous_response.shallow_updates().is_empty() {
169 gix_shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?;
170 }
171 }
172 Ok(Some(Outcome {
173 last_response: previous_response,
174 negotiate: NegotiateOutcome { action, rounds },
175 }))
176 }
177 }
178}
179
180#[crate::bisync::only_async]
181fn consume_received_pack<R, E>(
182 reader: R,
183 consume: impl FnOnce(&mut dyn std::io::BufRead, &mut dyn DynNestedProgress, &AtomicBool) -> Result<bool, E>,
184 progress: &mut dyn DynNestedProgress,
185 should_interrupt: &AtomicBool,
186) -> Result<(R, bool), Error>
187where
188 R: crate::futures_io::AsyncBufRead + Unpin,
189 E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
190{
191 let mut reader = crate::futures_lite::io::BlockOn::new(reader);
192 let may_read_to_end =
193 consume(&mut reader, progress, should_interrupt).map_err(|err| Error::ConsumePack(err.into()))?;
194 Ok((reader.into_inner(), may_read_to_end))
195}
196
197#[crate::bisync::only_sync]
198fn consume_received_pack<R, E>(
199 mut reader: R,
200 consume: impl FnOnce(&mut dyn std::io::BufRead, &mut dyn DynNestedProgress, &AtomicBool) -> Result<bool, E>,
201 progress: &mut dyn DynNestedProgress,
202 should_interrupt: &AtomicBool,
203) -> Result<(R, bool), Error>
204where
205 R: std::io::BufRead,
206 E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
207{
208 let may_read_to_end =
209 consume(&mut reader, progress, should_interrupt).map_err(|err| Error::ConsumePack(err.into()))?;
210 Ok((reader, may_read_to_end))
211}
212
213#[crate::bisync::only_async]
214async fn read_remaining(reader: &mut (impl crate::futures_io::AsyncRead + Unpin)) -> std::io::Result<()> {
215 crate::futures_lite::io::copy(reader, &mut crate::futures_lite::io::sink())
216 .await
217 .map(|_| ())
218}
219
220#[crate::bisync::only_sync]
221fn read_remaining(reader: &mut impl std::io::Read) -> std::io::Result<()> {
222 std::io::copy(reader, &mut std::io::sink()).map(|_| ())
223}
224
225fn acquire_shallow_lock(shallow_file: &Path) -> Result<gix_lock::File, Error> {
226 gix_lock::File::acquire_to_update_resource(shallow_file, gix_lock::acquire::Fail::Immediately, None)
227 .map_err(Into::into)
228}
229
230fn add_shallow_args(
231 args: &mut Arguments,
232 shallow: &Shallow,
233 shallow_file: &std::path::Path,
234) -> Result<(Option<nonempty::NonEmpty<gix_hash::ObjectId>>, Option<gix_lock::File>), Error> {
235 let expect_change = *shallow != Shallow::NoChange;
236 let shallow_lock = expect_change.then(|| acquire_shallow_lock(shallow_file)).transpose()?;
237
238 let shallow_commits = gix_shallow::read(shallow_file)?;
239 if (shallow_commits.is_some() || expect_change) && !args.can_use_shallow() {
240 return Err(Error::MissingServerFeature {
242 feature: "shallow",
243 description: "shallow clones need server support to remain shallow, otherwise bigger than expected packs are sent effectively unshallowing the repository",
244 });
245 }
246 if let Some(shallow_commits) = &shallow_commits {
247 for commit in shallow_commits.iter() {
248 args.shallow(commit);
249 }
250 }
251 match shallow {
252 Shallow::NoChange => {}
253 Shallow::DepthAtRemote(commits) => args.deepen(commits.get() as usize),
254 Shallow::Deepen(commits) => {
255 args.deepen(*commits as usize);
256 args.deepen_relative();
257 }
258 Shallow::Since { cutoff } => {
259 args.deepen_since(cutoff.seconds);
260 }
261 Shallow::Exclude {
262 remote_refs,
263 since_cutoff,
264 } => {
265 if let Some(cutoff) = since_cutoff {
266 args.deepen_since(cutoff.seconds);
267 }
268 for ref_ in remote_refs {
269 args.deepen_not(ref_.as_ref().as_bstr());
270 }
271 }
272 }
273 Ok((shallow_commits, shallow_lock))
274}
275
276fn setup_remote_progress<'a>(
277 progress: &mut dyn gix_features::progress::DynNestedProgress,
278 reader: &mut Box<dyn ExtendedBufRead<'a> + Unpin + 'a>,
279 should_interrupt: &'a AtomicBool,
280) {
281 reader.set_progress_handler(Some(Box::new({
282 let mut remote_progress = progress.add_child_with_id("remote".to_string(), ProgressId::RemoteProgress.into());
283 move |is_err: bool, data: &[u8]| {
284 crate::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress);
285 if should_interrupt.load(Ordering::Relaxed) {
286 std::ops::ControlFlow::Break(())
287 } else {
288 std::ops::ControlFlow::Continue(())
289 }
290 }
291 }) as HandleProgress<'a>));
292}