Skip to content

Commit db69e31

Browse files
committed
fix: no-want detection for negotiation phase is now consistent.
It being inconsistent was a reason for 'failing to parse server response' which was empty as we didn't provide any wants to the server, but didn't detect that case in the initial negotiation-preparation phase. Turns out we didn't detect it as our special handling of implicit tags was not done in the negotiation-preparation phase. The fix consists of unifying the filtering phase to all places that needed, so the preparation phase outcome is now consistent with what would have come later.
1 parent fe59956 commit db69e31

File tree

4 files changed

+38
-25
lines changed

4 files changed

+38
-25
lines changed

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

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub(crate) fn mark_complete_and_common_ref(
6868
graph: &mut gix_negotiate::Graph<'_>,
6969
ref_map: &fetch::RefMap,
7070
shallow: &fetch::Shallow,
71+
mapping_is_ignored: impl Fn(&fetch::Mapping) -> bool,
7172
) -> Result<Action, Error> {
7273
if let fetch::Shallow::Deepen(0) = shallow {
7374
// Avoid deepening (relative) with zero as it seems to upset the server. Git also doesn't actually
@@ -98,9 +99,11 @@ pub(crate) fn mark_complete_and_common_ref(
9899
r.target().try_id().map(ToOwned::to_owned)
99100
});
100101

101-
// Like git, we don't let known unchanged mappings participate in the tree traversal
102-
if want_id.zip(have_id).map_or(true, |(want, have)| want != have) {
103-
num_mappings_with_change += 1;
102+
if !mapping_is_ignored(mapping) {
103+
// Like git, we don't let known unchanged mappings participate in the tree traversal
104+
if want_id.zip(have_id).map_or(true, |(want, have)| want != have) {
105+
num_mappings_with_change += 1;
106+
}
104107
}
105108

106109
if let Some(commit) = want_id
@@ -114,7 +117,6 @@ pub(crate) fn mark_complete_and_common_ref(
114117
}
115118
}
116119

117-
// If any kind of shallowing operation is desired, the server may still create a pack for us.
118120
if matches!(shallow, Shallow::NoChange) {
119121
if num_mappings_with_change == 0 {
120122
return Ok(Action::NoChange);
@@ -167,40 +169,50 @@ pub(crate) fn mark_complete_and_common_ref(
167169
})
168170
}
169171

170-
/// Add all `wants` to `arguments`, which is the unpeeled direct target that the advertised remote ref points to.
171-
pub(crate) fn add_wants(
172-
repo: &crate::Repository,
173-
arguments: &mut gix_protocol::fetch::Arguments,
174-
ref_map: &fetch::RefMap,
175-
mapping_known: &[bool],
176-
shallow: &fetch::Shallow,
172+
/// Create a predicate that checks if a refspec mapping should be ignored.
173+
///
174+
/// We want to ignore mappings during negotiation if they would be handled implicitly by the server, which is the case
175+
/// when tags would be sent implicitly due to `Tags::Included`.
176+
pub(crate) fn make_refmapping_ignore_predicate(
177177
fetch_tags: fetch::Tags,
178-
) {
178+
ref_map: &fetch::RefMap,
179+
) -> impl Fn(&fetch::Mapping) -> bool + '_ {
179180
// With included tags, we have to keep mappings of tags to handle them later when updating refs, but we don't want to
180181
// explicitly `want` them as the server will determine by itself which tags are pointing to a commit it wants to send.
181182
// If we would not exclude implicit tag mappings like this, we would get too much of the graph.
182183
let tag_refspec_to_ignore = matches!(fetch_tags, crate::remote::fetch::Tags::Included)
183184
.then(|| fetch_tags.to_refspec())
184185
.flatten();
186+
move |mapping| {
187+
tag_refspec_to_ignore.map_or(false, |tag_spec| {
188+
mapping
189+
.spec_index
190+
.implicit_index()
191+
.and_then(|idx| ref_map.extra_refspecs.get(idx))
192+
.map_or(false, |spec| spec.to_ref() == tag_spec)
193+
})
194+
}
195+
}
185196

197+
/// Add all `wants` to `arguments`, which is the unpeeled direct target that the advertised remote ref points to.
198+
pub(crate) fn add_wants(
199+
repo: &crate::Repository,
200+
arguments: &mut gix_protocol::fetch::Arguments,
201+
ref_map: &fetch::RefMap,
202+
mapping_known: &[bool],
203+
shallow: &fetch::Shallow,
204+
mapping_is_ignored: impl Fn(&fetch::Mapping) -> bool,
205+
) {
186206
// When using shallow, we can't exclude `wants` as the remote won't send anything then. Thus we have to resend everything
187207
// we have as want instead to get exactly the same graph, but possibly deepened.
188208
let is_shallow = !matches!(shallow, fetch::Shallow::NoChange);
189209
let wants = ref_map
190210
.mappings
191211
.iter()
192212
.zip(mapping_known)
193-
.filter_map(|(m, known)| (is_shallow || !*known).then_some(m));
213+
.filter_map(|(m, known)| (is_shallow || !*known).then_some(m))
214+
.filter(|m| !mapping_is_ignored(m));
194215
for want in wants {
195-
// Here we ignore implicit tag mappings if needed.
196-
if tag_refspec_to_ignore.map_or(false, |tag_spec| {
197-
want.spec_index
198-
.implicit_index()
199-
.and_then(|idx| ref_map.extra_refspecs.get(idx))
200-
.map_or(false, |spec| spec.to_ref() == tag_spec)
201-
}) {
202-
continue;
203-
}
204216
let id_on_remote = want.remote.as_id();
205217
if !arguments.can_use_ref_in_want() || matches!(want.remote, fetch::Source::ObjectId(_)) {
206218
if let Some(id) = id_on_remote {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ where
138138
&mut graph,
139139
&self.ref_map,
140140
&self.shallow,
141+
negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),
141142
)?;
142143
let mut previous_response = None::<gix_protocol::fetch::Response>;
143144
let mut round = 1;
@@ -155,7 +156,7 @@ where
155156
&self.ref_map,
156157
remote_ref_target_known,
157158
&self.shallow,
158-
con.remote.fetch_tags,
159+
negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),
159160
);
160161
let is_stateless =
161162
arguments.is_stateless(!con.transport.connection_persists_across_multiple_requests());

gix/src/remote/fetch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub mod negotiate {
66
pub use super::super::connection::fetch::negotiate::Error;
77
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
88
pub(crate) use super::super::connection::fetch::negotiate::{
9-
add_wants, mark_complete_and_common_ref, one_round, Action,
9+
add_wants, make_refmapping_ignore_predicate, mark_complete_and_common_ref, one_round, Action,
1010
};
1111
}
1212

gix/tests/remote/fetch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ mod blocking_and_async_io {
341341
(fetch::Tags::Included, 7),
342342
(fetch::Tags::All, 7),
343343
] {
344-
let (repo, _tmp) = repo_rw("two-origins");
344+
let (repo, _tmp) = repo_rw("two-origins"); // TODO: also try shallow clones
345345
let mut remote = into_daemon_remote_if_async(
346346
repo.head()?
347347
.into_remote(Fetch)

0 commit comments

Comments
 (0)