gitbutlerapp/gitbutler · error
The source of changes cannot have a conflicted 'after' side.
Error message
The source of changes cannot have a conflicted 'after' side.
What it means
ChangesSource::Commit resolves the 'after' side of a diff to the commit's tree. If that commit is conflicted (contains unresolved merge state), the resulting diff is not well-defined, so the accessor refuses rather than diffing against a conflicted tree.
Solutions
- Resolve the conflict and re-commit the commit before using it as a change source
- Use ChangesSource::Tree { after_id } with a clean tree instead
- Filter out conflicted commits when selecting change sources programmatically
Example fix
// before
let source = ChangesSource::Commit { id: conflicted_id };
create_tree_without_diff(repo, source, specs)?;
// after
if !commit.is_conflicted() {
create_tree_without_diff(repo, source, specs)?;
} else { resolve_first()?; } Defensive patterns
Strategy: type-guard
Validate before calling
let commit = but_core::Commit::from_id(id.attach(&repo))?;
if commit.is_conflicted() { return Err(ConflictNeedsResolution); } Type guard
fn clean_commit_source(id: gix::ObjectId, repo: &gix::Repository) -> Option<ChangesSource> {
let c = but_core::Commit::from_id(id.attach(repo)).ok()?;
(!c.is_conflicted()).then_some(ChangesSource::Commit { id })
} Try / catch
match result {
Err(e) if e.to_string().contains("conflicted 'after' side") => {
// resolve conflict or switch to a clean tree source
}
other => other?,
} Prevention
- Skip conflicted commits when enumerating change sources
- Prefer ChangesSource::Tree with a known-clean tree for automated flows
- Resolve workspace conflicts before running tree-manipulation operations
When it happens
Trigger: Using a conflicted commit as a ChangesSource::Commit in create_tree_without_diff / uncommit paths, e.g. a commit produced by a merge with conflict markers or tracked conflict state.
Common situations: Workspaces with unresolved conflicts from merges or failed squashes; automation enumerating workspace commits without filtering out conflicted ones.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Cannot uncommit changes from a conflicted commit
- Cannot amend a conflicted commit
- cannot amend into : the commit is immutable (not part of a…
- Cannot merge : merging into / resulted in conflicts. Rebase…
- Cannot squash commits that would result in merge conflicts
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/730121b53542cf4e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/tree_manipulation/create_tree_without_diff.rs:47
ChangesSource::Commit { id } => {
let commit = repository.find_commit(*id)?;
if let Some(parent_id) = commit.parent_ids().next() {
let parent = but_core::Commit::from_id(parent_id)?;
Ok(parent.tree_id_or_auto_resolution()?.object()?.into_tree())
} else {
Ok(repository.empty_tree())
}
}
ChangesSource::Tree { before_id, .. } => Ok(repository.find_tree(*before_id)?),
}
}
fn after<'a>(&self, repository: &'a gix::Repository) -> anyhow::Result<gix::Tree<'a>> {
match self {
ChangesSource::Commit { id } => {
let commit = but_core::Commit::from_id(id.attach(repository))?;
if commit.is_conflicted() {
anyhow::bail!("The source of changes cannot have a conflicted 'after' side.");
}
Ok(repository.find_tree(commit.tree)?)
}
ChangesSource::Tree { after_id, .. } => Ok(repository.find_tree(*after_id)?),
}
}
}
/// Discard the given `changes` in either the work tree or an arbitrary commit or tree. If a change could not be matched with an
/// actual worktree change, for instance due to a race, that's not an error, instead it will be returned in the result Vec, along
/// with all hunks that couldn't be matched.
///
/// The returned Vec is typically empty, meaning that all `changes` could be discarded.
///
/// `context_lines` is the amount of context lines we should assume when obtaining hunks of worktree changes to match against
/// the ones we have specified in the hunks contained within `changes`.
///
/// Discarding a change is really more of an 'undo' of a change as it will restore the previous state to the desired extent - GitView on GitHub (pinned to 58e5313667)