Unified · Split
| @@ -637,10 +637,14 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README. · 636 unmodified lines | |||
| 637 | * [x] find remote itself | 637 | * [x] find remote itself |
| 638 | - [ ] respect `branch.<name>.merge` in the returned remote. | 638 | - [ ] respect `branch.<name>.merge` in the returned remote. |
| 639 | * **remotes** | 639 | * **remotes** |
| 640 | * [ ] clone | 640 | * [x] clone |
| 641 | * [ ] shallow | 641 | * [ ] shallow |
| 642 | * [ ] include-tags when shallow is used (needs separate fetch) | ||
| 643 | * [ ] prune non-existing shallow commits | ||
| 642 | * [ ] [bundles](https://git-scm.com/docs/git-bundle) | 644 | * [ ] [bundles](https://git-scm.com/docs/git-bundle) |
| 643 | * [x] fetch | 645 | * [x] fetch |
| 646 | * [ ] 'ref-in-want' | ||
| 647 | * [ ] standard negotiation algorithms (right now we only have a 'naive' one) | ||
| 644 | * [ ] push | 648 | * [ ] push |
| 645 | * [x] ls-refs | 649 | * [x] ls-refs |
| 646 | * [x] ls-refs with ref-spec filter | 650 | * [x] ls-refs with ref-spec filter |
| @@ -121,6 +121,7 @@ impl PrepareFetch { · 120 unmodified lines | |||
| 121 | .with_reflog_message(RefLogMessage::Override { | 121 | .with_reflog_message(RefLogMessage::Override { |
| 122 | message: reflog_message.clone(), | 122 | message: reflog_message.clone(), |
| 123 | }) | 123 | }) |
| 124 | .with_shallow(self.shallow.clone()) | ||
| 124 | .receive(should_interrupt)?; | 125 | .receive(should_interrupt)?; |
| 125 | 126 | ||
| 126 | util::append_config_to_repo_config(repo, config); | 127 | util::append_config_to_repo_config(repo, config); |
| @@ -184,6 +185,12 @@ impl PrepareFetch { · 57 unmodified lines | |||
| 184 | self.remote_name = Some(crate::remote::name::validated(name)?); | 185 | self.remote_name = Some(crate::remote::name::validated(name)?); |
| 185 | Ok(self) | 186 | Ok(self) |
| 186 | } | 187 | } |
| 188 | |||
| 189 | /// Make this clone a shallow one with the respective choice of shallow-ness. | ||
| 190 | pub fn with_shallow(mut self, shallow: crate::remote::fetch::Shallow) -> Self { | ||
| 191 | self.shallow = shallow; | ||
| 192 | self | ||
| 193 | } | ||
| 187 | } | 194 | } |
| 188 | 195 | ||
| 189 | /// Consumption | 196 | /// Consumption |
| @@ -1,7 +1,7 @@ | |||
| 1 | #![allow(clippy::result_large_err)] | 1 | #![allow(clippy::result_large_err)] |
| 2 | use std::convert::TryInto; | 2 | use std::convert::TryInto; |
| 3 | 3 | ||
| 4 | use crate::{bstr::BString, config::tree::gitoxide}; | 4 | use crate::{bstr::BString, config::tree::gitoxide, remote}; |
| 5 | 5 | ||
| 6 | type ConfigureRemoteFn = | 6 | type ConfigureRemoteFn = |
| 7 | Box<dyn FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, Box<dyn std::error::Error + Send + Sync>>>; | 7 | Box<dyn FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, Box<dyn std::error::Error + Send + Sync>>>; |
| @@ -22,6 +22,9 @@ pub struct PrepareFetch { · 14 unmodified lines | |||
| 22 | /// The url to clone from | 22 | /// The url to clone from |
| 23 | #[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))] | 23 | #[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))] |
| 24 | url: gix_url::Url, | 24 | url: gix_url::Url, |
| 25 | /// How to handle shallow clones | ||
| 26 | #[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))] | ||
| 27 | shallow: remote::fetch::Shallow, | ||
| 25 | } | 28 | } |
| 26 | 29 | ||
| 27 | /// The error returned by [`PrepareFetch::new()`]. | 30 | /// The error returned by [`PrepareFetch::new()`]. |
| @@ -99,6 +102,7 @@ impl PrepareFetch { · 71 unmodified lines | |||
| 99 | repo: Some(repo), | 102 | repo: Some(repo), |
| 100 | remote_name: None, | 103 | remote_name: None, |
| 101 | configure_remote: None, | 104 | configure_remote: None, |
| 105 | shallow: remote::fetch::Shallow::NoChange, | ||
| 102 | }) | 106 | }) |
| 103 | } | 107 | } |
| 104 | } | 108 | } |
| @@ -7,6 +7,8 @@ impl Clone { · 6 unmodified lines | |||
| 7 | /// The `clone.defaultRemoteName` key. | 7 | /// The `clone.defaultRemoteName` key. |
| 8 | pub const DEFAULT_REMOTE_NAME: keys::RemoteName = | 8 | pub const DEFAULT_REMOTE_NAME: keys::RemoteName = |
| 9 | keys::RemoteName::new_remote_name("defaultRemoteName", &config::Tree::CLONE); | 9 | keys::RemoteName::new_remote_name("defaultRemoteName", &config::Tree::CLONE); |
| 10 | /// The `clone.rejectShallow` key. | ||
| 11 | pub const REJECT_SHALLOW: keys::Boolean = keys::Boolean::new_boolean("rejectShallow", &config::Tree::CLONE); | ||
| 10 | } | 12 | } |
| 11 | 13 | ||
| 12 | impl Section for Clone { | 14 | impl Section for Clone { |
| @@ -15,6 +17,6 @@ impl Section for Clone { · 2 unmodified lines | |||
| 15 | } | 17 | } |
| 16 | 18 | ||
| 17 | fn keys(&self) -> &[&dyn Key] { | 19 | fn keys(&self) -> &[&dyn Key] { |
| 18 | &[&Self::DEFAULT_REMOTE_NAME] | 20 | &[&Self::DEFAULT_REMOTE_NAME, &Self::REJECT_SHALLOW] |
| 19 | } | 21 | } |
| 20 | } | 22 | } |
| @@ -28,6 +28,21 @@ pub enum Error { · 27 unmodified lines | |||
| 28 | path: std::path::PathBuf, | 28 | path: std::path::PathBuf, |
| 29 | source: std::io::Error, | 29 | source: std::io::Error, |
| 30 | }, | 30 | }, |
| 31 | #[error(transparent)] | ||
| 32 | ShallowOpen(#[from] crate::shallow::open::Error), | ||
| 33 | #[error("Server lack feature {feature:?}: {description}")] | ||
| 34 | MissingServerFeature { | ||
| 35 | feature: &'static str, | ||
| 36 | description: &'static str, | ||
| 37 | }, | ||
| 38 | #[error("Could not write 'shallow' file to incorporate remote updates after fetching")] | ||
| 39 | WriteShallowFile(#[from] crate::shallow::write::Error), | ||
| 40 | #[error("'shallow' file could not be locked in preparation for writing changes")] | ||
| 41 | LockShallowFile(#[from] gix_lock::acquire::Error), | ||
| 42 | #[error("Could not obtain configuration to learn if shallow remotes should be rejected")] | ||
| 43 | RejectShallowRemoteConfig(#[from] config::boolean::Error), | ||
| 44 | #[error("Receiving objects from shallow remotes is prohibited due to the value of `clone.rejectShallow`")] | ||
| 45 | RejectShallowRemote, | ||
| 31 | } | 46 | } |
| 32 | 47 | ||
| 33 | impl gix_protocol::transport::IsSpuriousError for Error { | 48 | impl gix_protocol::transport::IsSpuriousError for Error { |
| @@ -149,6 +149,7 @@ where · 148 unmodified lines | |||
| 149 | dry_run: DryRun::No, | 149 | dry_run: DryRun::No, |
| 150 | reflog_message: None, | 150 | reflog_message: None, |
| 151 | write_packed_refs: WritePackedRefs::Never, | 151 | write_packed_refs: WritePackedRefs::Never, |
| 152 | shallow: Default::default(), | ||
| 152 | }) | 153 | }) |
| 153 | } | 154 | } |
| 154 | } | 155 | } |
| @@ -179,6 +180,7 @@ where · 24 unmodified lines | |||
| 179 | dry_run: DryRun, | 180 | dry_run: DryRun, |
| 180 | reflog_message: Option<RefLogMessage>, | 181 | reflog_message: Option<RefLogMessage>, |
| 181 | write_packed_refs: WritePackedRefs, | 182 | write_packed_refs: WritePackedRefs, |
| 183 | shallow: remote::fetch::Shallow, | ||
| 182 | } | 184 | } |
| 183 | 185 | ||
| 184 | /// Builder | 186 | /// Builder |
| @@ -212,6 +214,14 @@ where · 27 unmodified lines | |||
| 212 | self.reflog_message = reflog_message.into(); | 214 | self.reflog_message = reflog_message.into(); |
| 213 | self | 215 | self |
| 214 | } | 216 | } |
| 217 | |||
| 218 | /// Define what to do when the current repository is a shallow clone. | ||
| 219 | /// | ||
| 220 | /// *Has no effect if the current repository is not as shallow clone.* | ||
| 221 | pub fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self { | ||
| 222 | self.shallow = shallow; | ||
| 223 | self | ||
| 224 | } | ||
| 215 | } | 225 | } |
| 216 | 226 | ||
| 217 | impl<'remote, 'repo, T, P> Drop for Prepare<'remote, 'repo, T, P> | 227 | impl<'remote, 'repo, T, P> Drop for Prepare<'remote, 'repo, T, P> |
| @@ -1,3 +1,5 @@ | |||
| 1 | use gix_refspec::RefSpec; | ||
| 2 | |||
| 1 | /// The way the negotiation is performed | 3 | /// The way the negotiation is performed |
| 2 | #[derive(Copy, Clone)] | 4 | #[derive(Copy, Clone)] |
| 3 | pub(crate) enum Algorithm { | 5 | pub(crate) enum Algorithm { |
| @@ -16,6 +18,7 @@ pub enum Error { · 12 unmodified lines | |||
| 16 | /// Negotiate one round with `algo` by looking at `ref_map` and adjust `arguments` to contain the haves and wants. | 18 | /// Negotiate one round with `algo` by looking at `ref_map` and adjust `arguments` to contain the haves and wants. |
| 17 | /// If this is not the first round, the `previous_response` is set with the last recorded server response. | 19 | /// If this is not the first round, the `previous_response` is set with the last recorded server response. |
| 18 | /// Returns `true` if the negotiation is done from our side so the server won't keep asking. | 20 | /// Returns `true` if the negotiation is done from our side so the server won't keep asking. |
| 21 | #[allow(clippy::too_many_arguments)] | ||
| 19 | pub(crate) fn one_round( | 22 | pub(crate) fn one_round( |
| 20 | algo: Algorithm, | 23 | algo: Algorithm, |
| 21 | round: usize, | 24 | round: usize, |
| @@ -24,13 +27,15 @@ pub(crate) fn one_round( · 2 unmodified lines | |||
| 24 | fetch_tags: crate::remote::fetch::Tags, | 27 | fetch_tags: crate::remote::fetch::Tags, |
| 25 | arguments: &mut gix_protocol::fetch::Arguments, | 28 | arguments: &mut gix_protocol::fetch::Arguments, |
| 26 | _previous_response: Option<&gix_protocol::fetch::Response>, | 29 | _previous_response: Option<&gix_protocol::fetch::Response>, |
| 30 | wants_shallow_change: Option<&[RefSpec]>, | ||
| 27 | ) -> Result<bool, Error> { | 31 | ) -> Result<bool, Error> { |
| 28 | let tag_refspec_to_ignore = fetch_tags | 32 | let tag_refspec_to_ignore = fetch_tags |
| 29 | .to_refspec() | 33 | .to_refspec() |
| 30 | .filter(|_| matches!(fetch_tags, crate::remote::fetch::Tags::Included)); | 34 | .filter(|_| matches!(fetch_tags, crate::remote::fetch::Tags::Included)); |
| 35 | let non_wildcard_specs_only = wants_shallow_change; | ||
| 31 | match algo { | 36 | match algo { |
| 32 | Algorithm::Naive => { | 37 | Algorithm::Naive => { |
| 33 | assert_eq!(round, 1, "Naive always finishes after the first round, and claims."); | 38 | assert_eq!(round, 1, "Naive always finishes after the first round, it claims."); |
| 34 | let mut has_missing_tracking_branch = false; | 39 | let mut has_missing_tracking_branch = false; |
| 35 | for mapping in &ref_map.mappings { | 40 | for mapping in &ref_map.mappings { |
| 36 | if tag_refspec_to_ignore.map_or(false, |tag_spec| { | 41 | if tag_refspec_to_ignore.map_or(false, |tag_spec| { |
| @@ -42,6 +47,14 @@ pub(crate) fn one_round( · 5 unmodified lines | |||
| 42 | }) { | 47 | }) { |
| 43 | continue; | 48 | continue; |
| 44 | } | 49 | } |
| 50 | if non_wildcard_specs_only | ||
| 51 | .and_then(|refspecs| mapping.spec_index.get(refspecs, &ref_map.extra_refspecs)) | ||
| 52 | .map_or(false, |spec| { | ||
| 53 | spec.to_ref().local().map_or(false, |ref_| ref_.contains(&b'*')) | ||
| 54 | }) | ||
| 55 | { | ||
| 56 | continue; | ||
| 57 | } | ||
| 45 | let have_id = mapping.local.as_ref().and_then(|name| { | 58 | let have_id = mapping.local.as_ref().and_then(|name| { |
| 46 | repo.find_reference(name) | 59 | repo.find_reference(name) |
| 47 | .ok() | 60 | .ok() |
| @@ -50,7 +63,7 @@ pub(crate) fn one_round( · 2 unmodified lines | |||
| 50 | match have_id { | 63 | match have_id { |
| 51 | Some(have_id) => { | 64 | Some(have_id) => { |
| 52 | if let Some(want_id) = mapping.remote.as_id() { | 65 | if let Some(want_id) = mapping.remote.as_id() { |
| 53 | if want_id != have_id { | 66 | if want_id != have_id || wants_shallow_change.is_some() { |
| 54 | arguments.want(want_id); | 67 | arguments.want(want_id); |
| 55 | arguments.have(have_id); | 68 | arguments.have(have_id); |
| 56 | } | 69 | } |
| @@ -65,10 +78,11 @@ pub(crate) fn one_round( · 8 unmodified lines | |||
| 65 | } | 78 | } |
| 66 | } | 79 | } |
| 67 | 80 | ||
| 68 | if has_missing_tracking_branch { | 81 | if has_missing_tracking_branch || (wants_shallow_change.is_some() && arguments.is_empty()) { |
| 69 | if let Ok(Some(r)) = repo.head_ref() { | 82 | if let Ok(Some(r)) = repo.head_ref() { |
| 70 | if let Some(id) = r.target().try_id() { | 83 | if let Some(id) = r.target().try_id() { |
| 71 | arguments.have(id); | 84 | arguments.have(id); |
| 85 | arguments.want(id); | ||
| 72 | } | 86 | } |
| 73 | } | 87 | } |
| 74 | } | 88 | } |
| @@ -1,16 +1,18 @@ | |||
| 1 | use std::sync::atomic::AtomicBool; | 1 | use std::sync::atomic::AtomicBool; |
| 2 | 2 | ||
| 3 | use gix_odb::FindExt; | 3 | use gix_odb::FindExt; |
| 4 | use gix_protocol::fetch::Arguments; | ||
| 4 | use gix_protocol::transport::client::Transport; | 5 | use gix_protocol::transport::client::Transport; |
| 5 | 6 | ||
| 7 | use crate::config::tree::Clone; | ||
| 6 | use crate::{ | 8 | use crate::{ |
| 7 | remote, | 9 | remote, |
| 8 | remote::{ | 10 | remote::{ |
| 9 | connection::fetch::config, | 11 | connection::fetch::config, |
| 10 | fetch, | 12 | fetch, |
| 11 | fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status}, | 13 | fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Shallow, Status}, |
| 12 | }, | 14 | }, |
| 13 | Progress, | 15 | Progress, Repository, |
| 14 | }; | 16 | }; |
| 15 | 17 | ||
| 16 | impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> | 18 | impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> |
| @@ -82,10 +84,17 @@ where · 65 unmodified lines | |||
| 82 | let mut arguments = gix_protocol::fetch::Arguments::new(protocol_version, fetch_features); | 84 | let mut arguments = gix_protocol::fetch::Arguments::new(protocol_version, fetch_features); |
| 83 | if matches!(con.remote.fetch_tags, crate::remote::fetch::Tags::Included) { | 85 | if matches!(con.remote.fetch_tags, crate::remote::fetch::Tags::Included) { |
| 84 | if !arguments.can_use_include_tag() { | 86 | if !arguments.can_use_include_tag() { |
| 85 | unimplemented!("we expect servers to support 'include-tag', otherwise we have to implement another pass to fetch attached tags separately"); | 87 | return Err(Error::MissingServerFeature { |
| 88 | feature: "include-tag", | ||
| 89 | description: | ||
| 90 | // NOTE: if this is an issue, we could probably do what's proposed here. | ||
| 91 | "To make this work we would have to implement another pass to fetch attached tags separately", | ||
| 92 | }); | ||
| 86 | } | 93 | } |
| 87 | arguments.use_include_tag(); | 94 | arguments.use_include_tag(); |
| 88 | } | 95 | } |
| 96 | let (shallow_commits, mut shallow_lock) = add_shallow_args(&mut arguments, &self.shallow, repo)?; | ||
| 97 | |||
| 89 | let mut previous_response = None::<gix_protocol::fetch::Response>; | 98 | let mut previous_response = None::<gix_protocol::fetch::Response>; |
| 90 | let mut round = 1; | 99 | let mut round = 1; |
| 91 | 100 | ||
| @@ -108,6 +117,7 @@ where · 16 unmodified lines | |||
| 108 | con.remote.fetch_tags, | 117 | con.remote.fetch_tags, |
| 109 | &mut arguments, | 118 | &mut arguments, |
| 110 | previous_response.as_ref(), | 119 | previous_response.as_ref(), |
| 120 | (self.shallow != Shallow::NoChange).then(|| con.remote.refspecs(remote::Direction::Fetch)), | ||
| 111 | ) { | 121 | ) { |
| 112 | Ok(_) if arguments.is_empty() => { | 122 | Ok(_) if arguments.is_empty() => { |
| 113 | gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); | 123 | gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); |
| @@ -146,11 +156,26 @@ where · 32 unmodified lines | |||
| 146 | if !sideband_all { | 156 | if !sideband_all { |
| 147 | setup_remote_progress(progress, &mut reader); | 157 | setup_remote_progress(progress, &mut reader); |
| 148 | } | 158 | } |
| 159 | previous_response = Some(response); | ||
| 149 | break 'negotiation reader; | 160 | break 'negotiation reader; |
| 150 | } else { | 161 | } else { |
| 151 | previous_response = Some(response); | 162 | previous_response = Some(response); |
| 152 | } | 163 | } |
| 153 | }; | 164 | }; |
| 165 | let previous_response = previous_response.expect("knowledge of a pack means a response was received"); | ||
| 166 | if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() { | ||
| 167 | let reject_shallow_remote = repo | ||
| 168 | .config | ||
| 169 | .resolved | ||
| 170 | .boolean_filter_by_key("clone.rejectShallow", &mut repo.filter_config_section()) | ||
| 171 | .map(|val| Clone::REJECT_SHALLOW.enrich_error(val)) | ||
| 172 | .transpose()? | ||
| 173 | .unwrap_or(false); | ||
| 174 | if reject_shallow_remote { | ||
| 175 | return Err(Error::RejectShallowRemote); | ||
| 176 | } | ||
| 177 | shallow_lock = acquire_shallow_lock(repo).map(Some)?; | ||
| 178 | } | ||
| 154 | 179 | ||
| 155 | let options = gix_pack::bundle::write::Options { | 180 | let options = gix_pack::bundle::write::Options { |
| 156 | thread_limit: config::index_threads(repo)?, | 181 | thread_limit: config::index_threads(repo)?, |
| @@ -187,6 +212,12 @@ where · 30 unmodified lines | |||
| 187 | gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); | 212 | gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); |
| 188 | } | 213 | } |
| 189 | 214 | ||
| 215 | if let Some(shallow_lock) = shallow_lock { | ||
| 216 | if !previous_response.shallow_updates().is_empty() { | ||
| 217 | crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?; | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 190 | let update_refs = refs::update( | 221 | let update_refs = refs::update( |
| 191 | repo, | 222 | repo, |
| 192 | self.reflog_message | 223 | self.reflog_message |
| @@ -221,6 +252,57 @@ where · 28 unmodified lines | |||
| 221 | } | 252 | } |
| 222 | } | 253 | } |
| 223 | 254 | ||
| 255 | fn acquire_shallow_lock(repo: &Repository) -> Result<gix_lock::File, Error> { | ||
| 256 | gix_lock::File::acquire_to_update_resource(repo.shallow_file(), gix_lock::acquire::Fail::Immediately, None) | ||
| 257 | .map_err(Into::into) | ||
| 258 | } | ||
| 259 | |||
| 260 | fn add_shallow_args( | ||
| 261 | args: &mut Arguments, | ||
| 262 | shallow: &Shallow, | ||
| 263 | repo: &Repository, | ||
| 264 | ) -> Result<(Option<crate::shallow::Commits>, Option<gix_lock::File>), Error> { | ||
| 265 | let expect_change = *shallow != Shallow::NoChange; | ||
| 266 | let shallow_lock = expect_change.then(|| acquire_shallow_lock(repo)).transpose()?; | ||
| 267 | |||
| 268 | let shallow_commits = repo.shallow_commits()?; | ||
| 269 | if (shallow_commits.is_some() || expect_change) && !args.can_use_shallow() { | ||
| 270 | // NOTE: if this is an issue, we can always unshallow the repo ourselves. | ||
| 271 | return Err(Error::MissingServerFeature { | ||
| 272 | feature: "shallow", | ||
| 273 | description: "shallow clones need server support to remain shallow, otherwise bigger than expected packs are sent effectively unshallowing the repository", | ||
| 274 | }); | ||
| 275 | } | ||
| 276 | if let Some(shallow_commits) = &shallow_commits { | ||
| 277 | for commit in shallow_commits.iter() { | ||
| 278 | args.shallow(commit); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | match shallow { | ||
| 282 | Shallow::NoChange => {} | ||
| 283 | Shallow::DepthAtRemote(commits) => args.deepen(commits.get() as usize), | ||
| 284 | Shallow::Deepen(commits) => { | ||
| 285 | args.deepen(*commits as usize); | ||
| 286 | args.deepen_relative(); | ||
| 287 | } | ||
| 288 | Shallow::Since { cutoff } => { | ||
| 289 | args.deepen_since(cutoff.seconds_since_unix_epoch as usize); | ||
| 290 | } | ||
| 291 | Shallow::Exclude { | ||
| 292 | remote_refs, | ||
| 293 | since_cutoff, | ||
| 294 | } => { | ||
| 295 | if let Some(cutoff) = since_cutoff { | ||
| 296 | args.deepen_since(cutoff.seconds_since_unix_epoch as usize); | ||
| 297 | } | ||
| 298 | for ref_ in remote_refs { | ||
| 299 | args.deepen_not(ref_.as_ref().as_bstr()); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | Ok((shallow_commits, shallow_lock)) | ||
| 304 | } | ||
| 305 | |||
| 224 | fn setup_remote_progress<P>( | 306 | fn setup_remote_progress<P>( |
| 225 | progress: &mut P, | 307 | progress: &mut P, |
| 226 | reader: &mut Box<dyn gix_protocol::transport::client::ExtendedBufRead + Unpin + '_>, | 308 | reader: &mut Box<dyn gix_protocol::transport::client::ExtendedBufRead + Unpin + '_>, |
| @@ -52,6 +52,52 @@ impl Tags { · 51 unmodified lines | |||
| 52 | } | 52 | } |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | /// Describe how shallow clones are handled when fetching, with variants defining how the *shallow boundary* is handled. | ||
| 56 | /// | ||
| 57 | /// The *shallow boundary* is a set of commits whose parents are not present in the repository. | ||
| 58 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
| 59 | pub enum Shallow { | ||
| 60 | /// Fetch all changes from the remote without affecting the shallow boundary at all. | ||
| 61 | NoChange, | ||
| 62 | /// Receive update to `depth` commits in the history of the refs to fetch (from the viewpoint of the remote), | ||
| 63 | /// with the value of `1` meaning to receive only the commit a ref is pointing to. | ||
| 64 | /// | ||
| 65 | /// This may update the shallow boundary to increase or decrease the amount of available history. | ||
| 66 | DepthAtRemote(std::num::NonZeroU32), | ||
| 67 | /// Increase the number of commits and thus expand the shallow boundary by `depth` commits as seen from our local | ||
| 68 | /// shallow boundary, with a value of `0` having no effect. | ||
| 69 | Deepen(u32), | ||
| 70 | /// Set the shallow boundary at the `cutoff` time, meaning that there will be no commits beyond that time. | ||
| 71 | Since { | ||
| 72 | /// The date beyond which there will be no history. | ||
| 73 | cutoff: gix_date::Time, | ||
| 74 | }, | ||
| 75 | /// Receive all history excluding all commits reachable from `remote_refs`. These can be long or short | ||
| 76 | /// ref names or tag names. | ||
| 77 | Exclude { | ||
| 78 | /// The ref names to exclude, short or long. Note that ambiguous short names will cause the remote to abort | ||
| 79 | /// without an error message being transferred (because the protocol does not support it) | ||
| 80 | remote_refs: Vec<gix_ref::PartialName>, | ||
| 81 | /// If some, this field has the same meaning as [`Shallow::Since`] which can be used in combination | ||
| 82 | /// with excluded references. | ||
| 83 | since_cutoff: Option<gix_date::Time>, | ||
| 84 | }, | ||
| 85 | } | ||
| 86 | |||
| 87 | impl Default for Shallow { | ||
| 88 | fn default() -> Self { | ||
| 89 | Shallow::NoChange | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | impl Shallow { | ||
| 94 | /// Produce a variant that causes the repository to loose its shallow boundary, effectively by extending it | ||
| 95 | /// beyond all limits. | ||
| 96 | pub fn undo() -> Self { | ||
| 97 | Shallow::DepthAtRemote((i32::MAX as u32).try_into().expect("valid at compile time")) | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 55 | /// Information about the relationship between our refspecs, and remote references with their local counterparts. | 101 | /// Information about the relationship between our refspecs, and remote references with their local counterparts. |
| 56 | #[derive(Default, Debug, Clone)] | 102 | #[derive(Default, Debug, Clone)] |
| 57 | #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] | 103 | #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] |
| @@ -12,8 +12,12 @@ impl Repository { · 11 unmodified lines | |||
| 12 | .map_or(false, |m| m.is_file() && m.len() > 0) | 12 | .map_or(false, |m| m.is_file() && m.len() > 0) |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | /// Return a shared list of shallow commits which is updated automatically if the in-memory snapshot has become stale as the underlying file | 15 | /// Return a shared list of shallow commits which is updated automatically if the in-memory snapshot has become stale |
| 16 | /// on disk has changed. | 16 | /// as the underlying file on disk has changed. |
| 17 | /// | ||
| 18 | /// The list of shallow commits represents the shallow boundary, beyond which we are lacking all (parent) commits. | ||
| 19 | /// Note that the list is never empty, as `Ok(None)` is returned in that case indicating the repository | ||
| 20 | /// isn't a shallow clone. | ||
| 17 | /// | 21 | /// |
| 18 | /// The shared list is shared across all clones of this repository. | 22 | /// The shared list is shared across all clones of this repository. |
| 19 | pub fn shallow_commits(&self) -> Result<Option<crate::shallow::Commits>, crate::shallow::open::Error> { | 23 | pub fn shallow_commits(&self) -> Result<Option<crate::shallow::Commits>, crate::shallow::open::Error> { |
| @@ -4,6 +4,73 @@ pub(crate) type CommitsStorage = · 3 unmodified lines | |||
| 4 | /// sorted to allow bisecting. | 4 | /// sorted to allow bisecting. |
| 5 | pub type Commits = gix_features::fs::SharedSnapshot<Vec<gix_hash::ObjectId>>; | 5 | pub type Commits = gix_features::fs::SharedSnapshot<Vec<gix_hash::ObjectId>>; |
| 6 | 6 | ||
| 7 | /// | ||
| 8 | #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] | ||
| 9 | pub mod write { | ||
| 10 | pub(crate) mod function { | ||
| 11 | use crate::shallow::{write::Error, Commits}; | ||
| 12 | use gix_protocol::fetch::response::ShallowUpdate; | ||
| 13 | use std::io::Write; | ||
| 14 | |||
| 15 | /// Write the previously obtained (possibly non-existing) `shallow_commits` to the shallow `file` | ||
| 16 | /// after applying all `updates`. | ||
| 17 | /// | ||
| 18 | /// If this leaves the list of shallow commits empty, the file is removed. | ||
| 19 | /// | ||
| 20 | /// ### Deviation | ||
| 21 | /// | ||
| 22 | /// Git also prunes the set of shallow commits while writing, we don't until we support some sort of pruning. | ||
| 23 | pub fn write( | ||
| 24 | mut file: gix_lock::File, | ||
| 25 | shallow_commits: Option<Commits>, | ||
| 26 | updates: &[ShallowUpdate], | ||
| 27 | ) -> Result<(), Error> { | ||
| 28 | let mut shallow_commits = shallow_commits.map(|sc| (**sc).to_owned()).unwrap_or_default(); | ||
| 29 | for update in updates { | ||
| 30 | match update { | ||
| 31 | ShallowUpdate::Shallow(id) => { | ||
| 32 | shallow_commits.push(*id); | ||
| 33 | } | ||
| 34 | ShallowUpdate::Unshallow(id) => shallow_commits.retain(|oid| oid != id), | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | if shallow_commits.is_empty() { | ||
| 39 | if let Err(err) = std::fs::remove_file(file.resource_path()) { | ||
| 40 | if err.kind() != std::io::ErrorKind::NotFound { | ||
| 41 | return Err(err.into()); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } else { | ||
| 45 | shallow_commits.sort(); | ||
| 46 | let mut buf = Vec::<u8>::new(); | ||
| 47 | for commit in shallow_commits { | ||
| 48 | commit.write_hex_to(&mut buf).map_err(Error::Io)?; | ||
| 49 | buf.push(b'\n'); | ||
| 50 | } | ||
| 51 | file.write_all(&buf).map_err(Error::Io)?; | ||
| 52 | file.flush()?; | ||
| 53 | } | ||
| 54 | file.commit()?; | ||
| 55 | Ok(()) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// The error returned by [`write()`][crate::shallow::write()]. | ||
| 60 | #[derive(Debug, thiserror::Error)] | ||
| 61 | #[allow(missing_docs)] | ||
| 62 | pub enum Error { | ||
| 63 | #[error(transparent)] | ||
| 64 | Commit(#[from] gix_lock::commit::Error<gix_lock::File>), | ||
| 65 | #[error("Could not remove an empty shallow file")] | ||
| 66 | RemoveEmpty(#[from] std::io::Error), | ||
| 67 | #[error("Failed to write object id to shallow file")] | ||
| 68 | Io(std::io::Error), | ||
| 69 | } | ||
| 70 | } | ||
| 71 | #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] | ||
| 72 | pub use write::function::write; | ||
| 73 | |||
| 7 | /// | 74 | /// |
| 8 | pub mod open { | 75 | pub mod open { |
| 9 | /// The error returned by [`Repository::shallow_commits()`][crate::Repository::shallow_commits()]. | 76 | /// The error returned by [`Repository::shallow_commits()`][crate::Repository::shallow_commits()]. |
| @@ -2,12 +2,203 @@ use crate::{remote, util::restricted}; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | #[cfg(feature = "blocking-network-client")] | 3 | #[cfg(feature = "blocking-network-client")] |
| 4 | mod blocking_io { | 4 | mod blocking_io { |
| 5 | use gix::remote::fetch::SpecIndex; | 5 | use gix::config::tree::{Clone, Core, Init, Key}; |
| 6 | use gix::remote::Direction; | ||
| 6 | use gix_object::bstr::ByteSlice; | 7 | use gix_object::bstr::ByteSlice; |
| 7 | use gix_ref::TargetRef; | 8 | use gix_ref::TargetRef; |
| 9 | use std::sync::atomic::AtomicBool; | ||
| 8 | 10 | ||
| 9 | use crate::{remote, util::restricted}; | 11 | use crate::{remote, util::restricted}; |
| 10 | 12 | ||
| 13 | use crate::util::hex_to_id; | ||
| 14 | use gix::remote::fetch::{Shallow, SpecIndex}; | ||
| 15 | |||
| 16 | #[test] | ||
| 17 | fn fetch_shallow_no_checkout_then_unshallow() -> crate::Result { | ||
| 18 | let tmp = gix_testtools::tempfile::TempDir::new()?; | ||
| 19 | let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default()); | ||
| 20 | let remote_name = "special"; | ||
| 21 | let desired_fetch_tags = gix::remote::fetch::Tags::Included; | ||
| 22 | let mut prepare = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())? | ||
| 23 | .with_remote_name(remote_name)? | ||
| 24 | .configure_remote({ | ||
| 25 | let called_configure_remote = called_configure_remote; | ||
| 26 | move |r| { | ||
| 27 | called_configure_remote.store(true, std::sync::atomic::Ordering::Relaxed); | ||
| 28 | let r = r | ||
| 29 | .with_refspecs(Some("+refs/tags/b-tag:refs/tags/b-tag"), gix::remote::Direction::Fetch)? | ||
| 30 | .with_fetch_tags(desired_fetch_tags); | ||
| 31 | Ok(r) | ||
| 32 | } | ||
| 33 | }) | ||
| 34 | .with_shallow(Shallow::DepthAtRemote(2.try_into().expect("non-zero"))); | ||
| 35 | let (repo, _out) = prepare.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?; | ||
| 36 | drop(prepare); | ||
| 37 | |||
| 38 | assert_eq!( | ||
| 39 | repo.shallow_commits()?.expect("shallow").as_slice(), | ||
| 40 | [ | ||
| 41 | hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"), | ||
| 42 | hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"), | ||
| 43 | hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"), | ||
| 44 | hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85") | ||
| 45 | ], | ||
| 46 | "shallow information is written" | ||
| 47 | ); | ||
| 48 | |||
| 49 | let shallow_commit_count = repo.head_id()?.ancestors().all()?.count(); | ||
| 50 | let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?; | ||
| 51 | |||
| 52 | remote | ||
| 53 | .connect(Direction::Fetch, gix::progress::Discard)? | ||
| 54 | .prepare_fetch(Default::default())? | ||
| 55 | .with_shallow(Shallow::undo()) | ||
| 56 | .receive(&AtomicBool::default())?; | ||
| 57 | |||
| 58 | assert!(repo.shallow_commits()?.is_none(), "the repo isn't shallow anymore"); | ||
| 59 | assert!( | ||
| 60 | !repo.is_shallow(), | ||
| 61 | "both methods agree - if there are no shallow commits, it shouldn't think the repo is shallow" | ||
| 62 | ); | ||
| 63 | assert!( | ||
| 64 | repo.head_id()?.ancestors().all()?.count() > shallow_commit_count, | ||
| 65 | "there are more commits now as the history is complete" | ||
| 66 | ); | ||
| 67 | |||
| 68 | Ok(()) | ||
| 69 | } | ||
| 70 | |||
| 71 | #[test] | ||
| 72 | fn from_shallow_prohibited_with_option() -> crate::Result { | ||
| 73 | let tmp = gix_testtools::tempfile::TempDir::new()?; | ||
| 74 | let err = gix::clone::PrepareFetch::new( | ||
| 75 | remote::repo("base.shallow").path(), | ||
| 76 | tmp.path(), | ||
| 77 | gix::create::Kind::Bare, | ||
| 78 | Default::default(), | ||
| 79 | gix::open::Options::isolated().config_overrides([Clone::REJECT_SHALLOW.validated_assignment_fmt(&true)?]), | ||
| 80 | )? | ||
| 81 | .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default()) | ||
| 82 | .unwrap_err(); | ||
| 83 | assert!( | ||
| 84 | matches!( | ||
| 85 | err, | ||
| 86 | gix::clone::fetch::Error::Fetch(gix::remote::fetch::Error::RejectShallowRemote) | ||
| 87 | ), | ||
| 88 | "we can avoid fetching from remotes with this setting" | ||
| 89 | ); | ||
| 90 | Ok(()) | ||
| 91 | } | ||
| 92 | |||
| 93 | #[test] | ||
| 94 | fn from_shallow_allowed_by_default() -> crate::Result { | ||
| 95 | let tmp = gix_testtools::tempfile::TempDir::new()?; | ||
| 96 | let (repo, _change) = gix::prepare_clone_bare(remote::repo("base.shallow").path(), tmp.path())? | ||
| 97 | .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?; | ||
| 98 | assert_eq!( | ||
| 99 | repo.shallow_commits()?.expect("present").as_slice(), | ||
| 100 | vec![ | ||
| 101 | hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"), | ||
| 102 | hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"), | ||
| 103 | hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"), | ||
| 104 | ] | ||
| 105 | ); | ||
| 106 | Ok(()) | ||
| 107 | } | ||
| 108 | |||
| 109 | #[test] | ||
| 110 | fn from_non_shallow_then_deepen_then_deepen_since_to_unshallow() -> crate::Result { | ||
| 111 | let tmp = gix_testtools::tempfile::TempDir::new()?; | ||
| 112 | let (repo, _change) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())? | ||
| 113 | .with_shallow(Shallow::DepthAtRemote(2.try_into()?)) | ||
| 114 | .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?; | ||
| 115 | |||
| 116 | assert!(repo.is_shallow()); | ||
| 117 | assert_eq!( | ||
| 118 | repo.shallow_commits()?.expect("present").as_slice(), | ||
| 119 | vec![ | ||
| 120 | hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"), | ||
| 121 | hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"), | ||
| 122 | ] | ||
| 123 | ); | ||
| 124 | |||
| 125 | let shallow_commit_count = repo.head_id()?.ancestors().all()?.count(); | ||
| 126 | |||
| 127 | let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?; | ||
| 128 | remote | ||
| 129 | .connect(Direction::Fetch, gix::progress::Discard)? | ||
| 130 | .prepare_fetch(Default::default())? | ||
| 131 | .with_shallow(Shallow::Deepen(1)) | ||
| 132 | .receive(&AtomicBool::default())?; | ||
| 133 | |||
| 134 | assert_eq!( | ||
| 135 | repo.shallow_commits()?.expect("present").as_slice(), | ||
| 136 | vec![ | ||
| 137 | hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"), | ||
| 138 | hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"), | ||
| 139 | hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85"), | ||
| 140 | ], | ||
| 141 | "the shallow boundary was changed" | ||
| 142 | ); | ||
| 143 | assert!( | ||
| 144 | repo.head_id()?.ancestors().all()?.count() > shallow_commit_count, | ||
| 145 | "there are more commits now as the history was deepened" | ||
| 146 | ); | ||
| 147 | |||
| 148 | let shallow_commit_count = repo.head_id()?.ancestors().all()?.count(); | ||
| 149 | remote | ||
| 150 | .connect(Direction::Fetch, gix::progress::Discard)? | ||
| 151 | .prepare_fetch(Default::default())? | ||
| 152 | .with_shallow(Shallow::Since { | ||
| 153 | cutoff: gix::date::Time::new(1112354053, 0), | ||
| 154 | }) | ||
| 155 | .receive(&AtomicBool::default())?; | ||
| 156 | |||
| 157 | assert!( | ||
| 158 | !repo.is_shallow(), | ||
| 159 | "the cutoff date is before the first commit, effectively unshallowing" | ||
| 160 | ); | ||
| 161 | assert!( | ||
| 162 | repo.head_id()?.ancestors().all()?.count() > shallow_commit_count, | ||
| 163 | "there is even more commits than previously" | ||
| 164 | ); | ||
| 165 | Ok(()) | ||
| 166 | } | ||
| 167 | |||
| 168 | #[test] | ||
| 169 | fn from_non_shallow_by_deepen_exclude_then_deepen_to_unshallow() -> crate::Result { | ||
| 170 | let tmp = gix_testtools::tempfile::TempDir::new()?; | ||
| 171 | let excluded_leaf_refs = ["g", "h", "j"]; | ||
| 172 | let (repo, _change) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())? | ||
| 173 | .with_shallow(Shallow::Exclude { | ||
| 174 | remote_refs: excluded_leaf_refs | ||
| 175 | .into_iter() | ||
| 176 | .map(|n| n.try_into().expect("valid")) | ||
| 177 | .collect(), | ||
| 178 | since_cutoff: None, | ||
| 179 | }) | ||
| 180 | .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?; | ||
| 181 | |||
| 182 | assert!(repo.is_shallow()); | ||
| 183 | assert_eq!( | ||
| 184 | repo.shallow_commits()?.expect("present").as_slice(), | ||
| 185 | vec![ | ||
| 186 | hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"), | ||
| 187 | hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"), | ||
| 188 | ] | ||
| 189 | ); | ||
| 190 | |||
| 191 | let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?; | ||
| 192 | remote | ||
| 193 | .connect(Direction::Fetch, gix::progress::Discard)? | ||
| 194 | .prepare_fetch(Default::default())? | ||
| 195 | .with_shallow(Shallow::Deepen(2)) | ||
| 196 | .receive(&AtomicBool::default())?; | ||
| 197 | |||
| 198 | assert!(!repo.is_shallow(), "one is just enough to unshallow it"); | ||
| 199 | Ok(()) | ||
| 200 | } | ||
| 201 | |||
| 11 | #[test] | 202 | #[test] |
| 12 | fn fetch_only_with_configuration() -> crate::Result { | 203 | fn fetch_only_with_configuration() -> crate::Result { |
| 13 | let tmp = gix_testtools::tempfile::TempDir::new()?; | 204 | let tmp = gix_testtools::tempfile::TempDir::new()?; |
| @@ -20,8 +211,8 @@ mod blocking_io { · 6 unmodified lines | |||
| 20 | gix::create::Kind::Bare, | 211 | gix::create::Kind::Bare, |
| 21 | Default::default(), | 212 | Default::default(), |
| 22 | gix::open::Options::isolated().config_overrides([ | 213 | gix::open::Options::isolated().config_overrides([ |
| 23 | "init.defaultBranch=unused-as-overridden-by-remote", | 214 | Init::DEFAULT_BRANCH.validated_assignment_fmt(&"unused-as-overridden-by-remote")?, |
| 24 | "core.logAllRefUpdates", | 215 | Core::LOG_ALL_REF_UPDATES.logical_name().into(), |
| 25 | // missing user and email is acceptable in this special case, i.e. `git` also doesn't mind filling it in. | 216 | // missing user and email is acceptable in this special case, i.e. `git` also doesn't mind filling it in. |
| 26 | ]), | 217 | ]), |
| 27 | )? | 218 | )? |
| @@ -1,3 +1,3 @@ | |||
| 1 | version https://git-lfs.github.com/spec/v1 | 1 | version https://git-lfs.github.com/spec/v1 |
| 2 | oid sha256:f0ffd6adef1a5755c2013dd7e793ca8e239308448872681a36a2216ea56623b2 | 2 | oid sha256:dceeedc3ee706632030f8317f2245c327456ec58a6ac690d09099557a5aa23b0 |
| 3 | size 32032 | 3 | size 32336 |
| @@ -1,7 +1,7 @@ | |||
| 1 | set -eu -o pipefail | 1 | set -eu -o pipefail |
| 2 | 2 | ||
| 3 | # IMPORTANT: keep this repo small as it's used for writes, hence will be executed for each writer! | 3 | # IMPORTANT: keep this repo small as it's used for writes, hence will be executed for each writer! |
| 4 | git clone --bare "${1:?First argument is the complex base repo from make_remote_repos.sh/base}" base | 4 | git clone ${2:-} --bare "${1:?First argument is the complex base repo from make_remote_repos.sh/base}" base |
| 5 | 5 | ||
| 6 | git clone --shared base clone-as-base-with-changes | 6 | git clone --shared base clone-as-base-with-changes |
| 7 | (cd clone-as-base-with-changes | 7 | (cd clone-as-base-with-changes |
| @@ -107,6 +107,9 @@ git init base · 106 unmodified lines | |||
| 107 | git branch -D tmp | 107 | git branch -D tmp |
| 108 | ) | 108 | ) |
| 109 | 109 | ||
| 110 | git clone --shared --depth 2 file://$PWD/base base.shallow | ||
| 111 | |||
| 112 | |||
| 110 | git clone --shared base clone | 113 | git clone --shared base clone |
| 111 | (cd clone | 114 | (cd clone |
| 112 | git remote add myself . | 115 | git remote add myself . |
| @@ -591,10 +591,7 @@ mod track_rewrites { · 590 unmodified lines | |||
| 591 | out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0, | 591 | out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0, |
| 592 | "no limit configured" | 592 | "no limit configured" |
| 593 | ); | 593 | ); |
| 594 | assert_eq!( | 594 | assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 57); |
| 595 | out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, | ||
| 596 | if cfg!(windows) { 63 } else { 57 } | ||
| 597 | ); | ||
| 598 | 595 | ||
| 599 | Ok(()) | 596 | Ok(()) |
| 600 | } | 597 | } |
| @@ -1,3 +1,15 @@ | |||
| 1 | mod shallow { | ||
| 2 | use gix::remote::fetch::Shallow; | ||
| 3 | |||
| 4 | #[test] | ||
| 5 | fn undo() { | ||
| 6 | assert_eq!( | ||
| 7 | Shallow::undo(), | ||
| 8 | Shallow::DepthAtRemote(2147483647u32.try_into().expect("known at compile time")) | ||
| 9 | ); | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 1 | #[cfg(any(feature = "blocking-network-client", feature = "async-network-client-async-std"))] | 13 | #[cfg(any(feature = "blocking-network-client", feature = "async-network-client-async-std"))] |
| 2 | mod blocking_and_async_io { | 14 | mod blocking_and_async_io { |
| 3 | use std::sync::atomic::AtomicBool; | 15 | use std::sync::atomic::AtomicBool; |