GitoxideLabs/gitoxide · error

an empty commit needs a title

Error message

an empty commit needs a title

What it means

Thrown when an `empty` todo command yields no title. An `empty` step creates a commit without changes, which still requires a commit message; the title is taken from the command's value or falls back to the previous line's `tail`, and both being empty is invalid.

Solutions

  1. Provide a title after the `empty` command, e.g. `empty WIP checkpoint`
  2. Ensure the preceding line provides a usable `tail` if relying on the fallback
  3. Remove the `empty` line if no empty commit is needed

Example fix

// before
empty
// after
empty add TODO notes
Defensive patterns

Strategy: validation

Validate before calling

for line in todo.lines() {
    if let Some(verb) = line.split_whitespace().next() {
        if verb == "empty" {
            let title = line["empty".len()..].trim();
            if title.is_empty() { anyhow::bail!("empty commit needs a title"); }
        }
    }
}

Type guard

fn empty_line_has_title(line: &str) -> bool {
    !line.strip_prefix("empty").map_or(false, str::trim)
        .map_or(true, str::is_empty) || line.split_whitespace().count() > 1
}

Try / catch

match parse_plan(...) {
    Err(e) if e.to_string().contains("needs a title") => { /* supply a title for the empty line */ }
    other => other?,
}

Prevention

When it happens

Trigger: `parse_plan` reads `empty` (or `empty ` with only whitespace) on a line where the fallback `tail` is also empty.

Common situations: Writing a bare `empty` line in a todo intending a placeholder commit; scripted generation that forgets to include a title; trimming that stripped the intended message.

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/da541762bc2bca5a. Report an issue: GitHub.

Appendix: source

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

                } else {
                    (resolve_commit(repo, value)?, false)
                };
                if !scope.contains(&id) {
                    anyhow::bail!("a pick is outside the editable history");
                }
                if picked.contains_key(&id) {
                    anyhow::bail!("a commit is picked more than once");
                }
                if resolved {
                    rebase::PlanCommit::Resolved(id)
                } else {
                    rebase::PlanCommit::Pick(id)
                }
            }
            "empty" => {
                let title = if value.trim().is_empty() { tail } else { value.trim() };
                if title.is_empty() {
                    anyhow::bail!("an empty commit needs a title");
                }
                rebase::PlanCommit::Empty(BString::from(title))
            }
            _ => anyhow::bail!("unsupported rebase todo command {verb:?}"),
        };
        let index = steps.len();
        if let rebase::PlanCommit::Pick(id) | rebase::PlanCommit::Resolved(id) = commit {
            picked.insert(id, index);
        }
        steps.push(rebase::PlanStep {
            parent,
            commit,
            squash: Vec::new(),
        });
        cursor = Some(rebase::PlanParent::Step(index));
        section_last_step = Some(index);
        if marked {
            let target = rebase::PlanParent::Step(index);

View on GitHub (pinned to e73179060b)