Skip to content

Commit 151cbe1

Browse files
committed
fix!: assure that empty fetches due to no objects wanted are terminated early
1 parent 71ad8f3 commit 151cbe1

File tree

4 files changed

+19
-8
lines changed

4 files changed

+19
-8
lines changed

gix-protocol/src/fetch/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ pub enum Error {
2121
LockShallowFile(#[from] gix_lock::acquire::Error),
2222
#[error("Receiving objects from shallow remotes is prohibited due to the value of `clone.rejectShallow`")]
2323
RejectShallowRemote,
24-
#[error("Failed to consume the pack sent by the remove")]
25-
ConsumePack(Box<dyn std::error::Error + Send + Sync + 'static>),
24+
#[error("Failed to consume the pack sent by the remote")]
25+
ConsumePack(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
2626
#[error("Failed to read remaining bytes in stream")]
2727
ReadRemainingBytes(#[source] std::io::Error),
2828
}

gix-protocol/src/fetch/function.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ use std::sync::atomic::{AtomicBool, Ordering};
2222
/// * …update local refs
2323
/// * …end the interaction after the fetch
2424
///
25-
/// Note that the interaction will never be ended, even on error or failure, leaving it up to the caller to do that, maybe
25+
/// **Note that the interaction will never be ended**, even on error or failure, leaving it up to the caller to do that, maybe
2626
/// with the help of [`SendFlushOnDrop`](crate::SendFlushOnDrop) which can wrap `transport`.
2727
/// Generally, the `transport` is left in a state that allows for more commands to be run.
2828
///
29-
/// Return `Ok(None)` if there was nothing to do because all remote refs are at the same state as they are locally, or `Ok(Some(outcome))`
30-
/// to inform about all the changes that were made.
29+
/// Return `Ok(None)` if there was nothing to do because all remote refs are at the same state as they are locally,
30+
/// or there was nothing wanted, or `Ok(Some(outcome))` to inform about all the changes that were made.
3131
#[maybe_async::maybe_async]
3232
pub async fn fetch<P, T, E>(
3333
negotiate: &mut impl Negotiate,
@@ -91,7 +91,9 @@ where
9191
negotiate::Action::MustNegotiate {
9292
remote_ref_target_known,
9393
} => {
94-
negotiate.add_wants(&mut arguments, remote_ref_target_known);
94+
if !negotiate.add_wants(&mut arguments, remote_ref_target_known) {
95+
return Ok(None);
96+
}
9597
let mut rounds = Vec::new();
9698
let is_stateless = arguments.is_stateless(!transport.connection_persists_across_multiple_requests());
9799
let mut state = negotiate::one_round::State::new(is_stateless);

gix-protocol/src/fetch/negotiate.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,17 +284,21 @@ pub fn make_refmapping_ignore_predicate(fetch_tags: Tags, ref_map: &RefMap) -> i
284284
/// * `ref_map` is the state of refs as known on the remote.
285285
/// * `shallow` defines if the history should be shallow.
286286
/// * `mapping_is_ignored` is typically initialized with [`make_refmapping_ignore_predicate`].
287+
///
288+
/// Returns `true` if at least one [want](crate::fetch::Arguments::want()) was added, or `false` otherwise.
289+
/// Note that not adding a single want can make the remote hang, so it's avoided on the client side by ending the fetch operation.
287290
pub fn add_wants(
288291
objects: &impl gix_object::FindHeader,
289292
arguments: &mut crate::fetch::Arguments,
290293
ref_map: &RefMap,
291294
remote_ref_target_known: &[bool],
292295
shallow: &Shallow,
293296
mapping_is_ignored: impl Fn(&refmap::Mapping) -> bool,
294-
) {
297+
) -> bool {
295298
// When using shallow, we can't exclude `wants` as the remote won't send anything then. Thus, we have to resend everything
296299
// we have as want instead to get exactly the same graph, but possibly deepened.
297300
let is_shallow = !matches!(shallow, Shallow::NoChange);
301+
let mut has_want = false;
298302
let wants = ref_map
299303
.mappings
300304
.iter()
@@ -306,13 +310,15 @@ pub fn add_wants(
306310
if !arguments.can_use_ref_in_want() || matches!(want.remote, refmap::Source::ObjectId(_)) {
307311
if let Some(id) = id_on_remote {
308312
arguments.want(id);
313+
has_want = true;
309314
}
310315
} else {
311316
arguments.want_ref(
312317
want.remote
313318
.as_name()
314319
.expect("name available if this isn't an object id"),
315320
);
321+
has_want = true;
316322
}
317323
let id_is_annotated_tag_we_have = id_on_remote
318324
.and_then(|id| objects.try_header(id).ok().flatten().map(|h| (id, h)))
@@ -324,6 +330,7 @@ pub fn add_wants(
324330
arguments.have(tag_on_remote);
325331
}
326332
}
333+
has_want
327334
}
328335

329336
/// Remove all commits that are more recent than the cut-off, which is the commit time of the oldest common commit we have with the server.

gix-protocol/src/fetch/types.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ mod with_fetch {
7070
/// Typically invokes [`negotiate::mark_complete_and_common_ref()`].
7171
fn mark_complete_and_common_ref(&mut self) -> Result<negotiate::Action, negotiate::Error>;
7272
/// Typically invokes [`negotiate::add_wants()`].
73-
fn add_wants(&mut self, arguments: &mut fetch::Arguments, remote_ref_target_known: &[bool]);
73+
/// Returns `true` if wants were added, or `false` if the negotiation should be aborted.
74+
#[must_use]
75+
fn add_wants(&mut self, arguments: &mut fetch::Arguments, remote_ref_target_known: &[bool]) -> bool;
7476
/// Typically invokes [`negotiate::one_round()`].
7577
fn one_round(
7678
&mut self,

0 commit comments

Comments
 (0)