GitoxideLabs/gitoxide · error

an undo operation title must be one line

Error message

an undo operation title must be one line

What it means

`validate_title` requires undo operation titles to be exactly one line. Commit titles cannot contain line breaks (the title is the first line of the commit message), so the library rejects titles containing `\n` or `\r` bytes rather than silently truncating them.

Solutions

  1. Extract only the first line of the message (split on `\n`) before passing it as the title
  2. Replace or strip `\r` and `\n` characters from user-supplied input before recording
  3. Validate the title is single-line in your own input handling and reject multi-line values with a clear message

Example fix

// before
undo.record(repo, full_commit_message)?;
// after
let title = full_commit_message.lines().next().unwrap_or_default();
undo.record(repo, title)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_single_line(title: &str) -> bool {
    !title.as_bytes().iter().any(|b| *b == b'\n' || *b == b'\r')
}

Prevention

When it happens

Trigger: Calling `record`, `write_commit`, or `parse_commit` with a title containing newline or carriage-return bytes, e.g. a multi-line commit message passed whole instead of just its first line.

Common situations: Passing a full commit message body as the title, or user input captured from a multi-line editor/textbox without normalizing newlines.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/undo.rs:671

    if let Some(hex) = value.strip_prefix(b"object:") {
        let id = ObjectId::from_hex(hex).context("an undo object ID is invalid")?;
        ensure!(
            id.kind() == repo.object_hash(),
            "an undo object ID uses the wrong hash kind"
        );
        return Ok(State::Object(id));
    }
    if let Some(name) = value.strip_prefix(b"symbolic:") {
        return FullName::try_from(name.as_bstr())
            .map(State::Symbolic)
            .context("an undo symbolic target is invalid");
    }
    bail!("an undo reference state has an unknown encoding")
}

fn validate_title(title: &str) -> Result<()> {
    ensure!(!title.is_empty(), "an undo operation title cannot be empty");
    ensure!(
        !title.as_bytes().iter().any(|byte| matches!(byte, b'\n' | b'\r')),
        "an undo operation title must be one line"
    );
    Ok(())
}

fn retention_parents(repo: &gix::Repository, predecessor: ObjectId, changes: &[RefChange]) -> Result<Vec<ObjectId>> {
    let mut parents = vec![predecessor];
    let mut seen = HashSet::from([predecessor]);
    for id in changes
        .iter()
        .flat_map(|change| [&change.before, &change.after])
        .filter_map(|state| match state {
            State::Object(id) => Some(*id),
            State::Missing | State::Symbolic(_) => None,
        })
    {
        if seen.contains(&id) {

View on GitHub (pinned to e73179060b)