{"record":{"id":"3c13bdbf8516bc4d","repo":"GitoxideLabs/gitoxide","slug":"the-move-source-must-be-the-current-head","errorCode":null,"errorMessage":"the move source must be the current HEAD","messagePattern":"the move source must be the current HEAD","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/rebase.rs","lineNumber":650,"sourceCode":"\npub(crate) fn move_insert_plan(\n    repo: &gix::Repository,\n    graph: &HistoryGraph,\n    source: ObjectId,\n    target: ObjectId,\n) -> Result<Plan> {\n    stack_insert_plan(repo, graph, source, source, target)\n}\n\npub(crate) fn stack_insert_plan(\n    repo: &gix::Repository,\n    graph: &HistoryGraph,\n    base: ObjectId,\n    head: ObjectId,\n    target: ObjectId,\n) -> Result<Plan> {\n    if repo.head_id()?.detach() != head {\n        anyhow::bail!(\"the move source must be the current HEAD\");\n    }\n    if !graph.is_ancestor(base, head) {\n        anyhow::bail!(\"the stack base must be an ancestor of HEAD\");\n    }\n    graph\n        .parents_of(target)\n        .context(\"the move target is not in the loaded history\")?;\n\n    let mut stack = vec![head];\n    let mut stack_parent = HashMap::new();\n    loop {\n        let id = *stack.last().expect(\"a stack always contains HEAD\");\n        let parents = graph.parents_of(id).context(\"a moved stack commit is incomplete\")?;\n        let [parent] = parents.as_slice() else {\n            anyhow::bail!(\"moving a stack requires every commit to have exactly one parent\");\n        };\n        stack_parent.insert(id, *parent);\n        if id == base {","sourceCodeStart":632,"sourceCodeEnd":668,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/rebase.rs#L632-L668","documentation":"The move/stack-insert operation relocates the stack of commits ending at HEAD, so its notion of 'the source' is whatever HEAD currently points to. The plan is computed against the on-disk repository state; if the caller-provided `head` id does not match `repo.head_id()`, the operation would move the wrong commits, so it bails immediately.","triggerScenarios":"Calling `move_insert_plan`/`stack_insert_plan(repo, graph, base, head, target)` where `head` is stale: HEAD moved (new commit, checkout, reset, rebase) between the caller reading it and invoking the operation, or the caller passes an unrelated commit id.","commonSituations":"Long-running UI sessions (like tix) where the repository changed while the plan request was built; scripts that cached a HEAD id earlier in execution; concurrent modification by another git process.","solutions":["Re-read the current HEAD id and pass it (or derive `base`/`head` from it) right before the move.","Refresh the loaded history graph and retry the operation with up-to-date ids.","Ensure no other process or in-session operation moves HEAD between snapshot and plan."],"exampleFix":"// before\nlet head = cached_head_id;\nstack_insert_plan(&repo, &graph, base, head, target)?\n\n// after\nlet head = repo.head_id()?.detach();\nstack_insert_plan(&repo, &graph, base, head, target)?","handlingStrategy":"validation","validationCode":"let current_head = repo.head_id()?.detach();\nif current_head != head {\n    eprintln!(\"HEAD moved; re-read repository state before moving\");\n    return Ok(());\n}","typeGuard":"fn head_is_current(repo: &gix::Repository, head: ObjectId) -> bool {\n    repo.head_id().map(|id| id.detach() == head).unwrap_or(false)\n}","tryCatchPattern":"match stack_insert_plan(&repo, &graph, base, head, target) {\n    Err(e) if e.to_string().contains(\"current HEAD\") => {\n        let head = repo.head_id()?.detach();\n        stack_insert_plan(&repo, &graph, base, head, target)?;\n    }\n    other => other?,\n}","preventionTips":["Always read HEAD immediately before building a plan, never from cached state","In long-running sessions, refresh graph and HEAD when the repository changes","Avoid holding plan requests across operations that can move HEAD"],"tags":["git","rebase","stale-state","head-mismatch"],"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-15T23:17:13.987Z"}