gitbutlerapp/gitbutler · error
' ' has no unresolved conflict
Error message
'{rela_path}' has no unresolved conflict What it means
resolve_worktree_conflicts writes resolutions for a conflicted path by rewriting its index stages (Base, Ours, theirs). If removing/re-checking the stages does not change the sorted-entry count, the path has no unresolved conflict stages, so there is nothing to resolve and the call is rejected.
Solutions
- Only pass paths that appear as conflicted (multi-stage) in the current index; refresh the conflict list right before resolving
- Skip paths whose entries are already stage-0 instead of passing them to the API
- Check whether an earlier resolve_worktree_conflicts call already handled the path (avoid double resolution)
- Re-read the index after operations that may have resolved conflicts implicitly
Example fix
// before: resolving every recorded path blindly
resolve_worktree_conflicts(&wt, &mut index, all_conflicted_paths)?;
// after: filter to paths still multi-stage
let still_conflicted: Vec<_> = all_conflicted_paths.into_iter()
.filter(|p| index.entries_with_path_by_stage(p).map(|e| e.len() > 1).unwrap_or(false))
.collect();
resolve_worktree_conflicts(&wt, &mut index, still_conflicted)?; Defensive patterns
Strategy: validation
Validate before calling
fn has_unresolved_conflict(index: &gix::index::File, rela_path: &str) -> bool {
index.entries_with_path(rela_path)
.map(|entries| entries.len() > 1 || entries.iter().any(|e| e.stage() != 0))
.unwrap_or(false)
} Try / catch
match resolve_worktree_conflicts(&wt, &mut index, &paths) {
Err(e) if e.to_string().contains("has no unresolved conflict") => {
// path already resolved — skip and continue
}
r => r,
} Prevention
- Refresh the conflict listing immediately before resolving
- Skip paths already reduced to a single stage-0 entry
- Don't call resolve twice for the same path in one run
- Re-read the index after any operation that could auto-resolve conflicts
When it happens
Trigger: Calling resolve_worktree_conflicts with a rela_path whose index entries are already fully resolved (single stage-0 entry) or that has no conflict entries at all — e.g. resolving the same path twice, or a path that was never conflicted.
Common situations: Double-applying a resolution after an earlier call already cleaned the stages; passing a path from a stale conflict listing generated before a prior resolution or a `git checkout`/rebase updated the index; scripting bulk resolutions where some paths auto-merged.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Branch " " has no conflicted commits.
- `but setup` cannot run from a linked worktree; run it from…
- Cannot currently work in repositories without a worktree
- Cannot currently work in repositories without a worktree
- Cannot hash directory entries that aren't files or symlinks
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/5643bd700d606387.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/worktree.rs:143
let rela_paths: BTreeSet<_> = rela_paths.into_iter().collect();
let mut index = repo.index_or_empty()?.into_owned_or_cloned();
let (mut pipeline, index_for_filter) = repo.filter_pipeline(None)?;
let mut path_check = gix::status::plumbing::SymlinkCheck::new(
repo.workdir().context("non-bare repository")?.into(),
);
let mut num_sorted_entries = index.entries().len();
for rela_path in &rela_paths {
let rela_path = rela_path.as_bstr();
let entries_before = num_sorted_entries;
delete_entry_by_path_bounded_stages(
&mut index,
rela_path,
&mut num_sorted_entries,
&[Stage::Base, Stage::Ours, Stage::Theirs],
);
if entries_before == num_sorted_entries {
anyhow::bail!("'{rela_path}' has no unresolved conflict");
}
// Stat before hashing so a write in between is caught by the stat mismatch.
let md = match gix::index::fs::Metadata::from_path_no_follow(
&path_check.verified_path_allow_nonexisting(rela_path)?,
) {
Ok(md) => md,
Err(err) if gix::fs::io_err::is_not_found(err.kind(), err.raw_os_error()) => {
delete_entry_by_path_bounded_stages(
&mut index,
rela_path,
&mut num_sorted_entries,
&[Stage::Unconflicted],
);
continue;
}
Err(err) => return Err(err.into()),
};
let (id, kind, _) = pipelineView on GitHub (pinned to 58e5313667)