{"record":{"id":"93052e244ca06155","repo":"gitbutlerapp/gitbutler","slug":"bug-cannot-possibly-not-have-any-changes-here","errorCode":null,"errorMessage":"BUG: Cannot possibly not have any changes here","messagePattern":"BUG: Cannot possibly not have any changes here","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/move.rs","lineNumber":840,"sourceCode":"        (None, Some(files), None) => {\n            let mut builder = DiffSpecBuilder::new(repo, context_lines);\n            let source_commit = files.head.0.clone();\n            for (commit, path) in files {\n                if commit.as_ref() != source_commit.as_ref() {\n                    return Err(\n                        bad_input(\"Cannot move changes from multiple commits\")\n                            .hint(\"Move changes from a single commit at first, then squash additional changes into the new commit\")\n                            .into()\n                    );\n                }\n\n                builder.push_changes_from_committed_file(commit.commit_id, path.as_bstr())?;\n            }\n\n            // It doesn't appear as if we need to sort DiffSpecs when they're resolved on a file\n            // level. For the future hunk level DiffSpecs we may need to, however.\n            let changes = NonEmpty::from_vec(builder.into_diff_specs())\n                .expect(\"BUG: Cannot possibly not have any changes here\");\n\n            Ok(ResolvedSources::CommittedChanges((source_commit, changes)))\n        }\n        (None, None, Some(branches)) => {\n            if !branches.tail.is_empty() {\n                Err(bad_input(\"Branches can only be moved one at a time\")\n                    .arg_name(\"<SOURCES>\")\n                    .into())\n            } else {\n                Ok(ResolvedSources::Branch(branches.head))\n            }\n        }\n        (None, None, None) => panic!(\"BUG: It should not be possible to omit sources\"),\n        (_, _, _) => Err(bad_input(\"Mixing source types is not allowed\")\n            .hint(\"You can only move one kind of source (e.g. commits) at a time\")\n            .arg_name(\"<SOURCES>\")\n            .into()),\n    }","sourceCodeStart":822,"sourceCodeEnd":858,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/command/legacy/move.rs#L822-L858","documentation":"Panic while resolving committed-changes sources for move: after pushing each selected committed file via push_changes_from_committed_file, the accumulated DiffSpec list is empty. Selection guarantees at least one committed file, so an empty result means the builder matched nothing - a selected path that is absent from the referenced commit (rename, case or unicode-normalization mismatch) silently appended no changes.","triggerScenarios":"Selecting a committed file whose path no longer exists in that commit (renamed between commits); case-sensitivity differences on case-insensitive filesystems; NFC/NFD unicode normalization differences (typical on macOS); a file selection computed against a different commit than the one passed in.","commonSituations":"Moving changes right after a rename; paths copied from older tooling output with different normalization; cross-platform checkouts normalizing filenames.","solutions":["Re-derive the selection from the current commit state (diff view or 'but commit list') and retry","Check exact path casing and unicode normalization against 'git ls-tree <commit>'","Maintainer: make push_changes_from_committed_file report when a path matches nothing so the failure is explained instead of panicking later"],"exampleFix":"// before\nbuilder.push_changes_from_committed_file(commit.commit_id, path.as_bstr())?;\n// ...\nlet changes = NonEmpty::from_vec(builder.into_diff_specs())\n    .expect(\"BUG: Cannot possibly not have any changes here\");\n\n// after - fail per path so an empty result is explained where it happens\nlet specs = builder.push_changes_from_committed_file(commit.commit_id, path.as_bstr())?;\nif specs == 0 {\n    anyhow::bail!(\"path {} not found in commit {}\", path.as_bstr(), commit.commit_id);\n}","handlingStrategy":"validation","validationCode":"// Before building the move operation, confirm each selected path exists in the commit\nlet tree = repo.find_tree(commit_id)?;\nfor path in paths.iter() {\n    if tree.lookup_entry_by_path(path.as_bstr() as &bstr::BStr).is_err() {\n        anyhow::bail!(\"path {} not present in commit {}\", path.as_bstr(), commit_id);\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive file selections from the commit's actual tree (ls-tree/diff) instead of stale cached state","Normalize path casing and unicode form (NFC) consistently across platforms","Have builders report 'matched 0 entries' as an error at push time rather than an empty result"],"tags":["rust","panic","move","diff-spec","path-mismatch","nonempty"],"backgroundTag":"nonempty-invariant-violation","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}