{"record":{"id":"96cc02c1770d07c8","repo":"GitoxideLabs/gitoxide","slug":"user-error-multiple-calls-are-allowed-only-until","errorCode":null,"errorMessage":"user error: multiple calls are allowed only until it succeeds","messagePattern":"user error: multiple calls are allowed only until it succeeds","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix/src/clone/fetch/mod.rs","lineNumber":120,"sourceCode":"    ///\n    /// Even though `async` is technically supported, it will still be blocking in nature as it uses a lot of non-async writes\n    /// and computation under the hood. Thus it should be spawned into a runtime which can handle blocking futures.\n    #[gix_protocol::bisync::bisync]\n    pub async fn fetch_only<P>(\n        &mut self,\n        mut progress: P,\n        should_interrupt: &std::sync::atomic::AtomicBool,\n    ) -> Result<(crate::Repository, crate::remote::fetch::Outcome), Error>\n    where\n        P: crate::NestedProgress,\n        P::SubProgress: 'static,\n    {\n        use crate::{bstr::ByteVec, remote, remote::fetch::RefLogMessage};\n\n        let mut repo = self\n            .repo\n            .as_ref()\n            .expect(\"user error: multiple calls are allowed only until it succeeds\")\n            .clone();\n\n        repo.committer_or_set_generic_fallback()?;\n\n        if !self.config_overrides.is_empty() {\n            let mut snapshot = repo.config_snapshot_mut();\n            snapshot.append_config(&self.config_overrides, gix_config::Source::Api)?;\n        }\n\n        let remote_name = match self.remote_name.as_ref() {\n            Some(name) => name.to_owned(),\n            None => repo\n                .config\n                .resolved\n                .string(crate::config::tree::Clone::DEFAULT_REMOTE_NAME)\n                .map(|n| crate::config::tree::Clone::DEFAULT_REMOTE_NAME.try_into_symbolic_name(n))\n                .transpose()?\n                .unwrap_or_else(|| {","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix/src/clone/fetch/mod.rs#L102-L138","documentation":"This `.expect(\"user error: multiple calls are allowed only until it succeeds\")` fires when `PrepareFetch::fetch_only` (gix clone/fetch) is invoked more than once, or after the prepare state was already consumed by a successful fetch. Internally the builder holds `repo` in an `Option` that is taken on success; a second call finds `None` and the process panics. The message explicitly frames this as a user error: the API permits exactly one fetch attempt.","triggerScenarios":"Calling `fetch_only()` (or `fetch_then_checkout()`) twice on the same `PrepareFetch` value, or calling `fetch_only` after `fetch_then_checkout` already succeeded and consumed the state.","commonSituations":"Retry loops that re-call `fetch_only` on the same prepared handle after a failure consumed intermediate state or after success; conditional code paths that accidentally invoke fetch twice.","solutions":["Call `fetch_only`/`fetch_then_checkout` exactly once per `PrepareFetch`; create a new `PrepareFetch` for retries.","Use the returned values immediately; don't stash the prepare handle for later reuse.","Restructure control flow so success paths never fall through to a second fetch call.","Match on the first call's `Option` result explicitly instead of re-invoking."],"exampleFix":"// before\nlet prep = gix::clone::PrepareFetch::new(...)?;\nlet _ = prep.fetch_only(PackCache, Interrupts)?;\nlet _ = prep.fetch_only(PackCache, Interrupts)?; // panics\n// after\nlet prep = gix::clone::PrepareFetch::new(...)?;\nlet remote = prep.fetch_only(PackCache, Interrupts)?; // single call\n// for retries, rebuild:\n// let prep = gix::clone::PrepareFetch::new(...)?;","handlingStrategy":"validation","validationCode":"// PrepareFetch cannot be queried directly; track consumption yourself\nif fetch_already_attempted {\n    anyhow::bail!(\"PrepareFetch::fetch_only must be called at most once; rebuild the prepare state to retry\");\n}","typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(AssertUnwindSafe(|| prep.fetch_only(cache, interrupts)));\nmatch result {\n    Ok(inner) => inner?,\n    Err(_) => anyhow::bail!(\"fetch prepare state already consumed; create a new PrepareFetch\"),\n}","preventionTips":["Call fetch_only/fetch_then_checkout exactly once per PrepareFetch","Rebuild PrepareFetch for each retry attempt","Never reuse a prepare handle after a successful fetch","Structure code so success paths cannot re-enter fetch calls"],"tags":["rust","panic","clone","fetch","api-misuse"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}