gitbutlerapp/gitbutler · error
BUG: `gix` disables this, as it knows we always need to be a
Error message
BUG: `gix` disables this, as it knows we always need to be able to run our own diff machinery
What it means
`but_core::unified_diff` drives gix's blob-diff platform and asserts it never sees `Operation::ExternalCommand`: the platform is intentionally created with external diff drivers disabled, because GitButler must run its own diff machinery (context handling, hunks, binary-to-text conversion) rather than shell out to `diff.external`/gitattributes drivers. The `unreachable!` fires when the platform was built honoring external diff commands after all.
Source
Thrown at crates/but-core/src/unified_diff.rs:228
let diff = gix::diff::blob::diff_with_slider_heuristics(algorithm, &input);
let (lines_added, lines_removed) = compute_line_changes(&diff);
let hunks = gix::diff::blob::UnifiedDiff::new(
&diff,
&input,
ConsumeBinaryHunk::new(ProduceDiffHunk::default(), "\n"),
ContextSize::symmetrical(context_lines),
)
.consume()?
.hunks;
UnifiedPatch::Patch {
is_result_of_binary_to_text_conversion: prep.old_or_new_is_derived,
hunks,
lines_added,
lines_removed,
}
}
Operation::ExternalCommand { .. } => {
unreachable!(
"BUG: `gix` disables this, as it knows we always need to be able to run our own diff machinery"
)
}
Operation::SourceOrDestinationIsBinary => {
use gix::diff::blob::platform::resource::Data;
fn size_for_data(data: Data<'_>) -> Option<u64> {
match data {
Data::Missing | Data::Buffer { .. } => None,
Data::Binary { size } => Some(size),
}
}
let (old, new) = diff_filter
.resources()
.expect("prepare would have failed if a resource is missing");
let size = size_for_data(old.data)
.or(size_for_data(new.data))
.expect("BUG: one of the resources must have been binary/too big");
let big_file_size = repo.big_file_threshold()?;View on GitHub (pinned to caf1f223d3)
Solutions
- Build the diff platform with external drivers disabled (pass no driver lookup), as the unified-diff code intends.
- Unset `diff.external` in the affected repository or global config if you control the environment.
- After gix upgrades, re-run unified-diff tests against a repo with gitattributes diff drivers to catch plumbing changes.
Example fix
# environment-level workaround # before git config --global diff.external 'my-diff-tool' # after (let GitButler run its own diff machinery) git config --global --unset diff.external
Defensive patterns
Strategy: validation
Validate before calling
// refuse to run when an external diff driver would hijack blob diffs
let ext = repo.config().string("diff.external")?;
if ext.is_some() {
anyhow::bail!("diff.external is set ({:?}); unset it or diff with drivers disabled", ext.as_ref().map(|s| s.to_string()));
} Prevention
- Construct the gix blob-diff platform with external drivers disabled, as but-core's unified_diff does.
- Unset diff.external in repos where GitButler panics on diffing.
- Test against repositories with gitattributes diff drivers after gix upgrades.
When it happens
Trigger: Constructing the gix diff platform with drivers looked up from repository config while `diff.external` or a `diff=<driver>` gitattribute is set; a refactor that starts passing `gix::diff::blob::Drivers` from config; a gix upgrade changing driver plumbing defaults.
Common situations: User repositories with `diff.external` configured or `.gitattributes` diff drivers; corporate environments setting `diff.external` globally; code reuse of a generic platform builder.
Related errors
- we never return these as the status iteration is configured
- BUG: we use 'matching' so there are no directories
- disabled
- BUG: this must have been deactivated
- worktree-changes are always set if there are hunks
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/b735b2a8c6a8be25.
Report an issue: GitHub.