GitoxideLabs/gitoxide · error

the last fork section contains no commits

Error message

the last fork section contains no commits

What it means

When the parsed todo contains more than one fork section, the final section must contain at least one commit command (`pick`, `empty`, `squash`, or a reference line). `parse` throws this error when the todo ends with a `fork` heading that is never followed by any command, which would leave the plan with an unusable trailing section.

Solutions

  1. Add at least one `pick`, `empty`, `squash`, or `(ref…)` line below the final fork heading.
  2. Delete the trailing fork heading entirely if its section is intentionally empty and it is the last section.
  3. Merge the trailing empty section into the previous section by removing the extra fork separator.

Example fix

// before
──── fork aaa1 base ────
pick bbb2 ...
──── fork ccc3 tip ────

// after (heading removed)
──── fork aaa1 base ────
pick bbb2 ...
Defensive patterns

Strategy: validation

Validate before calling

// Rust: ensure the last fork section is non-empty before submitting the todo
let editable: Vec<&str> = text.lines()
    .filter(|l| !l.trim().is_empty() && !l.trim().starts_with("# ") && !l.contains("<!--"))
    .collect();
let last_is_bare_fork = editable.last()
    .map(|l| l.starts_with('─') && l.trim_matches('─').trim().starts_with("fork "))
    .unwrap_or(false);
if last_is_bare_fork {
    return Err("last fork section contains no commits");
}

Try / catch

match parse_plan(&repo, text) {
    Ok(plan) => apply(plan),
    Err(e) if e.to_string().contains("last fork section contains no commits") => {
        eprintln!("Add a command under the final fork heading or delete it.");
        Err(e)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Editing the todo so the last `──── fork <id> ────` heading has no `pick`/`empty`/`squash`/reference lines beneath it (e.g. deleting all trailing picks, or leaving a stray duplicated fork heading at the end).

Common situations: A user removes the trailing lines of the todo while pruning history but keeps the fork heading; a scripted todo generator appends a fork marker without emitting the section's commits; the editor truncated the file.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/f588a544ed0b444c. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/todo.rs:1018

            commit,
            squash: Vec::new(),
        });
        cursor = Some(rebase::PlanParent::Step(index));
        section_last_step = Some(index);
        if marked {
            let target = rebase::PlanParent::Step(index);
            if checkout_target.is_some_and(|checkout| checkout != target) {
                anyhow::bail!("the @ command and @ reference point to different results");
            }
            checkout_target = Some(target);
        }
        section_has_commit = true;
    }
    if sections == 0 {
        anyhow::bail!("the rebase todo has no fork heading");
    }
    if sections > 1 && !section_has_commit {
        anyhow::bail!("the last fork section contains no commits");
    }
    if state.marker_required && checkout_target.is_none() {
        anyhow::bail!("the current checkout marker must be retained");
    }
    let checkout_reference = match (checkout_target, explicit_checkout_reference) {
        (Some(target), Some((name, reference_target))) => {
            if target != reference_target {
                anyhow::bail!("the @ command and @ reference point to different results");
            }
            Some(name)
        }
        (None, Some(_)) => anyhow::bail!("an @ reference requires an @ command at the same result"),
        (Some(target), None) => state
            .head_ref
            .take()
            .filter(|name| ref_targets.get(name) == Some(&target)),
        (None, None) => None,
    };

View on GitHub (pinned to e73179060b)