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
46 ///46 ///
47 /// As we could determine that nothing changed without remote interaction, there was no negotiation at all.47 /// As we could determine that nothing changed without remote interaction, there was no negotiation at all.
48 NoPackReceived {48 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>,
49 /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as55 /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as
50 /// reported by the remote.56 /// reported by the remote.
51 update_refs: refs::update::Outcome,57 update_refs: refs::update::Outcome,
52 },58 },
53 /// There was at least one tip with a new object which we received.59 /// There was at least one tip with a new object which we received.
54 Change {60 Change {
55 /// The number of rounds it took to minimize the pack to contain only the objects we don't have.61 /// Information about the pack negotiation phase.
56 negotiation_rounds: usize,62 negotiate: outcome::Negotiate,
57 /// Information collected while writing the pack and its index.63 /// Information collected while writing the pack and its index.
58 write_pack_bundle: gix_pack::bundle::write::Outcome,64 write_pack_bundle: gix_pack::bundle::write::Outcome,
59 /// Information collected while updating references.65 /// Information collected while updating references.
60 update_refs: refs::update::Outcome,66 update_refs: refs::update::Outcome,
61 },67 },
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 },
70}68}
71 69
72/// The outcome of receiving a pack via [`Prepare::receive()`].70/// The outcome of receiving a pack via [`Prepare::receive()`].
@@ -78,6 +76,46 @@ pub struct Outcome { · 5 unmodified lines
78 pub status: Status,76 pub status: Status,
79}77}
80 78
79/// Additional types related to the outcome of a fetch operation.
80pub 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
81/// The progress ids used in during various steps of the fetch operation.119/// The progress ids used in during various steps of the fetch operation.
82///120///
83/// Note that tagged progress isn't very widely available yet, but support can be improved as needed.121/// 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
19 connection::fetch::config,19 connection::fetch::config,
20 fetch,20 fetch,
21 fetch::{21 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,
23 },24 },
24 },25 },
25 Progress, Repository,26 Progress, Repository,
@@ -141,11 +142,10 @@ where · 115 unmodified lines
141 negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),142 negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),
142 )?;143 )?;
143 let mut previous_response = None::<gix_protocol::fetch::Response>;144 let mut previous_response = None::<gix_protocol::fetch::Response>;
144 let mut round = 1;145 let (mut write_pack_bundle, negotiate) = match &action {
145 let mut write_pack_bundle = match &action {
146 negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => {146 negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => {
147 gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok();147 gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok();
148 None148 (None, None)
149 }149 }
150 negotiate::Action::MustNegotiate {150 negotiate::Action::MustNegotiate {
151 remote_ref_target_known,151 remote_ref_target_known,
@@ -158,6 +158,7 @@ where · 6 unmodified lines
158 &self.shallow,158 &self.shallow,
159 negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),159 negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map),
160 );160 );
161 let mut rounds = Vec::new();
161 let is_stateless =162 let is_stateless =
162 arguments.is_stateless(!con.transport.connection_persists_across_multiple_requests());163 arguments.is_stateless(!con.transport.connection_persists_across_multiple_requests());
163 let mut haves_to_send = gix_negotiate::window_size(is_stateless, None);164 let mut haves_to_send = gix_negotiate::window_size(is_stateless, None);
@@ -166,7 +167,7 @@ where · 2 unmodified lines
166 let mut common = is_stateless.then(Vec::new);167 let mut common = is_stateless.then(Vec::new);
167 let reader = 'negotiation: loop {168 let reader = 'negotiation: loop {
168 progress.step();169 progress.step();
169 progress.set_name(format!("negotiate (round {round})"));170 progress.set_name(format!("negotiate (round {})", rounds.len() + 1));
170 171
171 let is_done = match negotiate::one_round(172 let is_done = match negotiate::one_round(
172 negotiator.deref_mut(),173 negotiator.deref_mut(),
@@ -182,6 +183,12 @@ where · 9 unmodified lines
182 }183 }
183 seen_ack |= ack_seen;184 seen_ack |= ack_seen;
184 in_vain += haves_sent;185 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 });
185 let is_done = haves_sent != haves_to_send || (seen_ack && in_vain >= 256);192 let is_done = haves_sent != haves_to_send || (seen_ack && in_vain >= 256);
186 haves_to_send = gix_negotiate::window_size(is_stateless, haves_to_send);193 haves_to_send = gix_negotiate::window_size(is_stateless, haves_to_send);
187 is_done194 is_done
@@ -206,11 +213,9 @@ where · 18 unmodified lines
206 setup_remote_progress(progress, &mut reader, should_interrupt);213 setup_remote_progress(progress, &mut reader, should_interrupt);
207 }214 }
208 break 'negotiation reader;215 break 'negotiation reader;
209 } else {
210 round += 1;
211 }216 }
212 };217 };
213 drop(graph);218 let graph = graph.detach();
214 drop(graph_repo);219 drop(graph_repo);
215 let previous_response = previous_response.expect("knowledge of a pack means a response was received");220 let previous_response = previous_response.expect("knowledge of a pack means a response was received");
216 if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() {221 if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() {
@@ -267,7 +272,7 @@ where · 50 unmodified lines
267 crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?;272 crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?;
268 }273 }
269 }274 }
270 write_pack_bundle275 (write_pack_bundle, Some(outcome::Negotiate { graph, rounds }))
271 }276 }
272 };277 };
273 278
@@ -294,21 +299,17 @@ where · 20 unmodified lines
294 299
295 let out = Outcome {300 let out = Outcome {
296 ref_map: std::mem::take(&mut self.ref_map),301 ref_map: std::mem::take(&mut self.ref_map),
297 status: if matches!(self.dry_run, fetch::DryRun::Yes) {302 status: match write_pack_bundle {
298 assert!(write_pack_bundle.is_none(), "in dry run we never read a bundle");303 Some(write_pack_bundle) => Status::Change {
299 Status::DryRun {304 write_pack_bundle,
300 update_refs,305 update_refs,
301 negotiation_rounds: round,306 negotiate: negotiate.expect("if we have a pack, we always negotiated it"),
302 }307 },
303 } else {308 None => Status::NoPackReceived {
304 match write_pack_bundle {309 dry_run: matches!(self.dry_run, fetch::DryRun::Yes),
305 Some(write_pack_bundle) => Status::Change {310 negotiate,
306 write_pack_bundle,311 update_refs,
307 update_refs,312 },
308 negotiation_rounds: round,
309 },
310 None => Status::NoPackReceived { update_refs },
311 }
312 },313 },
313 };314 };
314 Ok(out)315 Ok(out)
gix/src/remote/fetch.rs+3−1
@@ -11,7 +11,9 @@ pub mod negotiate { · 10 unmodified lines
11}11}
12 12
13#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]13#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
14pub use super::connection::fetch::{prepare, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status};14pub use super::connection::fetch::{
15 outcome, prepare, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status,
16};
15 17
16/// If `Yes`, don't really make changes but do as much as possible to get an idea of what would be done.18/// If `Yes`, don't really make changes but do as much as possible to get an idea of what would be done.
17#[derive(Debug, Copy, Clone, PartialEq, Eq)]19#[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
137 137
138 match out.status {138 match out.status {
139 Status::Change {139 Status::Change {
140 negotiation_rounds,140 negotiate,
141 write_pack_bundle,141 write_pack_bundle,
142 ..142 ..
143 } => {143 } => {
144 assert_eq!(144 assert_eq!(
145 negotiation_rounds, 1,145 negotiate.rounds.len(),
146 1,
146 "we don't really have a way to see that tips from alternates were added, I think"147 "we don't really have a way to see that tips from alternates were added, I think"
147 );148 );
148 assert_eq!(149 assert_eq!(
@@ -215,11 +216,12 @@ mod blocking_and_async_io { · 66 unmodified lines
215 match changes.status {216 match changes.status {
216 Status::Change {217 Status::Change {
217 write_pack_bundle,218 write_pack_bundle,
218 negotiation_rounds,219 negotiate,
219 ..220 ..
220 } => {221 } => {
221 assert_eq!(222 assert_eq!(
222 negotiation_rounds, expected_negotiation_rounds,223 negotiate.rounds.len(),
224 expected_negotiation_rounds,
223 "we need multiple rounds"225 "we need multiple rounds"
224 );226 );
225 // the server only has our `b1` and an extra commit or two.227 // the server only has our `b1` and an extra commit or two.
@@ -389,8 +391,9 @@ mod blocking_and_async_io { · 163 unmodified lines
389 .await?;391 .await?;
390 392
391 match res.status {393 match res.status {
392 fetch::Status::NoPackReceived { update_refs } => {394 fetch::Status::NoPackReceived { update_refs, negotiate: _, dry_run } => {
393 assert_eq!(update_refs.edits.len(), expected_ref_count, "{shallow_args:?}|{fetch_tags:?}");395 assert_eq!(update_refs.edits.len(), expected_ref_count, "{shallow_args:?}|{fetch_tags:?}");
396 assert!(!dry_run, "we actually perform the operation");
394 },397 },
395 _ => unreachable!(398 _ => unreachable!(
396 "{shallow_args:?}|{fetch_tags:?}: default negotiation is able to realize nothing is required and doesn't get to receiving a pack"399 "{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
446 .await?;449 .await?;
447 450
448 match res.status {451 match res.status {
449 gix::remote::fetch::Status::Change { write_pack_bundle, update_refs, negotiation_rounds } => {452 gix::remote::fetch::Status::Change { write_pack_bundle, update_refs, negotiate } => {
450 assert_eq!(negotiation_rounds, 1);453 assert_eq!(negotiate.rounds.len(), 1);
451 assert_eq!(write_pack_bundle.index.data_hash, hex_to_id(expected_data_hash), );454 assert_eq!(write_pack_bundle.index.data_hash, hex_to_id(expected_data_hash), );
452 assert_eq!(write_pack_bundle.index.num_objects, 3 + num_objects_offset, "{fetch_tags:?}");455 assert_eq!(write_pack_bundle.index.num_objects, 3 + num_objects_offset, "{fetch_tags:?}");
453 assert!(write_pack_bundle.data_path.as_deref().map_or(false, std::path::Path::is_file));456 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
530 fetch::Status::Change {533 fetch::Status::Change {
531 write_pack_bundle,534 write_pack_bundle,
532 update_refs,535 update_refs,
533 negotiation_rounds,536 negotiate,
534 } => {537 } => {
535 assert_eq!(negotiation_rounds, 1);538 assert_eq!(negotiate.rounds.len(), 1);
536 assert_eq!(write_pack_bundle.pack_version, gix::odb::pack::data::Version::V2);539 assert_eq!(write_pack_bundle.pack_version, gix::odb::pack::data::Version::V2);
537 assert_eq!(write_pack_bundle.object_hash, repo.object_hash());540 assert_eq!(write_pack_bundle.object_hash, repo.object_hash());
538 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.");541 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
568 571
569 update_refs572 update_refs
570 }573 }
571 fetch::Status::DryRun {574 fetch::Status::NoPackReceived {
575 dry_run,
572 update_refs,576 update_refs,
573 negotiation_rounds,577 negotiate: _,
574 } => {578 } => {
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 );
576 update_refs583 update_refs
577 }584 }
578 fetch::Status::NoPackReceived { .. } => {
579 unreachable!("we firmly expect changes here, as the other origin has changes")
580 }
581 };585 };
582 586
583 assert_eq!(587 assert_eq!(