gitbutlerapp/gitbutler · error

Deletions or additions aren't well-defined for hunk-based…

Error message

Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead

What it means

Hunk-based diff specs only make sense for modifications within existing files. When the walk encounters a deletion or addition (one side of the entry missing) while processing a hunk-based operation, there is no meaningful 'before'/'after' hunk pair, so it bails and directs the caller to whole-file mode.

Solutions

  1. Use whole-file mode for the additions/deletions (full-file DiffSpecs) instead of hunk specs
  2. Split the operation: handle added/deleted files with whole-file specs and only use hunk specs for modified files
  3. Disable hunk-level selection for change sets that contain file additions or deletions

Example fix

// before
specs = hunks_for_all_changes(commit); // includes added file → bails
// after
specs = commit.changes().flat_map(|c| if c.is_addition_or_deletion() {
    vec![whole_file_spec(c)]
} else {
    c.hunk_specs()
});
Defensive patterns

Strategy: validation

Validate before calling

let has_add_or_delete = commit.changed_files()
    .iter().any(|c| matches!(c, Change::Addition{..} | Change::Deletion{..}));
if has_add_or_delete { use_whole_file_mode(); }

Type guard

fn hunk_safe(change: &Change) -> bool {
    matches!(change, Change::Modification { .. })
}

Try / catch

match result {
    Err(e) if e.to_string().contains("whole-file mode") => {
        // rebuild specs in whole-file mode and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling create_tree_without_diff with hunk-level HunkSpecs while the commit/tree diff contains file additions or deletions (e.g. new files in the commit, deleted files), causing the code to hit the bail in the entry-matching branch.

Common situations: Users select individual hunks but the commit also adds/removes whole files; automation passing hunk specs for a change set containing new or deleted files.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/45abccc02f83752b. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-workspace/src/tree_manipulation/create_tree_without_diff.rs:128

        let Some(after_entry) = after.lookup_entry(change.path.clone().split_str("/"))? else {
            let Some(before_entry) = before_entry else {
                // If there is no before entry and no after entry, then
                // something has gone wrong.
                dropped.push(change);
                continue;
            };

            if change.hunk_headers.is_empty() {
                // If there is no after_change, then it must have been deleted.
                // Therefore, we can just add it again.
                builder.upsert(
                    change.path.as_bstr(),
                    before_entry.mode().kind(),
                    before_entry.object_id(),
                )?;
                continue;
            } else {
                anyhow::bail!(
                    "Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead"
                );
            }
        };

        match after_entry.mode().kind() {
            gix::objs::tree::EntryKind::Blob | gix::objs::tree::EntryKind::BlobExecutable => {
                let after_blob = after_entry.object()?.into_blob();
                if change.hunk_headers.is_empty() {
                    revert_file_to_before_state(&before_entry, &mut builder, &change)?;
                } else {
                    let Some(before_entry) = before_entry else {
                        anyhow::bail!(
                            "Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead"
                        );
                    };

                    let diff = but_core::UnifiedPatch::compute(

View on GitHub (pinned to 58e5313667)