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 | /// 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 as | 55 | /// 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. | ||
| 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 | /// 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. |
| @@ -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 | None | 148 | (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_done | 194 | 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_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 | 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) |
| @@ -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"))] |
| 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 | /// 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)] |
| @@ -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_refs | 572 | 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_refs | 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 | assert_eq!( | 587 | assert_eq!( |