diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 7fe34db11d..46b9977968 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -573,19 +573,14 @@ declare module 'vscode' { } /** - * Represents an auto-mode model routing resolution. Displayed as a collapsible - * widget in the chat stream showing which model was selected and why. + * Explains what the "Auto" model routed a turn to, as a single status line. + * Push a part without a model for the in-flight state, then a resolved one. + * Auto may route several times in a turn; each route gets its own row. */ export class ChatResponseAutoModeResolutionPart { - /** The model ID that was selected by the router */ - resolvedModel: string; - /** The user-facing display name of the resolved model */ - resolvedModelName: string; - /** The router's classification label */ - predictedLabel: string; - /** Confidence score (0-1) from the router */ - confidence: number; - constructor(resolvedModel: string, resolvedModelName: string, predictedLabel: string, confidence: number); + /** The model the router picked, or `undefined` while routing is in flight. */ + resolvedModel: { id: string; name: string } | undefined; + constructor(resolvedModel?: { id: string; name: string }); } export interface ChatResponseStream { diff --git a/src/github/activityBarViewProvider.ts b/src/github/activityBarViewProvider.ts index 03ffb923a7..7595d5111e 100644 --- a/src/github/activityBarViewProvider.ts +++ b/src/github/activityBarViewProvider.ts @@ -17,7 +17,7 @@ import { IComment } from '../common/comment'; import { emojify, ensureEmojis } from '../common/emoji'; import { disposeAll } from '../common/lifecycle'; import Logger from '../common/logger'; -import { CHECKOUT_DEFAULT_BRANCH, CHECKOUT_PULL_REQUEST_BASE_BRANCH, DELETE_BRANCH_AFTER_MERGE, POST_DONE, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; +import { CHECKOUT_DEFAULT_BRANCH, CHECKOUT_PULL_REQUEST_BASE_BRANCH, POST_DONE, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; import { ReviewEvent, TimelineEvent } from '../common/timelineEvent'; import { formatError } from '../common/utils'; import { generateUuid } from '../common/uuid'; @@ -486,18 +486,17 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W if (!result.merged) { vscode.window.showErrorMessage(vscode.l10n.t('Merging pull request failed: {0}', result?.message ?? '')); - } else { - // Check if auto-delete branch setting is enabled - const deleteBranchAfterMerge = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(DELETE_BRANCH_AFTER_MERGE, false); - if (deleteBranchAfterMerge) { - // Automatically delete the branch after successful merge - await PullRequestReviewCommon.autoDeleteBranchesAfterMerge(this._folderRepositoryManager, this._item); - } } this._replyMessage(message, { state: result.merged ? GithubItemStateEnum.Merged : GithubItemStateEnum.Open, }); + if (result.merged) { + const branchDeletionMessage = await PullRequestReviewCommon.handleBranchDeletionAfterMerge(this._folderRepositoryManager, this._item); + if (branchDeletionMessage) { + this._postMessage(branchDeletionMessage); + } + } } catch (e) { vscode.window.showErrorMessage(vscode.l10n.t('Unable to merge pull request. {0}', formatError(e))); diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index c39e55f05d..0e012f17c1 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -857,13 +857,6 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel(DELETE_BRANCH_AFTER_MERGE, false); - if (deleteBranchAfterMerge) { - // Automatically delete the branch after successful merge - await PullRequestReviewCommon.autoDeleteBranchesAfterMerge(this._folderRepositoryManager, this._item); - } } const mergeResult: MergeResult = { @@ -872,6 +865,13 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel { + try { + const deleteBranchAfterMerge = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(DELETE_BRANCH_AFTER_MERGE, false); + if (deleteBranchAfterMerge) { + await autoDeleteBranchesAfterMerge(folderRepositoryManager, item); + } else if ((await item.githubRepository.getMetadata()).delete_branch_on_merge) { + const result = await deleteBranch(folderRepositoryManager, item); + return result.isReply ? undefined : result.message; + } + } catch (e) { + Logger.error(`Branch cleanup after merge failed: ${formatError(e)}`, 'PullRequestReviewCommon'); + } + } + async function performBranchDeletion(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel, defaultBranch: string, branchInfo: BranchInfo, selectedActions: SelectedAction[]): Promise { const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo?.branch); const deletedBranchTypes: string[] = []; diff --git a/src/test/github/pullRequestOverview.test.ts b/src/test/github/pullRequestOverview.test.ts index d0469014fa..67ce2fee76 100644 --- a/src/test/github/pullRequestOverview.test.ts +++ b/src/test/github/pullRequestOverview.test.ts @@ -23,7 +23,7 @@ import { GitApiImpl } from '../../api/api1'; import { CredentialStore } from '../../github/credentials'; import { GitHubServerType } from '../../common/authentication'; import { GitHubRemote } from '../../common/remote'; -import { CheckState } from '../../github/interface'; +import { CheckState, GithubItemStateEnum } from '../../github/interface'; import { CreatePullRequestHelper } from '../../view/createPullRequestHelper'; import { RepositoriesManager } from '../../github/repositoriesManager'; import { MockThemeWatcher } from '../mocks/mockThemeWatcher'; @@ -189,4 +189,43 @@ describe('PullRequestOverview', function () { assert.strictEqual(panel1!.getCurrentTitle(), '#2000 New feature'); }); }); + + describe('mergePullRequest', function () { + it('prompts to delete the local branch when GitHub deletes branches after merge', async function () { + repo.buildMetadata(repository => repository.delete_branch_on_merge!(true)); + repo.addGraphQLPullRequest(builder => { + builder.pullRequest(response => { + response.repository(r => { + r.pullRequest(pr => pr.number(1000)); + }); + }); + }); + + const prItem = convertRESTPullRequestToRawPullRequest(new PullRequestBuilder().number(1000).build(), repo); + const prModel = new PullRequestModel(credentialStore, telemetry, repo, remote, prItem); + const identity = { owner: prModel.remote.owner, repo: prModel.remote.repositoryName, number: prModel.number }; + await PullRequestOverviewPanel.createOrShow(telemetry, EXTENSION_URI, pullRequestManager, identity, prModel); + + const panel = PullRequestOverviewPanel.findPanel(identity.owner, identity.repo, identity.number)!; + sinon.stub(prModel, 'merge').resolves({ merged: true, message: '', timeline: [] }); + sinon.stub(pullRequestManager, 'getBranchNameForPullRequest').resolves({ + branch: 'new-feature', + createdForPullRequest: false, + }); + sinon.stub(pullRequestManager, 'getPullRequestRepositoryDefaultBranch').resolves('main'); + const showQuickPick = sinon.stub(vscode.window, 'showQuickPick').resolves(undefined); + const replyMessage = sinon.stub(panel as any, '_replyMessage'); + + await (panel as any).mergePullRequest({ + command: 'pr.merge', + args: { title: '', description: '', method: 'squash' }, + }); + + assert.strictEqual(showQuickPick.calledOnce, true); + const actions = showQuickPick.firstCall.args[0] as readonly (vscode.QuickPickItem & { type: string })[]; + assert.strictEqual(actions.some(action => action.type === 'local'), true); + assert.strictEqual(replyMessage.firstCall.args[1].state, GithubItemStateEnum.Merged); + sinon.assert.callOrder(replyMessage, showQuickPick); + }); + }); });