GitoxideLabs/gitoxide · error

present and consumed once

Error message

present and consumed once

What it means

PrepareFetch::persist() calls self.repo.take().expect("present and consumed once"). Like PrepareCheckout::persist, it moves the Repository out exactly once; the expect fires if the Option is already None because persist() was called before or the repository was already handed over by the fetch flow.

Solutions

  1. Call persist() exactly once on the owned PrepareFetch value.
  2. If fetch succeeded, use the Repository returned by the fetch call instead of persist().
  3. Ensure only one code path can reach persist() (consume self; persist is a one-shot method).

Example fix

// before
let repo = prepare.persist();
let repo2 = prepare.persist(); // cannot happen with move semantics; guard one-shot usage
// after
let repo = prepare.persist(); // single, final call
Defensive patterns

Strategy: validation

Validate before calling

// persist() consumes self, so the compiler enforces single use; ensure one code path owns it:
let repo = prepare.persist();

Prevention

When it happens

Trigger: Calling persist() twice on the same PrepareFetch; calling persist() after a previous code path consumed the internal repo (e.g. success path already moved it).

Common situations: Multiple exit branches each calling persist(); wrapping persist() in a helper called in several places; using persist() after main fetch already returned the Repository.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/3cf5da9e56a3e143. Report an issue: GitHub.

Appendix: source

Thrown at gix/src/clone/access.rs:106

                    && spec.destination().is_none()
                    && (source == "HEAD" || is_full_ref || gix_hash::ObjectId::from_hex(source).is_ok());
                is_valid
                    .then(|| spec.to_owned())
                    .ok_or(crate::clone::with_revision::Error::Invalid { revision })
            })
            .transpose()?;
        if self.revision.is_some() {
            self.ref_name = None;
        }
        Ok(self)
    }
}

/// Consumption
impl PrepareFetch {
    /// Persist the contained repository as is even if an error may have occurred when fetching from the remote.
    pub fn persist(mut self) -> Repository {
        self.repo.take().expect("present and consumed once")
    }
}

impl Drop for PrepareFetch {
    fn drop(&mut self) {
        if let Some(repo) = self.repo.take() {
            super::cleanup_clone_destination_on_drop(&repo, self.remove_worktree_on_drop);
        }
    }
}

impl From<PrepareFetch> for Repository {
    fn from(prep: PrepareFetch) -> Self {
        prep.persist()
    }
}

View on GitHub (pinned to e73179060b)