{"record":{"id":"faf6b5e9c2b658ac","repo":"GitoxideLabs/gitoxide","slug":"an-undo-commit-has-invalid-retention-parents","errorCode":null,"errorMessage":"an undo commit has invalid retention parents","messagePattern":"an undo commit has invalid retention parents","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/undo.rs","lineNumber":861,"sourceCode":"    let message = gix::objs::commit::MessageRef::from_bytes(&commit.message);\n    let title = message\n        .title\n        .to_str()\n        .context(\"an undo operation title is not UTF-8\")?\n        .to_owned();\n    validate_title(&title)?;\n    let body = message.body.context(\"an undo queue commit has no metadata body\")?;\n    let changes = parse_config(repo, body)?;\n    let parent = commit.parents.first().copied();\n    match parent {\n        None => {\n            ensure!(title == START_TITLE, \"the undo sentinel has the wrong title\");\n            ensure!(changes.is_empty(), \"the undo sentinel contains reference changes\");\n        }\n        Some(parent) => {\n            ensure!(!changes.is_empty(), \"an undo operation contains no reference changes\");\n            let expected = retention_parents(repo, parent, &changes)?;\n            ensure!(\n                commit.parents.as_slice() == expected,\n                \"an undo commit has invalid retention parents\"\n            );\n        }\n    }\n    Ok(ParsedCommit { parent, title, changes })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn repo() -> gix_testtools::Result<(gix_testtools::tempfile::TempDir, gix::Repository)> {\n        let fixture = gix_testtools::scripted_fixture_writable(\"create_commit.sh\")?;\n        let repository = crate::test_repository::open(fixture.path())?;\n        Ok((fixture, repository))\n    }\n","sourceCodeStart":843,"sourceCodeEnd":879,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/undo.rs#L843-L879","documentation":"Undo commits use extra parents (retention parents) to keep referenced history reachable, and `parse_commit` verifies these parents match what `retention_parents` computes from the parent commit and the recorded changes. A mismatch means the commit's parent list was altered or created by a different/incompatible version.","triggerScenarios":"Calling `parse_commit` (via `is_queue_commit` or `load`) on an undo commit whose `parents` slice differs from the expected retention parents computed for its parent and recorded changes.","commonSituations":"Queue history rewritten by an external tool, commits created by an older/newer library version with different retention rules, or manual cherry-pick/rebase of queue commits.","solutions":["Rebuild the undo queue from scratch (delete the ref and re-record operations) so retention parents are recomputed correctly","Check the library version that created the queue matches the one parsing it","Do not rewrite, rebase, or cherry-pick undo queue commits with external git tooling"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match undo.load(repo) {\n    Err(e) if e.to_string().contains(\"retention parents\") => {\n        // treat queue as corrupt; rebuild from scratch\n    }\n    other => other?,\n}","preventionTips":["Never rebase/cherry-pick undo queue commits","Keep the library version consistent between writing and reading the queue","Validate queue integrity before relying on planned undo/redo"],"tags":["git","undo","corruption"],"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"}