History

feat!: provide `fetch::outcome::Negotiate` for details on what happened during negotiation.

We also remove the `negotiation_rounds` field in favor of a far more detailed `fetch::outcome::Negotiate` struct.

Sebastian Thiel committed 3 years ago

Commit 682def01 parent d0ff6bc4 files · +94 −49Browse files

Unified · Split

4 files
  1. gix/src/remote/connection/fetch/mod.rs +48 −10
  2. gix/src/remote/connection/fetch/receive_pack.rs +24 −23
  3. gix/src/remote/fetch.rs +3 −1
  4. gix/tests/remote/fetch.rs +19 −15
gix/src/remote/connection/fetch/mod.rs+48−10
@@ -46,27 +46,25 @@ pub enum Status { · 45 unmodified lines
4646 ///
4747 /// As we could determine that nothing changed without remote interaction, there was no negotiation at all.
4848 NoPackReceived {
49+ /// If `true`, we didn't receive a pack due to dry-run mode being enabled.
50+ dry_run: bool,
51+ /// Information about the pack negotiation phase if negotiation happened at all.
52+ ///
53+ /// It's possible that negotiation didn't have to happen as no reference of interest changed on the server.
54+ negotiate: Option<outcome::Negotiate>,
4955 /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as
5056 /// reported by the remote.
5157 update_refs: refs::update::Outcome,
5258 },
5359 /// There was at least one tip with a new object which we received.
5460 Change {
55− /// The number of rounds it took to minimize the pack to contain only the objects we don't have.
56− negotiation_rounds: usize,
61+ /// Information about the pack negotiation phase.
62+ negotiate: outcome::Negotiate,
5763 /// Information collected while writing the pack and its index.
5864 write_pack_bundle: gix_pack::bundle::write::Outcome,
5965 /// Information collected while updating references.
6066 update_refs: refs::update::Outcome,
6167 },
62− /// A dry run was performed which leaves the local repository without any change
63− /// nor will a pack have been received.
64− DryRun {
65− /// The number of rounds it took to minimize the *would-be-sent*-pack to contain only the objects we don't have.
66− negotiation_rounds: usize,
67− /// Information about what updates to refs would have been done.
68− update_refs: refs::update::Outcome,
69− },
7068 }
7169
7270 /// The outcome of receiving a pack via [`Prepare::receive()`].
@@ -78,6 +76,46 @@ pub struct Outcome { · 5 unmodified lines
7876 pub status: Status,
7977 }
8078
79+/// Additional types related to the outcome of a fetch operation.
80+pub mod outcome {
81+ /// Information about the negotiation phase of a fetch.
82+ ///
83+ /// Note that negotiation can happen even if no pack is ultimately produced.
84+ #[derive(Default, Debug, Clone)]
85+ pub struct Negotiate {
86+ /// The negotiation graph indicating what kind of information 'the algorithm' collected in the end.
87+ pub graph: gix_negotiate::IdMap,
88+ /// Additional information for each round of negotiation.
89+ pub rounds: Vec<negotiate::Round>,
90+ }
91+
92+ ///
93+ pub mod negotiate {
94+ /// Key information about each round in the pack-negotiation.
95+ #[derive(Debug, Clone)]
96+ pub struct Round {
97+ /// The amount of `HAVE` lines sent this round.
98+ ///
99+ /// Each `HAVE` is an object that we tell the server about which would acknowledge each one it has as well.
100+ pub haves_sent: usize,
101+ /// A total counter, over all previous rounds, indicating how many `HAVE`s we sent without seeing a single acknowledgement,
102+ /// i.e. the indication of a common object.
103+ ///
104+ /// This number maybe zero or be lower compared to the previous round if we have received at least one acknowledgement.
105+ pub in_vain: usize,
106+ /// The amount of haves we should send in this round.
107+ ///
108+ /// If the value is lower than `haves_sent` (the `HAVE` lines actually sent), the negotiation algorithm has run out of options
109+ /// which typically indicates the end of the negotiation phase.
110+ pub haves_to_send: usize,
111+ /// If `true`, the server reported, as response to our previous `HAVE`s, that at least one of them is in common by acknowledging it.
112+ ///
113+ /// This may also lead to the server responding with a pack.
114+ pub previous_response_had_at_least_one_in_common: bool,
115+ }
116+ }
117+}
118+
81119 /// The progress ids used in during various steps of the fetch operation.
82120 ///
83121 /// Note that tagged progress isn't very widely available yet, but support can be improved as needed.
gix/src/remote/connection/fetch/receive_pack.rs+24−23
@@ -19,7 +19,8 @@ use crate::{ · 18 unmodified lines
1919 connection::fetch::config,
2020 fetch,
2121 fetch::{
22− negotiate, negotiate::Algorithm, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Shallow, Status,
22+ negotiate, negotiate::Algorithm, outcome, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage,
23+ Shallow, Status,
2324 },
2425 },
2526 Progress, Repository,
@@ -141,11 +142,10 @@ where · 115 unmodified lines
141142 negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),
142143 )?;
143144 let mut previous_response = None::<gix_protocol::fetch::Response>;
144− let mut round = 1;
145− let mut write_pack_bundle = match &action {
145+ let (mut write_pack_bundle, negotiate) = match &action {
146146 negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => {
147147 gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok();
148− None
148+ (None, None)
149149 }
150150 negotiate::Action::MustNegotiate {
151151 remote_ref_target_known,
@@ -158,6 +158,7 @@ where · 6 unmodified lines
158158 &self.shallow,
159159 negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),
160160 );
161+ let mut rounds = Vec::new();
161162 let is_stateless =
162163 arguments.is_stateless(!con.transport.connection_persists_across_multiple_requests());
163164 let mut haves_to_send = gix_negotiate::window_size(is_stateless, None);
@@ -166,7 +167,7 @@ where · 2 unmodified lines
166167 let mut common = is_stateless.then(Vec::new);
167168 let reader = 'negotiation: loop {
168169 progress.step();
169− progress.set_name(format!("negotiate (round {round})"));
170+ progress.set_name(format!("negotiate (round {})", rounds.len() + 1));
170171
171172 let is_done = match negotiate::one_round(
172173 negotiator.deref_mut(),
@@ -182,6 +183,12 @@ where · 9 unmodified lines
182183 }
183184 seen_ack |= ack_seen;
184185 in_vain += haves_sent;
186+ rounds.push(outcome::negotiate::Round {
187+ haves_sent,
188+ in_vain,
189+ haves_to_send,
190+ previous_response_had_at_least_one_in_common: ack_seen,
191+ });
185192 let is_done = haves_sent != haves_to_send || (seen_ack && in_vain >= 256);
186193 haves_to_send = gix_negotiate::window_size(is_stateless, haves_to_send);
187194 is_done
@@ -206,11 +213,9 @@ where · 18 unmodified lines
206213 setup_remote_progress(progress, &mut reader, should_interrupt);
207214 }
208215 break 'negotiation reader;
209− } else {
210− round += 1;
211216 }
212217 };
213− drop(graph);
218+ let graph = graph.detach();
214219 drop(graph_repo);
215220 let previous_response = previous_response.expect("knowledge of a pack means a response was received");
216221 if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() {
@@ -267,7 +272,7 @@ where · 50 unmodified lines
267272 crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?;
268273 }
269274 }
270− write_pack_bundle
275+ (write_pack_bundle, Some(outcome::Negotiate { graph, rounds }))
271276 }
272277 };
273278
@@ -294,21 +299,17 @@ where · 20 unmodified lines
294299
295300 let out = Outcome {
296301 ref_map: std::mem::take(&mut self.ref_map),
297− status: if matches!(self.dry_run, fetch::DryRun::Yes) {
298− assert!(write_pack_bundle.is_none(), "in dry run we never read a bundle");
299− Status::DryRun {
302+ status: match write_pack_bundle {
303+ Some(write_pack_bundle) => Status::Change {
304+ write_pack_bundle,
300305 update_refs,
301− negotiation_rounds: round,
302− }
303− } else {
304− match write_pack_bundle {
305− Some(write_pack_bundle) => Status::Change {
306− write_pack_bundle,
307− update_refs,
308− negotiation_rounds: round,
309− },
310− None => Status::NoPackReceived { update_refs },
311− }
306+ negotiate: negotiate.expect("if we have a pack, we always negotiated it"),
307+ },
308+ None => Status::NoPackReceived {
309+ dry_run: matches!(self.dry_run, fetch::DryRun::Yes),
310+ negotiate,
311+ update_refs,
312+ },
312313 },
313314 };
314315 Ok(out)
gix/src/remote/fetch.rs+3−1
@@ -11,7 +11,9 @@ pub mod negotiate { · 10 unmodified lines
1111 }
1212
1313 #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
14−pub use super::connection::fetch::{prepare, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status};
14+pub use super::connection::fetch::{
15+ outcome, prepare, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status,
16+};
1517
1618 /// If `Yes`, don't really make changes but do as much as possible to get an idea of what would be done.
1719 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
gix/tests/remote/fetch.rs+19−15
@@ -137,12 +137,13 @@ mod blocking_and_async_io { · 136 unmodified lines
137137
138138 match out.status {
139139 Status::Change {
140− negotiation_rounds,
140+ negotiate,
141141 write_pack_bundle,
142142 ..
143143 } => {
144144 assert_eq!(
145− negotiation_rounds, 1,
145+ negotiate.rounds.len(),
146+ 1,
146147 "we don't really have a way to see that tips from alternates were added, I think"
147148 );
148149 assert_eq!(
@@ -215,11 +216,12 @@ mod blocking_and_async_io { · 66 unmodified lines
215216 match changes.status {
216217 Status::Change {
217218 write_pack_bundle,
218− negotiation_rounds,
219+ negotiate,
219220 ..
220221 } => {
221222 assert_eq!(
222− negotiation_rounds, expected_negotiation_rounds,
223+ negotiate.rounds.len(),
224+ expected_negotiation_rounds,
223225 "we need multiple rounds"
224226 );
225227 // the server only has our `b1` and an extra commit or two.
@@ -389,8 +391,9 @@ mod blocking_and_async_io { · 163 unmodified lines
389391 .await?;
390392
391393 match res.status {
392− fetch::Status::NoPackReceived { update_refs } => {
394+ fetch::Status::NoPackReceived { update_refs, negotiate: _, dry_run } => {
393395 assert_eq!(update_refs.edits.len(), expected_ref_count, "{shallow_args:?}|{fetch_tags:?}");
396+ assert!(!dry_run, "we actually perform the operation");
394397 },
395398 _ => unreachable!(
396399 "{shallow_args:?}|{fetch_tags:?}: default negotiation is able to realize nothing is required and doesn't get to receiving a pack"
@@ -446,8 +449,8 @@ mod blocking_and_async_io { · 49 unmodified lines
446449 .await?;
447450
448451 match res.status {
449− gix::remote::fetch::Status::Change { write_pack_bundle, update_refs, negotiation_rounds } => {
450− assert_eq!(negotiation_rounds, 1);
452+ gix::remote::fetch::Status::Change { write_pack_bundle, update_refs, negotiate } => {
453+ assert_eq!(negotiate.rounds.len(), 1);
451454 assert_eq!(write_pack_bundle.index.data_hash, hex_to_id(expected_data_hash), );
452455 assert_eq!(write_pack_bundle.index.num_objects, 3 + num_objects_offset, "{fetch_tags:?}");
453456 assert!(write_pack_bundle.data_path.as_deref().map_or(false, std::path::Path::is_file));
@@ -530,9 +533,9 @@ mod blocking_and_async_io { · 76 unmodified lines
530533 fetch::Status::Change {
531534 write_pack_bundle,
532535 update_refs,
533− negotiation_rounds,
536+ negotiate,
534537 } => {
535− assert_eq!(negotiation_rounds, 1);
538+ assert_eq!(negotiate.rounds.len(), 1);
536539 assert_eq!(write_pack_bundle.pack_version, gix::odb::pack::data::Version::V2);
537540 assert_eq!(write_pack_bundle.object_hash, repo.object_hash());
538541 assert_eq!(write_pack_bundle.index.num_objects, 4, "{dry_run}: this value is 4 when git does it with 'consecutive' negotiation style, but could be 33 if completely naive.");
@@ -568,16 +571,17 @@ mod blocking_and_async_io { · 29 unmodified lines
568571
569572 update_refs
570573 }
571− fetch::Status::DryRun {
574+ fetch::Status::NoPackReceived {
575+ dry_run,
572576 update_refs,
573− negotiation_rounds,
577+ negotiate: _,
574578 } => {
575− assert_eq!(negotiation_rounds, 1);
579+ assert!(
580+ dry_run,
581+ "the only reason we receive no pack is if we are in dry-run mode"
582+ );
576583 update_refs
577584 }
578− fetch::Status::NoPackReceived { .. } => {
579− unreachable!("we firmly expect changes here, as the other origin has changes")
580− }
581585 };
582586
583587 assert_eq!(