diff --git a/gix/src/commit.rs b/gix/src/commit.rs index ad191f64a4a..d9c8d635ba6 100644 --- a/gix/src/commit.rs +++ b/gix/src/commit.rs @@ -1,6 +1,8 @@ //! #![allow(clippy::empty_docs)] +use std::convert::Infallible; + /// An empty array of a type usable with the `gix::easy` API to help declaring no parents should be used pub const NO_PARENT_IDS: [gix_hash::ObjectId; 0] = []; @@ -22,6 +24,12 @@ pub enum Error { ReferenceEdit(#[from] crate::reference::edit::Error), } +impl From<std::convert::Infallible> for Error { + fn from(_value: Infallible) -> Self { + unreachable!("cannot be invoked") + } +} + /// #[cfg(feature = "revision")] pub mod describe { diff --git a/gix/src/remote/connection/fetch/update_refs/mod.rs b/gix/src/remote/connection/fetch/update_refs/mod.rs index bb3c4f5e379..640b028865e 100644 --- a/gix/src/remote/connection/fetch/update_refs/mod.rs +++ b/gix/src/remote/connection/fetch/update_refs/mod.rs @@ -102,6 +102,8 @@ pub(crate) fn update( let update = if is_implicit_tag { Mode::ImplicitTagNotSentByRemote.into() } else { + // Assure the ODB is not to blame for the missing object. + repo.try_find_object(remote_id)?; Mode::RejectedSourceObjectNotFound { id: remote_id.into() }.into() }; updates.push(update); diff --git a/gix/src/remote/connection/fetch/update_refs/update.rs b/gix/src/remote/connection/fetch/update_refs/update.rs index 741fc12f5ba..f5adb348e2a 100644 --- a/gix/src/remote/connection/fetch/update_refs/update.rs +++ b/gix/src/remote/connection/fetch/update_refs/update.rs @@ -23,6 +23,8 @@ mod error { PeelToId(#[from] crate::reference::peel::Error), #[error("Failed to follow a symbolic reference to assure worktree isn't affected")] FollowSymref(#[from] gix_ref::file::find::existing::Error), + #[error(transparent)] + FindObject(#[from] crate::object::find::Error), } } diff --git a/gix/tests/gix/remote/fetch.rs b/gix/tests/gix/remote/fetch.rs index aa4d75f6241..671bba74783 100644 --- a/gix/tests/gix/remote/fetch.rs +++ b/gix/tests/gix/remote/fetch.rs @@ -14,6 +14,7 @@ mod shallow { mod blocking_and_async_io { use std::sync::atomic::AtomicBool; + use gix::config::tree::User; use gix::{ config::tree::Protocol, remote::{fetch, fetch::Status, Direction::Fetch}, @@ -85,6 +86,73 @@ mod blocking_and_async_io { try_repo_rw(name).unwrap() } + #[test] + #[cfg(feature = "blocking-network-client")] + fn fetch_more_packs_than_can_be_handled() -> gix_testtools::Result { + use gix::interrupt::IS_INTERRUPTED; + use gix_odb::store::init::Slots; + use gix_testtools::tempfile; + fn create_empty_commit(repo: &gix::Repository) -> anyhow::Result<()> { + let name = repo.head_name()?.expect("no detached head"); + repo.commit( + name.as_bstr(), + "empty", + gix::hash::ObjectId::empty_tree(repo.object_hash()), + repo.try_find_reference(name.as_ref())?.map(|r| r.id()), + )?; + Ok(()) + } + for max_packs in 1..=3 { + let remote_dir = tempfile::tempdir()?; + let mut remote_repo = gix::init_bare(remote_dir.path())?; + { + let mut config = remote_repo.config_snapshot_mut(); + config.set_value(&User::NAME, "author")?; + config.set_value(&User::EMAIL, "email@example.com")?; + } + create_empty_commit(&remote_repo)?; + + let local_dir = tempfile::tempdir()?; + let (local_repo, _) = gix::clone::PrepareFetch::new( + remote_repo.path(), + local_dir.path(), + gix::create::Kind::Bare, + Default::default(), + gix::open::Options::isolated().object_store_slots(Slots::Given(max_packs)), + )? + .fetch_only(gix::progress::Discard, &IS_INTERRUPTED)?; + + let remote = local_repo + .branch_remote( + local_repo.head_ref()?.expect("branch available").name().shorten(), + Fetch, + ) + .expect("remote is configured after clone")?; + for _round_to_create_pack in 1..12 { + create_empty_commit(&remote_repo)?; + match remote + .connect(Fetch)? + .prepare_fetch(gix::progress::Discard, Default::default())? + .receive(gix::progress::Discard, &IS_INTERRUPTED) + { + Ok(out) => { + for local_tracking_branch_name in out.ref_map.mappings.into_iter().filter_map(|m| m.local) { + let r = local_repo.find_reference(&local_tracking_branch_name)?; + r.id() + .object() + .expect("object should be present after fetching, triggering pack refreshes works"); + local_repo.head_ref()?.unwrap().set_target_id(r.id(), "post fetch")?; + } + } + Err(err) => assert!(err + .to_string() + .starts_with("The slotmap turned out to be too small with ")), + } + } + } + Ok(()) + } + #[test] #[cfg(feature = "blocking-network-client")] #[allow(clippy::result_large_err)]