{"record":{"id":"9230f1aa6b683612","repo":"GitoxideLabs/gitoxide","slug":"the-undo-queue-first-parent-chain-contains-a-cycle","errorCode":null,"errorMessage":"the undo queue first-parent chain contains a cycle","messagePattern":"the undo queue first-parent chain contains a cycle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/undo.rs","lineNumber":141,"sourceCode":"        ensure!(seen.insert(name.clone()), \"a symbolic reference chain contains a cycle\");\n        let Some(reference) = repo.try_find_reference(name.as_ref())? else {\n            return Ok(false);\n        };\n        let target = reference.target();\n        let Some(next) = target.try_name() else {\n            return Ok(false);\n        };\n        name = next.to_owned();\n    }\n}\n\npub(crate) fn is_queue_commit(repo: &gix::Repository, needle: ObjectId) -> Result<bool> {\n    let Some(mut id) = read_queue_ref(repo, TIP_REF)?.or(read_queue_ref(repo, CURSOR_REF)?) else {\n        return Ok(false);\n    };\n    let mut seen = HashSet::new();\n    loop {\n        ensure!(seen.insert(id), \"the undo queue first-parent chain contains a cycle\");\n        let Ok(stored) = parse_commit(repo, id) else {\n            return Ok(false);\n        };\n        if id == needle {\n            return Ok(true);\n        }\n        let Some(parent) = stored.parent else {\n            return Ok(false);\n        };\n        id = parent;\n    }\n}\n\npub(crate) fn review_blocks_undo(repo: &gix::Repository) -> Result<bool> {\n    let references = repo.references().context(\"could not open review references\")?;\n    for reference in references\n        .prefixed(crate::history::REVIEW_PREFIX.as_bstr())\n        .context(\"could not iterate review references\")?","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/undo.rs#L123-L159","documentation":"`is_queue_commit` follows the first-parent chain of the undo queue tip (or cursor) commit to decide whether a given object id is part of the queue. A `HashSet` of visited ids enforces termination; if a commit id repeats, the first-parent chain is cyclic, which valid commit graphs cannot produce, so this error is raised.","triggerScenarios":"Undo planning via `is_queue_commit` when the undo-queue tip commit's first-parent ancestry loops back on itself, e.g. commit A's first-parent chain reaches A again via corrupted or forged commit objects.","commonSituations":"Object store corruption or hand-crafted commit objects; a buggy external tool rewrote history into a loop; tests injecting deliberately cyclic commits.","solutions":["Verify the queue commit graph with `git log --first-parent <queue-tip>` and `git fsck`.","Rebuild or discard the undo queue ref if its history is corrupt.","If constructing queue commits in code, ensure each new commit's parent is a previously unvisited (strictly older) commit."],"exampleFix":"// before: linking a commit back into the queue, creating a loop\nlet new = commit(parent_id /* an ancestor that loops */);\n// after: parent must be the previous tip, advancing the chain\nlet new = commit(queue_tip_id);","handlingStrategy":"validation","validationCode":"// confirm the queue tip's first-parent history is linear before planning undo\nlet out = std::process::Command::new(\"git\")\n    .args([\"rev-list\", \"--first-parent\", tip_id.to_string().as_str()])\n    .output()?;\n// duplicate ids in output => cyclic/corrupt chain, do not call undo APIs","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only build queue commits whose parent is the immediate previous queue tip.","Run `git fsck` after any history rewrite near the queue.","Never point queue refs at arbitrary user-made commits."],"tags":["git","commit-graph","cycle","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"}