{"record":{"id":"730121b53542cf4e","repo":"gitbutlerapp/gitbutler","slug":"the-source-of-changes-cannot-have-a-conflicted-after-side","errorCode":null,"errorMessage":"The source of changes cannot have a conflicted 'after' side.","messagePattern":"The source of changes cannot have a conflicted 'after' side\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/tree_manipulation/create_tree_without_diff.rs","lineNumber":47,"sourceCode":"            ChangesSource::Commit { id } => {\n                let commit = repository.find_commit(*id)?;\n                if let Some(parent_id) = commit.parent_ids().next() {\n                    let parent = but_core::Commit::from_id(parent_id)?;\n                    Ok(parent.tree_id_or_auto_resolution()?.object()?.into_tree())\n                } else {\n                    Ok(repository.empty_tree())\n                }\n            }\n            ChangesSource::Tree { before_id, .. } => Ok(repository.find_tree(*before_id)?),\n        }\n    }\n\n    fn after<'a>(&self, repository: &'a gix::Repository) -> anyhow::Result<gix::Tree<'a>> {\n        match self {\n            ChangesSource::Commit { id } => {\n                let commit = but_core::Commit::from_id(id.attach(repository))?;\n                if commit.is_conflicted() {\n                    anyhow::bail!(\"The source of changes cannot have a conflicted 'after' side.\");\n                }\n                Ok(repository.find_tree(commit.tree)?)\n            }\n            ChangesSource::Tree { after_id, .. } => Ok(repository.find_tree(*after_id)?),\n        }\n    }\n}\n\n/// Discard the given `changes` in either the work tree or an arbitrary commit or tree. If a change could not be matched with an\n/// actual worktree change, for instance due to a race, that's not an error, instead it will be returned in the result Vec, along\n/// with all hunks that couldn't be matched.\n///\n/// The returned Vec is typically empty, meaning that all `changes` could be discarded.\n///\n/// `context_lines` is the amount of context lines we should assume when obtaining hunks of worktree changes to match against\n/// the ones we have specified in the hunks contained within `changes`.\n///\n/// Discarding a change is really more of an 'undo' of a change as it will restore the previous state to the desired extent - Git","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-workspace/src/tree_manipulation/create_tree_without_diff.rs#L29-L65","documentation":"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.","triggerScenarios":"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.","commonSituations":"Workspaces with unresolved conflicts from merges or failed squashes; automation enumerating workspace commits without filtering out conflicted ones.","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"],"exampleFix":"// before\nlet source = ChangesSource::Commit { id: conflicted_id };\ncreate_tree_without_diff(repo, source, specs)?;\n// after\nif !commit.is_conflicted() {\n    create_tree_without_diff(repo, source, specs)?;\n} else { resolve_first()?; }","handlingStrategy":"type-guard","validationCode":"let commit = but_core::Commit::from_id(id.attach(&repo))?;\nif commit.is_conflicted() { return Err(ConflictNeedsResolution); }","typeGuard":"fn clean_commit_source(id: gix::ObjectId, repo: &gix::Repository) -> Option<ChangesSource> {\n    let c = but_core::Commit::from_id(id.attach(repo)).ok()?;\n    (!c.is_conflicted()).then_some(ChangesSource::Commit { id })\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"conflicted 'after' side\") => {\n        // resolve conflict or switch to a clean tree source\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["git","merge-conflict","precondition"],"backgroundTag":"incompatible-source-type","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"}