{"record":{"id":"8075bbfc83aa3ab8","repo":"GitoxideLabs/gitoxide","slug":"a-fork-target-must-be-picked-before-it-is-used","errorCode":null,"errorMessage":"a fork target must be picked before it is used","messagePattern":"a fork target must be picked before it is used","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/todo.rs","lineNumber":886,"sourceCode":"            let target = line\n                .trim_matches('─')\n                .trim()\n                .strip_prefix(\"fork \")\n                .context(\"a fork separator needs a fork ID\")?;\n            if sections > 0 && !section_has_commit {\n                anyhow::bail!(\"a fork section contains no commits\");\n            }\n            let id = resolve_commit(\n                repo,\n                target\n                    .split_whitespace()\n                    .next()\n                    .context(\"a fork heading needs a commit ID\")?,\n            )?;\n            cursor = Some(if let Some(index) = picked.get(&id) {\n                rebase::PlanParent::Step(*index)\n            } else if scope.contains(&id) {\n                anyhow::bail!(\"a fork target must be picked before it is used\");\n            } else {\n                rebase::PlanParent::Existing(id)\n            });\n            sections += 1;\n            section_has_commit = false;\n            section_last_step = None;\n            continue;\n        }\n        if line.starts_with('(') && line.ends_with(')') {\n            let target = cursor.context(\"a reference line must follow a fork or command\")?;\n            for (marked, value) in parse_ref_line(line)? {\n                let name = resolve_ref_name(repo, &mut state.expected_refs, value.as_bstr())?;\n                if ref_targets.insert(name.clone(), target).is_some() {\n                    anyhow::bail!(\"a reference is placed more than once\");\n                }\n                if marked {\n                    if !state.checkout_allowed || repo.workdir().is_none() {\n                        anyhow::bail!(\"the rebase todo cannot select a checkout without a worktree\");","sourceCodeStart":868,"sourceCodeEnd":904,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/todo.rs#L868-L904","documentation":"A fork heading names a target commit; if that target belongs to the rebase scope it must already have been picked by an earlier (lower in the file, since parsing is bottom-up) plan step, so the fork can attach to that step. Using an in-scope fork target before it is picked would create a plan referencing a nonexistent step, so it is rejected.","triggerScenarios":"`parse` bails when a fork heading's resolved commit `id` is in `scope` but not in the `picked` map at that point (not `PlanParent::Step`). Happens when a user reorders fork sections so a fork targeting a scoped commit appears before the section that picks that commit.","commonSituations":"Manually reordering fork blocks in the todo editor; moving a fork heading to the wrong position while restructuring; deleting the pick step a fork depended on.","solutions":["Move the fork heading below (in file order: after) the section that picks its target commit","Change the fork target to a commit outside the rebase scope (an existing commit) if it should not attach to a plan step","Restore the pick line for the target commit in the preceding section"],"exampleFix":"// before (fork targets in-scope commit before it is picked)\n──fork def5678──\npick 2222222\n──fork abc1234──\npick 1111111\n// after (pick the target first, then fork onto it)\n──fork abc1234──\npick 1111111\n──fork def5678──\npick 2222222","handlingStrategy":"validation","validationCode":"// fork targets inside the scope must already have a pick step above them\nfor fork in fork_targets(edited) {\n    if scope.contains(&fork.id) && !picked_before(fork, edited) {\n        return Err(format!(\"fork target {} must be picked first\", fork.id));\n    }\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = parse(repo, edited) {\n    if e.to_string().contains(\"must be picked before it is used\") {\n        // prompt user to reorder the fork section\n    }\n}","preventionTips":["Keep fork headings adjacent to the section that picks their target","Reorder whole sections, not individual fork headings","Consult the generated todo comments describing ordering rules"],"tags":["rebase","todo-parsing","fork-ordering"],"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"}