{"record":{"id":"45abccc02f83752b","repo":"gitbutlerapp/gitbutler","slug":"deletions-or-additions-aren-t-well-defined-for-hunk-based","errorCode":null,"errorMessage":"Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead","messagePattern":"Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/tree_manipulation/create_tree_without_diff.rs","lineNumber":128,"sourceCode":"        let Some(after_entry) = after.lookup_entry(change.path.clone().split_str(\"/\"))? else {\n            let Some(before_entry) = before_entry else {\n                // If there is no before entry and no after entry, then\n                // something has gone wrong.\n                dropped.push(change);\n                continue;\n            };\n\n            if change.hunk_headers.is_empty() {\n                // If there is no after_change, then it must have been deleted.\n                // Therefore, we can just add it again.\n                builder.upsert(\n                    change.path.as_bstr(),\n                    before_entry.mode().kind(),\n                    before_entry.object_id(),\n                )?;\n                continue;\n            } else {\n                anyhow::bail!(\n                    \"Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead\"\n                );\n            }\n        };\n\n        match after_entry.mode().kind() {\n            gix::objs::tree::EntryKind::Blob | gix::objs::tree::EntryKind::BlobExecutable => {\n                let after_blob = after_entry.object()?.into_blob();\n                if change.hunk_headers.is_empty() {\n                    revert_file_to_before_state(&before_entry, &mut builder, &change)?;\n                } else {\n                    let Some(before_entry) = before_entry else {\n                        anyhow::bail!(\n                            \"Deletions or additions aren't well-defined for hunk-based operations - use the whole-file mode instead\"\n                        );\n                    };\n\n                    let diff = but_core::UnifiedPatch::compute(","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-workspace/src/tree_manipulation/create_tree_without_diff.rs#L110-L146","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nspecs = hunks_for_all_changes(commit); // includes added file → bails\n// after\nspecs = commit.changes().flat_map(|c| if c.is_addition_or_deletion() {\n    vec![whole_file_spec(c)]\n} else {\n    c.hunk_specs()\n});","handlingStrategy":"validation","validationCode":"let has_add_or_delete = commit.changed_files()\n    .iter().any(|c| matches!(c, Change::Addition{..} | Change::Deletion{..}));\nif has_add_or_delete { use_whole_file_mode(); }","typeGuard":"fn hunk_safe(change: &Change) -> bool {\n    matches!(change, Change::Modification { .. })\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"whole-file mode\") => {\n        // rebuild specs in whole-file mode and retry\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["git","diff","hunks"],"backgroundTag":"unsupported-operation","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}