Skip to content

Commit ca54b8c

Browse files
authored
Merge pull request #1731 from GitoxideLabs/fix-pack-receive
fix pack receive
2 parents 6822689 + 5c21ebc commit ca54b8c

File tree

8 files changed

+62
-17
lines changed

8 files changed

+62
-17
lines changed

gitoxide-core/src/net.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,20 @@ mod impls {
3636
}
3737

3838
#[cfg(any(feature = "async-client", feature = "blocking-client"))]
39-
pub use gix::protocol::transport::connect;
39+
#[gix::protocol::maybe_async::maybe_async]
40+
pub async fn connect<Url, E>(
41+
url: Url,
42+
options: gix::protocol::transport::client::connect::Options,
43+
) -> Result<
44+
gix::protocol::SendFlushOnDrop<Box<dyn gix::protocol::transport::client::Transport + Send>>,
45+
gix::protocol::transport::client::connect::Error,
46+
>
47+
where
48+
Url: TryInto<gix::url::Url, Error = E>,
49+
gix::url::parse::Error: From<E>,
50+
{
51+
Ok(gix::protocol::SendFlushOnDrop::new(
52+
gix::protocol::transport::connect(url, options).await?,
53+
false,
54+
))
55+
}

gitoxide-core/src/pack/receive.rs

+17-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::pack::receive::protocol::fetch::negotiate;
33
use crate::OutputFormat;
44
use gix::config::tree::Key;
55
use gix::protocol::maybe_async;
6+
use gix::remote::fetch::Error;
67
use gix::DynNestedProgress;
78
pub use gix::{
89
hash::ObjectId,
@@ -64,7 +65,7 @@ where
6465

6566
let agent = gix::protocol::agent(gix::env::agent());
6667
let mut handshake = gix::protocol::fetch::handshake(
67-
&mut transport,
68+
&mut transport.inner,
6869
gix::protocol::credentials::builtin,
6970
vec![("agent".into(), Some(agent.clone()))],
7071
&mut progress,
@@ -85,13 +86,22 @@ where
8586
&fetch_refspecs,
8687
gix::protocol::fetch::Context {
8788
handshake: &mut handshake,
88-
transport: &mut transport,
89+
transport: &mut transport.inner,
8990
user_agent: user_agent.clone(),
9091
trace_packetlines,
9192
},
9293
gix::protocol::fetch::refmap::init::Options::default(),
9394
)
9495
.await?;
96+
97+
if refmap.mappings.is_empty() && !refmap.remote_refs.is_empty() {
98+
return Err(Error::NoMapping {
99+
refspecs: refmap.refspecs.clone(),
100+
num_remote_refs: refmap.remote_refs.len(),
101+
}
102+
.into());
103+
}
104+
95105
let mut negotiate = Negotiate { refmap: &refmap };
96106
gix::protocol::fetch(
97107
&mut negotiate,
@@ -114,7 +124,7 @@ where
114124
&ctx.should_interrupt,
115125
gix::protocol::fetch::Context {
116126
handshake: &mut handshake,
117-
transport: &mut transport,
127+
transport: &mut transport.inner,
118128
user_agent,
119129
trace_packetlines,
120130
},
@@ -140,10 +150,13 @@ impl gix::protocol::fetch::Negotiate for Negotiate<'_> {
140150
})
141151
}
142152

143-
fn add_wants(&mut self, arguments: &mut Arguments, _remote_ref_target_known: &[bool]) {
153+
fn add_wants(&mut self, arguments: &mut Arguments, _remote_ref_target_known: &[bool]) -> bool {
154+
let mut has_want = false;
144155
for id in self.refmap.mappings.iter().filter_map(|m| m.remote.as_id()) {
145156
arguments.want(id);
157+
has_want = true;
146158
}
159+
has_want
147160
}
148161

149162
fn one_round(

gix-protocol/src/fetch/error.rs

+2-2
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

+11-4
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,14 +91,21 @@ 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);
98100
let mut reader = 'negotiation: loop {
99101
let _round = gix_trace::detail!("negotiate round", round = rounds.len() + 1);
100102
progress.step();
101103
progress.set_name(format!("negotiate (round {})", rounds.len() + 1));
104+
if should_interrupt.load(Ordering::Relaxed) {
105+
return Err(Error::Negotiate(negotiate::Error::NegotiationFailed {
106+
rounds: rounds.len(),
107+
}));
108+
}
102109

103110
let is_done = match negotiate.one_round(&mut state, &mut arguments, previous_response.as_ref()) {
104111
Ok((round, is_done)) => {

gix-protocol/src/fetch/negotiate.rs

+8-1
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

+3-1
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,

gix/src/remote/connection/fetch/receive_pack.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,15 @@ impl gix_protocol::fetch::Negotiate for Negotiate<'_, '_, '_> {
274274
)
275275
}
276276

277-
fn add_wants(&mut self, arguments: &mut Arguments, remote_ref_target_known: &[bool]) {
277+
fn add_wants(&mut self, arguments: &mut Arguments, remote_ref_target_known: &[bool]) -> bool {
278278
negotiate::add_wants(
279279
self.objects,
280280
arguments,
281281
self.ref_map,
282282
remote_ref_target_known,
283283
self.shallow,
284284
negotiate::make_refmapping_ignore_predicate(self.tags, self.ref_map),
285-
);
285+
)
286286
}
287287

288288
fn one_round(

tests/journey/gix.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ title "gix (with repository)"
109109

110110
# for some reason, on CI the daemon always shuts down before we can connect,
111111
# or isn't actually ready despite having accepted the first connection already.
112-
(not_on_ci with "git:// protocol"
112+
(with "git:// protocol"
113113
launch-git-daemon
114114
(with "version 1"
115115
it "generates the correct output" && {
@@ -278,7 +278,7 @@ title "gix commit-graph"
278278
)
279279
)
280280
fi
281-
(not_on_ci with "git:// protocol"
281+
(with "git:// protocol"
282282
launch-git-daemon
283283
(with "version 1"
284284
(with "NO output directory"

0 commit comments

Comments
 (0)