GitoxideLabs/gitoxide · error

undo reference sections are duplicated or out of order

Error message

undo reference sections are duplicated or out of order

What it means

Undo metadata lists one `[ref "<name>"]` section per changed reference, and these sections must be strictly ordered by reference name with no duplicates. `parse_config` compares each reference name to the previous one and fails when a name is not strictly greater, which indicates either the same reference appears twice or the sections were written out of order.

Solutions

  1. Sort the `[ref ...]` sections lexicographically by reference name in the metadata
  2. Remove duplicate sections so each reference name appears exactly once
  3. Regenerate the undo metadata by re-running the tix operation that wrote it
  4. Avoid hand-editing undo metadata; let tix serialize it

Example fix

// before (duplicated and out of order)
[ref "refs/heads/zebra"]
	before = missing
	after = object:111
[ref "refs/heads/alpha"]
	before = missing
	after = object:222
[ref "refs/heads/zebra"]
	before = object:111
	after = object:333
// after
[ref "refs/heads/alpha"]
	before = missing
	after = object:222
[ref "refs/heads/zebra"]
	before = missing
	after = object:333
Defensive patterns

Strategy: validation

Validate before calling

// verify ref sections are unique and sorted before parsing
let config = gix::config::File::try_from(body.as_ref())?;
let mut names: Vec<_> = config.sections()
    .filter(|s| s.header().name() == b"ref")
    .filter_map(|s| s.header().subsection_name().map(|n| n.to_vec()))
    .collect();
let sorted = names.clone();
names.sort();
names.dedup();
assert_eq!(names.len(), sorted.len(), "duplicate ref sections");
assert_eq!(sorted, names, "ref sections out of order");

Type guard

fn sections_sorted_unique(names: &[Vec<u8>]) -> bool {
    names.windows(2).all(|w| w[0] < w[1])
}

Try / catch

match parse_undo_metadata(&repo, &body) {
    Ok(changes) => changes,
    Err(e) if e.to_string().contains("duplicated or out of order") => {
        eprintln!("undo metadata malformed; regenerating");
        regenerate_undo_metadata(&repo)?
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Parsing undo metadata where two `[ref "refs/heads/main"]` sections exist, or where `[ref "refs/heads/zebra"]` precedes `[ref "refs/heads/alpha"]` — from hand-editing, a faulty writer, or a merge that reordered/duplicated sections.

Common situations: Manual edits to the metadata ref, merge conflicts that duplicate or reorder sections, or third-party tooling rewriting the config body without preserving canonical ordering.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

        undo.value("version").as_ref().map(|value| value.as_slice()) == Some(VERSION.as_bytes()),
        "unsupported undo metadata version"
    );

    let mut changes = Vec::new();
    let mut previous_name: Option<FullName> = None;
    for section in sections {
        ensure!(
            section.header().name() == b"ref",
            "undo metadata contains an unknown section"
        );
        let subsection = section
            .header()
            .subsection_name()
            .context("an undo ref section has no reference name")?;
        let name = FullName::try_from(subsection).context("an undo entry contains an invalid reference name")?;
        ensure!(!is_queue_ref(name.as_bstr()), "the undo queue records itself");
        if let Some(previous) = &previous_name {
            ensure!(
                previous < &name,
                "undo reference sections are duplicated or out of order"
            );
        }
        previous_name = Some(name.clone());
        ensure_exact_keys(&section, &["before", "after"])?;
        let before = section.value("before").context("an undo ref has no before-state")?;
        let before = parse_state(repo, before.as_bstr())?;
        let after = section.value("after").context("an undo ref has no after-state")?;
        let after = parse_state(repo, after.as_bstr())?;
        ensure!(before != after, "an undo ref does not change");
        changes.push(RefChange { name, before, after });
    }
    Ok(changes)
}

fn ensure_exact_keys(section: &gix::config::file::SectionRef<'_>, expected: &[&str]) -> Result<()> {
    let actual: Vec<_> = section.value_names().collect();

View on GitHub (pinned to e73179060b)