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
- Use whole-file mode for the additions/deletions (full-file DiffSpecs) instead of hunk specs
- Split the operation: handle added/deleted files with whole-file specs and only use hunk specs for modified files
- 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
- Only offer hunk-level selection for modified files in the UI
- Map additions/deletions to whole-file specs automatically
- Test change sets containing new/deleted files when building hunk pipelines
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
- Couldn't obtain diff for worktree changes.
- Unexpected hunk with neither newlines or oldlines being 0
- Alternative hunks algorithms still didn't produce properly…
- Cannot uncommit hunks that would result in merge conflicts
- Deletions or additions aren't well-defined for hunk-based…
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)