gitbutlerapp/gitbutler · error

Couldn't obtain diff for worktree changes.

Error message

Couldn't obtain diff for worktree changes.

What it means

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.

Solutions

  1. Verify the target file is a text blob that produces a diff (git diff -- <path>) before hunk discard.
  2. Re-enumerate worktree changes immediately before discarding to avoid stale descriptors.
  3. Fall back to whole-file restore when no patch can be computed for the change.

Example fix

// before
let Some(UnifiedPatch::Patch { hunks, .. }) = wt_change.unified_patch_with_filter(...)? else {
    bail!("Couldn't obtain diff for worktree changes.")
};

// after
match wt_change.unified_patch_with_filter(...)? {
    Some(UnifiedPatch::Patch { hunks, .. }) => discard_hunks(hunks),
    Some(_) | None => whole_file_restore(wt_change)?, // fallback
}
Defensive patterns

Strategy: fallback

Validate before calling

// verify the change still yields a text patch before hunk discard
let patch = wt_change.unified_patch_with_filter(repo, ctx, &mut filter)?;
let discardable = matches!(patch, Some(UnifiedPatch::Patch { .. }));

Try / catch

if let Err(e) = discard_hunks(...) {
    if e.to_string().contains("Couldn't obtain diff") {
        whole_file_restore(change)?; // safe fallback
    } else { return Err(e.into()); }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-workspace/src/tree_manipulation/discard_worktree_changes.rs:610

        index: &gix::index::State,
        context_lines: u32,
    ) -> anyhow::Result<()> {
        let repo = pipeline.repo;
        let state_in_worktree = ChangeState {
            id: repo.object_hash().null(),
            kind: previous_state.kind,
        };
        let mut diff_filter = but_core::unified_diff::filter_from_state(
            repo,
            Some(state_in_worktree),
            UnifiedPatch::CONVERSION_MODE,
        )?;
        let Some(UnifiedPatch::Patch {
            hunks: hunks_in_worktree,
            ..
        }) = wt_change.unified_patch_with_filter(repo, context_lines, &mut diff_filter)?
        else {
            bail!("Couldn't obtain diff for worktree changes.")
        };

        let mut hunks_to_keep: Vec<HunkHeader> = (hunks_in_worktree)
            .into_iter()
            .map(Into::into)
            .filter(|hunk| {
                match hunks_to_discard
                    .iter()
                    .enumerate()
                    .find_map(|(idx, hunk_to_discard)| (hunk_to_discard == hunk).then_some(idx))
                {
                    None => true,
                    Some(idx_to_remove) => {
                        hunks_to_discard.remove(idx_to_remove);
                        false
                    }
                }
            })

View on GitHub (pinned to 58e5313667)