{"record":{"id":"857eb25cd15bfbeb","repo":"gitbutlerapp/gitbutler","slug":"no-committed-changes-to-discard","errorCode":null,"errorMessage":"No committed changes to discard","messagePattern":"No committed changes to discard","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/but/src/command/legacy/discard.rs","lineNumber":428,"sourceCode":"                        commits.push(commit);\n                    }\n                }\n                commits\n            };\n            ExecutableDiscardOperation::Branches { branches, commits }\n        }\n        DiscardOperation::Commits(commits) => ExecutableDiscardOperation::Commits(commits),\n        DiscardOperation::CommittedFiles { source, paths } => {\n            let changes = {\n                let context_lines = ctx.settings.context_lines;\n                let (repo, ..) = ctx.workspace_and_db_mut_with_perm(perm.read_permission())?;\n                let mut builder = DiffSpecBuilder::new(&repo, context_lines);\n                for path in &paths {\n                    builder.push_changes_from_committed_file(source.commit_id, path.as_bstr())?;\n                }\n                builder.into_diff_specs()\n            };\n            anyhow::ensure!(!changes.is_empty(), \"No committed changes to discard\");\n            ExecutableDiscardOperation::CommittedFiles {\n                source,\n                paths,\n                changes,\n            }\n        }\n        DiscardOperation::Uncommitted(selection) => {\n            let changes = {\n                let context_lines = ctx.settings.context_lines;\n                let (repo, ..) = ctx.workspace_and_db_mut_with_perm(perm.read_permission())?;\n                let mut builder = DiffSpecBuilder::new(&repo, context_lines);\n                match selection {\n                    UncommittedSelection::All => builder.push_changes_from_uncommitted_area()?,\n                    UncommittedSelection::Changes(changes) => {\n                        for change in *changes {\n                            match change {\n                                UncommittedDiscardSource::HunkOrFile(change) => {\n                                    builder.push_changes_from_uncommitted(&change)?;","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/legacy/discard.rs#L410-L446","documentation":"When discarding committed files (DiscardOperation::CommittedFiles), the code builds DiffSpecs for each (source commit, path) pair via DiffSpecBuilder::push_changes_from_committed_file, then asserts the result is non-empty with anyhow::ensure!. The error means every listed path produced no diff against the source commit — nothing would be reverted, so the (transactional, undo-recorded) discard is refused rather than recorded as a no-op.","triggerScenarios":"Calling discard-committed-files with paths that are identical in the source commit and the current state (already reverted, or the paths never differed), or with paths misspelled/case-mismatched so each push_changes_from_committed_file contributes nothing.","commonSituations":"Re-running a discard command from history after the first run already restored the files; path casing on case-insensitive filesystems; passing directory paths expecting recursion when only exact file specs match.","solutions":["Verify there is actually something to revert: `git diff <source_commit> -- <paths>` should show output before you discard.","If files are already restored, no action is needed — this error is the safe no-op guard.","Check exact path spelling/casing against `git ls-tree <commit>` output."],"exampleFix":"# before\n$ but discard --files src/main.rs --from <sha>   # already reverted earlier\nError: No committed changes to discard\n\n# after: confirm there is a diff first\n$ git diff <sha> -- src/main.rs   # shows hunks, then retry; empty means nothing to do","handlingStrategy":"validation","validationCode":"// Confirm a diff exists before discarding committed files\nlet has_changes = paths.iter().any(|p| {\n    repo.diff_tree_to_path(source.commit_id, Some(p)).lines().count() > 0\n});\nanyhow::ensure!(has_changes, \"nothing differs; skip the discard entirely\");","typeGuard":null,"tryCatchPattern":"if let Err(e) = discard_committed_files(ctx, source, paths) {\n    if e.to_string().contains(\"No committed changes to discard\") {\n        // already clean — treat as success\n        return Ok(());\n    }\n    return Err(e);\n}","preventionTips":["Diff-check paths against the source commit before invoking discard.","Don't re-run discard commands from history; check state first."],"tags":["discard","diff","committed-files","guard"],"backgroundTag":"nothing-to-discard","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}