We also remove the `negotiation_rounds` field in favor of a far more detailed `fetch::outcome::Negotiate` struct.
Unified · Split
| @@ -46,27 +46,25 @@ pub enum Status { · 45 unmodified lines | ||
| 46 | 46 | /// |
| 47 | 47 | /// As we could determine that nothing changed without remote interaction, there was no negotiation at all. |
| 48 | 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 | 55 | /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as |
| 50 | 56 | /// reported by the remote. |
| 51 | 57 | update_refs: refs::update::Outcome, |
| 52 | 58 | }, |
| 53 | 59 | /// There was at least one tip with a new object which we received. |
| 54 | 60 | 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, | |
| 57 | 63 | /// Information collected while writing the pack and its index. |
| 58 | 64 | write_pack_bundle: gix_pack::bundle::write::Outcome, |
| 59 | 65 | /// Information collected while updating references. |
| 60 | 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 | 70 | /// The outcome of receiving a pack via [`Prepare::receive()`]. |
| @@ -78,6 +76,46 @@ pub struct Outcome { · 5 unmodified lines | ||
| 78 | 76 | pub status: Status, |
| 79 | 77 | } |
| 80 | 78 | |
| 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 | + | |
| 81 | 119 | /// The progress ids used in during various steps of the fetch operation. |
| 82 | 120 | /// |
| 83 | 121 | /// Note that tagged progress isn't very widely available yet, but support can be improved as needed. |
| @@ -19,7 +19,8 @@ use crate::{ · 18 unmodified lines | ||
| 19 | 19 | connection::fetch::config, |
| 20 | 20 | fetch, |
| 21 | 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 | 26 | Progress, Repository, |
| @@ -141,11 +142,10 @@ where · 115 unmodified lines | ||
| 141 | 142 | negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map), |
| 142 | 143 | )?; |
| 143 | 144 | 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 { | |
| 146 | 146 | negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => { |
| 147 | 147 | gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); |
| 148 | − None | |
| 148 | + (None, None) | |
| 149 | 149 | } |
| 150 | 150 | negotiate::Action::MustNegotiate { |
| 151 | 151 | remote_ref_target_known, |
| @@ -158,6 +158,7 @@ where · 6 unmodified lines | ||
| 158 | 158 | &self.shallow, |
| 159 | 159 | negotiate::make_refmapping_ignore_predicate(con.remote.fetch_tags, &self.ref_map), |
| 160 | 160 | ); |
| 161 | + let mut rounds = Vec::new(); | |
| 161 | 162 | let is_stateless = |
| 162 | 163 | arguments.is_stateless(!con.transport.connection_persists_across_multiple_requests()); |
| 163 | 164 | let mut haves_to_send = gix_negotiate::window_size(is_stateless, None); |
| @@ -166,7 +167,7 @@ where · 2 unmodified lines | ||
| 166 | 167 | let mut common = is_stateless.then(Vec::new); |
| 167 | 168 | let reader = 'negotiation: loop { |
| 168 | 169 | progress.step(); |
| 169 | − progress.set_name(format!("negotiate (round {round})")); | |
| 170 | + progress.set_name(format!("negotiate (round {})", rounds.len() + 1)); | |
| 170 | 171 | |
| 171 | 172 | let is_done = match negotiate::one_round( |
| 172 | 173 | negotiator.deref_mut(), |
| @@ -182,6 +183,12 @@ where · 9 unmodified lines | ||
| 182 | 183 | } |
| 183 | 184 | seen_ack |= ack_seen; |
| 184 | 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 | 192 | let is_done = haves_sent != haves_to_send || (seen_ack && in_vain >= 256); |
| 186 | 193 | haves_to_send = gix_negotiate::window_size(is_stateless, haves_to_send); |
| 187 | 194 | is_done |
| @@ -206,11 +213,9 @@ where · 18 unmodified lines | ||
| 206 | 213 | setup_remote_progress(progress, &mut reader, should_interrupt); |
| 207 | 214 | } |
| 208 | 215 | break 'negotiation reader; |
| 209 | − } else { | |
| 210 | − round += 1; | |
| 211 | 216 | } |
| 212 | 217 | }; |
| 213 | − drop(graph); | |
| 218 | + let graph = graph.detach(); | |
| 214 | 219 | drop(graph_repo); |
| 215 | 220 | let previous_response = previous_response.expect("knowledge of a pack means a response was received"); |
| 216 | 221 | if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() { |
| @@ -267,7 +272,7 @@ where · 50 unmodified lines | ||
| 267 | 272 | crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?; |
| 268 | 273 | } |
| 269 | 274 | } |
| 270 | − write_pack_bundle | |
| 275 | + (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 | 300 | let out = Outcome { |
| 296 | 301 | 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, | |
| 300 | 305 | 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 | + }, | |
| 312 | 313 | }, |
| 313 | 314 | }; |
| 314 | 315 | Ok(out) |
| @@ -11,7 +11,9 @@ pub mod negotiate { · 10 unmodified lines | ||
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | #[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 | +}; | |
| 15 | 17 | |
| 16 | 18 | /// If `Yes`, don't really make changes but do as much as possible to get an idea of what would be done. |
| 17 | 19 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| @@ -137,12 +137,13 @@ mod blocking_and_async_io { · 136 unmodified lines | ||
| 137 | 137 | |
| 138 | 138 | match out.status { |
| 139 | 139 | Status::Change { |
| 140 | − negotiation_rounds, | |
| 140 | + negotiate, | |
| 141 | 141 | write_pack_bundle, |
| 142 | 142 | .. |
| 143 | 143 | } => { |
| 144 | 144 | assert_eq!( |
| 145 | − negotiation_rounds, 1, | |
| 145 | + negotiate.rounds.len(), | |
| 146 | + 1, | |
| 146 | 147 | "we don't really have a way to see that tips from alternates were added, I think" |
| 147 | 148 | ); |
| 148 | 149 | assert_eq!( |
| @@ -215,11 +216,12 @@ mod blocking_and_async_io { · 66 unmodified lines | ||
| 215 | 216 | match changes.status { |
| 216 | 217 | Status::Change { |
| 217 | 218 | write_pack_bundle, |
| 218 | − negotiation_rounds, | |
| 219 | + negotiate, | |
| 219 | 220 | .. |
| 220 | 221 | } => { |
| 221 | 222 | assert_eq!( |
| 222 | − negotiation_rounds, expected_negotiation_rounds, | |
| 223 | + negotiate.rounds.len(), | |
| 224 | + expected_negotiation_rounds, | |
| 223 | 225 | "we need multiple rounds" |
| 224 | 226 | ); |
| 225 | 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 | 391 | .await?; |
| 390 | 392 | |
| 391 | 393 | match res.status { |
| 392 | − fetch::Status::NoPackReceived { update_refs } => { | |
| 394 | + fetch::Status::NoPackReceived { update_refs, negotiate: _, dry_run } => { | |
| 393 | 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 | 398 | _ => unreachable!( |
| 396 | 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 | 449 | .await?; |
| 447 | 450 | |
| 448 | 451 | 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); | |
| 451 | 454 | assert_eq!(write_pack_bundle.index.data_hash, hex_to_id(expected_data_hash), ); |
| 452 | 455 | assert_eq!(write_pack_bundle.index.num_objects, 3 + num_objects_offset, "{fetch_tags:?}"); |
| 453 | 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 | 533 | fetch::Status::Change { |
| 531 | 534 | write_pack_bundle, |
| 532 | 535 | update_refs, |
| 533 | − negotiation_rounds, | |
| 536 | + negotiate, | |
| 534 | 537 | } => { |
| 535 | − assert_eq!(negotiation_rounds, 1); | |
| 538 | + assert_eq!(negotiate.rounds.len(), 1); | |
| 536 | 539 | assert_eq!(write_pack_bundle.pack_version, gix::odb::pack::data::Version::V2); |
| 537 | 540 | assert_eq!(write_pack_bundle.object_hash, repo.object_hash()); |
| 538 | 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 | 572 | update_refs |
| 570 | 573 | } |
| 571 | − fetch::Status::DryRun { | |
| 574 | + fetch::Status::NoPackReceived { | |
| 575 | + dry_run, | |
| 572 | 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 | 583 | 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 | 587 | assert_eq!( |