GitoxideLabs/gitoxide · error
the move source must be the current HEAD
Error message
the move source must be the current HEAD
What it means
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.
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.
Example fix
// before let head = cached_head_id; stack_insert_plan(&repo, &graph, base, head, target)? // after let head = repo.head_id()?.detach(); stack_insert_plan(&repo, &graph, base, head, target)?
Defensive patterns
Strategy: validation
Validate before calling
let current_head = repo.head_id()?.detach();
if current_head != head {
eprintln!("HEAD moved; re-read repository state before moving");
return Ok(());
} Type guard
fn head_is_current(repo: &gix::Repository, head: ObjectId) -> bool {
repo.head_id().map(|id| id.detach() == head).unwrap_or(false)
} Try / catch
match stack_insert_plan(&repo, &graph, base, head, target) {
Err(e) if e.to_string().contains("current HEAD") => {
let head = repo.head_id()?.detach();
stack_insert_plan(&repo, &graph, base, head, target)?;
}
other => other?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- rebase todo requires at least one -x/--hide revision when…
- the hidden and visible revisions have no editable fork point
- the revisions have multiple editable fork points
- aborted without changes: conflict while applying ; pass…
- an edit unexpectedly produced a merge conflict
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/3c13bdbf8516bc4d.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/rebase.rs:650
pub(crate) fn move_insert_plan(
repo: &gix::Repository,
graph: &HistoryGraph,
source: ObjectId,
target: ObjectId,
) -> Result<Plan> {
stack_insert_plan(repo, graph, source, source, target)
}
pub(crate) fn stack_insert_plan(
repo: &gix::Repository,
graph: &HistoryGraph,
base: ObjectId,
head: ObjectId,
target: ObjectId,
) -> Result<Plan> {
if repo.head_id()?.detach() != head {
anyhow::bail!("the move source must be the current HEAD");
}
if !graph.is_ancestor(base, head) {
anyhow::bail!("the stack base must be an ancestor of HEAD");
}
graph
.parents_of(target)
.context("the move target is not in the loaded history")?;
let mut stack = vec![head];
let mut stack_parent = HashMap::new();
loop {
let id = *stack.last().expect("a stack always contains HEAD");
let parents = graph.parents_of(id).context("a moved stack commit is incomplete")?;
let [parent] = parents.as_slice() else {
anyhow::bail!("moving a stack requires every commit to have exactly one parent");
};
stack_parent.insert(id, *parent);
if id == base {View on GitHub (pinned to e73179060b)