{"record":{"id":"efa9d02f71638519","repo":"gitbutlerapp/gitbutler","slug":"source-commit-must-not-be-conflicted","errorCode":null,"errorMessage":"Source commit must not be conflicted","messagePattern":"Source commit must not be conflicted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/commit/move_changes.rs","lineNumber":67,"sourceCode":"    let (destination_selector, destination_commit) =\n        editor.find_selectable_commit(destination_commit)?;\n\n    // Early return if source and destination are the same\n    if source_commit.id == destination_commit.id {\n        // Select the commit to get a valid selector, then just rebase (no-op)\n        let outcome = editor.rebase()?;\n        return Ok(MoveChangesOutcome {\n            rebase: outcome,\n            source_selector,\n            destination_selector,\n        });\n    }\n\n    // Step 1: Get the source commit and its tree\n    let source_tree_id = {\n        let source_commit = source_commit.clone().attach(editor.repo());\n        if source_commit.is_conflicted() {\n            bail!(\"Source commit must not be conflicted\")\n        }\n        source_commit.tree\n    };\n\n    let (source_tree_without_changes_id, dropped_diffs) = create_tree_without_diff(\n        editor.repo(),\n        ChangesSource::Commit {\n            id: source_commit.id,\n        },\n        changes_to_move,\n        context_lines,\n    )?;\n\n    if !dropped_diffs.is_empty() {\n        bail!(\"Failed to extract described changes from source commit\");\n    }\n\n    let new_source_commit_id = {","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-workspace/src/commit/move_changes.rs#L49-L85","documentation":"move_changes_between_commits requires the source commit to be conflict-free, because it computes a new tree by removing the described diffs from the source tree — impossible to do reliably on a conflicted tree. It bails before any tree rewrite.","triggerScenarios":"Calling move_changes_between_commits (or any wrapper like move_file_between_commits) where source_commit.clone().attach(repo).is_conflicted() is true.","commonSituations":"Moving file changes out of a commit that is mid-conflict after a rebase/merge; automated reassignment flows that don't check commit conflict state first.","solutions":["Resolve the source commit's conflicts first, then retry the move","Pick a different source commit that is not conflicted","Abort the conflicted operation and redo it before moving changes"],"exampleFix":"// before\neditor.move_changes_between_commits(conflicted_src, dst, changes, ctx)?;\n// after\nif conflicted_src.attach(repo).is_conflicted() { resolve_first()?; }\neditor.move_changes_between_commits(conflicted_src, dst, changes, ctx)?;","handlingStrategy":"validation","validationCode":"if source.attach(editor.repo()).is_conflicted() {\n    bail!(\"resolve source conflicts before moving changes\");\n}\neditor.move_changes_between_commits(source_selector, dest_selector, changes, ctx)?;","typeGuard":"fn moveable(commit: &gix::Commit) -> bool { !commit.clone().attach(commit.repo).is_conflicted() }","tryCatchPattern":"match editor.move_changes_between_commits(src, dst, changes, ctx) {\n    Err(e) if e.to_string().contains(\"Source commit must not be conflicted\") => resolve_then_retry(src),\n    other => other,\n}","preventionTips":["Check conflict state on both source and destination before moves","Resolve rebase/merge conflicts before reassigning changes","Avoid moving changes out of commits created during conflicted operations"],"tags":["git","conflict","move","precondition"],"backgroundTag":"invalid-state-transition","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"}