{"record":{"id":"6d81658b9d0316a3","repo":"GitoxideLabs/gitoxide","slug":"a-continuation-fork-cannot-target-an-unwritten-emp","errorCode":null,"errorMessage":"a continuation fork cannot target an unwritten empty commit","messagePattern":"a continuation fork cannot target an unwritten empty commit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/todo.rs","lineNumber":298,"sourceCode":"    };\n    let mut document = b\"<!-- Rebase help follows. Saving unchanged continues the materialized rebase; empty this file or remove the tix-rebase-state-v2 comment to cancel. -->\\n# Continue materialized rebase\\n\\n\".to_vec();\n    let mut body = Vec::new();\n    let mut enrichments = crate::enrich::open(repo)?;\n    let mut tree_enrichments = crate::enrich::open_tree(repo)?;\n    for (index, step) in plan.steps.iter().enumerate() {\n        let continues = matches!(step.parent, rebase::PlanParent::Step(parent) if parent + 1 == index);\n        if !continues {\n            if index > 0 {\n                body.push(b'\\n');\n            }\n            let parent = match step.parent {\n                rebase::PlanParent::Existing(id) => id,\n                rebase::PlanParent::Step(parent) => match plan.steps[parent].commit {\n                    rebase::PlanCommit::Pick(id) | rebase::PlanCommit::Copy(id) | rebase::PlanCommit::Resolved(id) => {\n                        id\n                    }\n                    rebase::PlanCommit::Empty(_) => {\n                        anyhow::bail!(\"a continuation fork cannot target an unwritten empty commit\")\n                    }\n                },\n            };\n            let base_title = (parent == plan.base)\n                .then(|| anchor_title(repo, parent))\n                .transpose()?\n                .unwrap_or_default();\n            write_fork_heading(\n                &mut body,\n                repo,\n                parent,\n                (parent == plan.base).then_some((\"base\", base_title.as_str())),\n                show_change_ids,\n            )?;\n            if matches!(step.parent, rebase::PlanParent::Existing(_)) {\n                write_plan_refs_at(&mut body, &plan.expected_refs, step.parent)?;\n            }\n        }","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/todo.rs#L280-L316","documentation":"When building a continuation todo, a fork's parent step must already have a written commit (Pick/Copy/Resolved carrying an object id). If the referenced plan step is still `PlanCommit::Empty` — meaning no commit has been written for it yet — the continuation cannot anchor onto it and `prepare_continuation` bails.","triggerScenarios":"Calling `prepare_continuation` where a continuation fork references a plan step (via `PlanParent::Step`) whose commit is `rebase::PlanCommit::Empty(_)` — i.e. the plan lists an empty placeholder step that hasn't been executed/written during the interrupted rebase.","commonSituations":"Resuming an interrupted rebase at a point before the referenced step materialized; a plan edited by hand or by tooling that reordered steps so a fork points at a not-yet-executed empty step; an empty commit being skipped in an upstream rebase while a continuation still references it.","solutions":["Re-run the rebase so the referenced step is executed and a real commit is written, then create the continuation.","Update the continuation to fork from the nearest ancestor step that has a written commit (Pick/Copy/Resolved).","Regenerate the plan so empty steps are not used as fork parents.","If the empty commit is genuinely unneeded, drop it from the plan and re-derive the continuation."],"exampleFix":"// before\n// plan step 2 is PlanCommit::Empty(_), fork targets it\nprepare_continuation(repo, &plan, forks_at_step_2)?; // bails\n\n// after\n// fork from the last executed step instead\nprepare_continuation(repo, &plan, forks_at_step_1)?; // step 1 is PlanCommit::Resolved(id)","handlingStrategy":"validation","validationCode":"// only fork from steps that already have a written commit\nlet ok = |p: rebase::PlanParent| match p {\n    rebase::PlanParent::Existing(_) => true,\n    rebase::PlanParent::Step(i) => !matches!(plan.steps[i].commit, rebase::PlanCommit::Empty(_)),\n};","typeGuard":null,"tryCatchPattern":"match prepare_continuation(repo, &plan, forks) {\n    Ok(todo) => /* proceed */,\n    Err(e) if e.to_string().contains(\"unwritten empty commit\") => {\n        // retarget fork at nearest executed step, then retry\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Only create continuations after the referenced steps executed","Don't hand-edit or reorder plan steps that forks depend on","Skip/drop empty commits in the plan rather than continuing onto them","Validate plan steps before persisting continuation state"],"tags":["git","rebase","continuation","plan"],"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-16T04:17:20.429Z"}