gitbutlerapp/gitbutler · info

key was just inserted

Error message

key was just inserted

What it means

Panic from `expect("key was just inserted")` in `merge_specs_by_path` (but-workspace uncommit_changes.rs:296). The function maintains two structures in lockstep: `order` records each first-seen `(previous_path, path)` key and `by_path` inserts that same key. The final pass removes each recorded key from the map, so every removal must hit; the expect asserts the map-side of that pairing. Unreachable unless the two structures are desynchronized by an edit.

Source

Thrown at crates/but-workspace/src/commit/uncommit_changes.rs:296

            }
            Entry::Occupied(mut entry) => {
                let existing = entry.get_mut();
                if existing.hunk_headers.is_empty() || spec.hunk_headers.is_empty() {
                    // Whole-file removal supersedes any hunk selection.
                    existing.hunk_headers.clear();
                } else {
                    for header in spec.hunk_headers {
                        if !existing.hunk_headers.contains(&header) {
                            existing.hunk_headers.push(header);
                        }
                    }
                }
            }
        }
    }
    order
        .into_iter()
        .map(|key| by_path.remove(&key).expect("key was just inserted"))
        .collect()
}

fn failure(group: &GroupedUncommitChanges, error: impl Into<String>) -> UncommitChangesFailure {
    UncommitChangesFailure {
        commit_id: group.commit_id,
        changes: group.changes.clone(),
        error: error.into(),
    }
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. No caller-side action — keys are inserted and recorded in the same match arm
  2. If editing, consider a single structure (e.g. `IndexMap`) so order and storage cannot diverge
  3. Add a unit test with duplicate paths mixing whole-file and per-hunk specs

Example fix

// before (two structures kept in sync manually)
let mut order = Vec::new();
let mut by_path = HashMap::new();
...
order.into_iter().map(|key| by_path.remove(&key).expect("key was just inserted")).collect()

// after: one structure, no sync to break
use indexmap::IndexMap;
let mut by_path = IndexMap::new();
// merge into by_path.entry(key) ...
by_path.into_iter().map(|(_, spec)| spec).collect::<Vec<_>>()
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Uncommitting selected changes where multiple DiffSpecs target the same file (per-hunk selections), which routes through this merge function; the panic requires a code change that pushes to `order` without inserting into `by_path` (or with a different key), not any input to the shipped code.

Common situations: Maintainers adding rename-aware merging or path normalization between the push and the insert; forks altering the key type; none for end users.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/31f57a6c7d81a9a7. Report an issue: GitHub.