{"record":{"id":"5627b88d117791bf","repo":"gitbutlerapp/gitbutler","slug":"no-commits-were-provided-to-squash","errorCode":null,"errorMessage":"No commits were provided to squash","messagePattern":"No commits were provided to squash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/commit/squash.rs","lineNumber":54,"sourceCode":"        guard.write_permission(),\n    )\n}\n\n/// Squash `subject_commit_ids` into `target_commit_id` under caller-held\n/// exclusive repository access.\n///\n/// This variant does not create an oplog entry. When `dry_run` is enabled, it\n/// returns a preview of the resulting workspace state without materializing the rebases.\npub fn commit_squash_only_with_perm(\n    ctx: &mut but_ctx::Context,\n    subject_commit_ids: Vec<gix::ObjectId>,\n    target_commit_id: gix::ObjectId,\n    how_to_combine_messages: MessageCombinationStrategy,\n    dry_run: DryRun,\n    perm: &mut RepoExclusive,\n) -> anyhow::Result<CommitSquashResult> {\n    if subject_commit_ids.is_empty() {\n        anyhow::bail!(\"No commits were provided to squash\")\n    }\n    let mut meta = ctx.meta()?;\n    let (repo, mut ws, mut db) = ctx.workspace_mut_and_db_mut_with_perm(perm)?;\n    let editor = Editor::create(&mut ws, &mut meta, &repo, &mut db)?;\n    let SquashCommitsOutcome {\n        rebase,\n        commit_selector,\n    } = but_workspace::commit::squash_commits(\n        editor,\n        subject_commit_ids,\n        target_commit_id,\n        how_to_combine_messages,\n    )?;\n    let new_commit = rebase.lookup_pick(commit_selector)?;\n    let workspace = WorkspaceState::from_successful_rebase(rebase, &repo, dry_run)?;\n\n    Ok(CommitSquashResult {\n        new_commit,","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-api/src/commit/squash.rs#L36-L72","documentation":"commit_squash_only_with_perm (the squash entry point behind the but-api squash surface) validates up front that subject_commit_ids is non-empty; an empty selection means there is nothing to fold into the target commit, so it bails before opening the workspace or creating an Editor.","triggerScenarios":"Calling commit_squash_only_with_perm or its but-api wrapper with an empty Vec<gix::ObjectId> — typically a UI passing an empty multi-selection or a script computing a commit list from a filter that matched nothing.","commonSituations":"Frontends not disabling the squash action with nothing selected; batch pipelines looping over zero matched commits; selection state lost before the call.","solutions":["Pass at least one commit id to squash into the target commit.","Guard the caller: treat an empty selection as a no-op or disable the action instead of invoking the API.","In UIs, require a non-empty selection before enabling squash."],"exampleFix":"// before\nbut_api::commit_squash_only_with_perm(ctx, vec![], target_id, how, DryRun::No, perm)?;\n\n// after\nif !subject_commit_ids.is_empty() {\n    but_api::commit_squash_only_with_perm(ctx, subject_commit_ids, target_id, how, DryRun::No, perm)?;\n}","handlingStrategy":"validation","validationCode":"fn can_squash(subject_commit_ids: &[gix::ObjectId]) -> bool {\n    !subject_commit_ids.is_empty()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate a non-empty selection at the UI/CLI boundary.","Treat an empty selection as a no-op in callers rather than an API error.","Compute commit lists before invoking and bail early when filters match nothing."],"tags":["squash","commits","input-validation","api"],"backgroundTag":"empty-commit-selection","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}