{"record":{"id":"372d0a3fd4ef367c","repo":"GitoxideLabs/gitoxide","slug":"find-returned-a-cached-commit-so-we-expect-cache","errorCode":null,"errorMessage":"find returned a cached commit, so we expect cache to be present","messagePattern":"find returned a cached commit, so we expect cache to be present","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-blame/src/file/function.rs","lineNumber":873,"sourceCode":"    )?;\n    stats.trees_decoded -= 1;\n    Ok(res.map(|e| e.oid))\n}\n\ntype ParentIds = SmallVec<[(gix_hash::ObjectId, i64); 2]>;\n\nfn collect_parents(\n    commit: gix_traverse::commit::Either<'_, '_>,\n    odb: &impl gix_object::Find,\n    cache: Option<&gix_commitgraph::Graph>,\n    buf: &mut Vec<u8>,\n) -> Result<ParentIds, Error> {\n    let mut parent_ids: ParentIds = Default::default();\n    match commit {\n        gix_traverse::commit::Either::CachedCommit(commit) => {\n            let cache = cache\n                .as_ref()\n                .expect(\"find returned a cached commit, so we expect cache to be present\");\n            for parent_pos in commit.iter_parents() {\n                let parent = cache.commit_at(parent_pos?);\n                parent_ids.push((parent.id().to_owned(), parent.committer_timestamp() as i64));\n            }\n        }\n        gix_traverse::commit::Either::CommitRefIter(commit_ref_iter) => {\n            for id in commit_ref_iter.parent_ids() {\n                let parent = odb.find_commit_iter(id.as_ref(), buf).ok();\n                let parent_commit_time = parent\n                    .and_then(|parent| parent.committer().ok().map(|committer| committer.seconds()))\n                    .unwrap_or_default();\n                parent_ids.push((id, parent_commit_time));\n            }\n        }\n    }\n    Ok(parent_ids)\n}\n","sourceCodeStart":855,"sourceCodeEnd":891,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-blame/src/file/function.rs#L855-L891","documentation":"This is an expect() panic in gix-blame's collect_parents: when the commit graph returns a CachedCommit variant, the function requires a parallel commit cache (Graph cache) to have been provided. The library assumes that whenever a cached commit is produced, the cache must exist; if the cache is None while the iterator yields CachedCommit, the assumption is violated and it panics instead of returning a recoverable error.","triggerScenarios":"Calling the blame file API with a commit iterator/graph configured so that Either::CachedCommit is produced while the `cache` parameter is None — e.g. a miswired internal combination of traversal options where the cache handle is not passed down.","commonSituations":"Hit by library maintainers or users constructing the blame traversal directly with inconsistent graph/cache arguments, or after an upstream refactor changed how the commit cache is threaded through, rather than by ordinary end users of the porcelain API.","solutions":["Ensure the commit cache is created and passed to the blame traversal whenever the graph may yield cached commits","Inspect the call site in gix-blame/src/file/function.rs collect_parents and make cache retrieval fallible (return an Error) instead of expect","Update gix-blame if this is a known bug fixed in a newer release","File a bug with a reproduction, since a panic here indicates an internal invariant violation"],"exampleFix":"// before\nlet cache = cache\n    .as_ref()\n    .expect(\"find returned a cached commit, so we expect cache to be present\");\n// after\nlet cache = cache.as_ref().ok_or_else(|| {\n    message(\"cached commit produced without a commit cache being present\")\n})?;","handlingStrategy":"try-catch","validationCode":"// Before invoking blame internals, ensure a cache is supplied when cached commits are possible:\nassert!(cache.is_some(), \"commit cache must be provided for cached-commit traversal\");","typeGuard":"fn cache_present(cache: &Option<Cache>) -> bool { cache.is_some() }","tryCatchPattern":"// Panics are not catchable safely in Rust; guard instead:\nlet cache = match cache.as_ref() {\n    Some(c) => c,\n    None => return Err(message(\"cached commit without cache\")),\n};","preventionTips":["Always construct the blame traversal with its commit cache wired in","Prefer fallible ok_or over expect when threading optional handles","Run blame against a fixture repo in CI to catch regressions","Update gix-blame regularly to pick up invariant fixes"],"tags":["panic","internal-invariant","blame","git"],"backgroundTag":"internal-invariant-violation","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"}