{"record":{"id":"3d88a11d57bbea42","repo":"gitbutlerapp/gitbutler","slug":"couldn-t-obtain-diff-for-worktree-changes","errorCode":null,"errorMessage":"Couldn't obtain diff for worktree changes.","messagePattern":"Couldn't obtain diff for worktree changes\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/tree_manipulation/discard_worktree_changes.rs","lineNumber":610,"sourceCode":"        index: &gix::index::State,\n        context_lines: u32,\n    ) -> anyhow::Result<()> {\n        let repo = pipeline.repo;\n        let state_in_worktree = ChangeState {\n            id: repo.object_hash().null(),\n            kind: previous_state.kind,\n        };\n        let mut diff_filter = but_core::unified_diff::filter_from_state(\n            repo,\n            Some(state_in_worktree),\n            UnifiedPatch::CONVERSION_MODE,\n        )?;\n        let Some(UnifiedPatch::Patch {\n            hunks: hunks_in_worktree,\n            ..\n        }) = wt_change.unified_patch_with_filter(repo, context_lines, &mut diff_filter)?\n        else {\n            bail!(\"Couldn't obtain diff for worktree changes.\")\n        };\n\n        let mut hunks_to_keep: Vec<HunkHeader> = (hunks_in_worktree)\n            .into_iter()\n            .map(Into::into)\n            .filter(|hunk| {\n                match hunks_to_discard\n                    .iter()\n                    .enumerate()\n                    .find_map(|(idx, hunk_to_discard)| (hunk_to_discard == hunk).then_some(idx))\n                {\n                    None => true,\n                    Some(idx_to_remove) => {\n                        hunks_to_discard.remove(idx_to_remove);\n                        false\n                    }\n                }\n            })","sourceCodeStart":592,"sourceCodeEnd":628,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-workspace/src/tree_manipulation/discard_worktree_changes.rs#L592-L628","documentation":"Thrown when unified_patch_with_filter returns None while computing which hunks to discard, i.e. no unified patch could be produced for the worktree change even though the code path expects one. Without the worktree diff, the library cannot determine remaining hunks, so it aborts. This usually means the change is not a plain text-blob modification.","triggerScenarios":"Calling hunk-level discard (restore_state_to_worktree with hunks) on a change whose unified_patch_with_filter yields None — e.g. binary files, paths the diff filter rejects, or race where the file changed/vanished between change enumeration and patch computation.","commonSituations":"User tries to discard lines of a binary or non-UTF-8 file; the file was modified or deleted by another process between listing changes and discarding; custom diff filters exclude the file.","solutions":["Verify the target file is a text blob that produces a diff (git diff -- <path>) before hunk discard.","Re-enumerate worktree changes immediately before discarding to avoid stale descriptors.","Fall back to whole-file restore when no patch can be computed for the change."],"exampleFix":"// before\nlet Some(UnifiedPatch::Patch { hunks, .. }) = wt_change.unified_patch_with_filter(...)? else {\n    bail!(\"Couldn't obtain diff for worktree changes.\")\n};\n\n// after\nmatch wt_change.unified_patch_with_filter(...)? {\n    Some(UnifiedPatch::Patch { hunks, .. }) => discard_hunks(hunks),\n    Some(_) | None => whole_file_restore(wt_change)?, // fallback\n}","handlingStrategy":"fallback","validationCode":"// verify the change still yields a text patch before hunk discard\nlet patch = wt_change.unified_patch_with_filter(repo, ctx, &mut filter)?;\nlet discardable = matches!(patch, Some(UnifiedPatch::Patch { .. }));","typeGuard":null,"tryCatchPattern":"if let Err(e) = discard_hunks(...) {\n    if e.to_string().contains(\"Couldn't obtain diff\") {\n        whole_file_restore(change)?; // safe fallback\n    } else { return Err(e.into()); }\n}","preventionTips":["Re-enumerate worktree changes right before discarding to avoid stale data","Restrict line-level discard UI to text files (exclude binary assets)","Always implement a whole-file fallback path for hunk operations"],"tags":["git","diff","hunks"],"backgroundTag":"internal-invariant-violation","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"}