From b08db058041fdcdfd0ab0a3e583095e857b146ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:15:09 +0000 Subject: [PATCH 1/9] Initial plan From 2dc39b65810d3a43e034a718ed4d1cc086a29c51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:19:59 +0000 Subject: [PATCH 2/9] Initial plan for delete branch after merge feature Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/@types/vscode.proposed.chatParticipantAdditions.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } From 2b4989fdd8177933a095771ce9af3fa53afb9ada Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:23:10 +0000 Subject: [PATCH 3/9] Add deleteBranchAfterMerge setting and implementation Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- package.json | 5 +++++ package.nls.json | 1 + src/common/settingKeys.ts | 1 + src/github/activityBarViewProvider.ts | 8 ++++++++ src/github/pullRequestOverview.ts | 9 ++++++++- 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 121adffe67..40a0862981 100644 --- a/package.json +++ b/package.json @@ -321,6 +321,11 @@ "default": true, "description": "%githubPullRequests.defaultDeletionMethod.selectRemote.description%" }, + "githubPullRequests.deleteBranchAfterMerge": { + "type": "boolean", + "default": false, + "description": "%githubPullRequests.deleteBranchAfterMerge.description%" + }, "githubPullRequests.terminalLinksHandler": { "type": "string", "enum": [ diff --git a/package.nls.json b/package.nls.json index 73c02b7684..e55e888940 100644 --- a/package.nls.json +++ b/package.nls.json @@ -39,6 +39,7 @@ "githubPullRequests.hideViewedFiles.description": "Hide files that have been marked as viewed in the pull request changes tree.", "githubPullRequests.defaultDeletionMethod.selectLocalBranch.description": "When true, the option to delete the local branch will be selected by default when deleting a branch from a pull request.", "githubPullRequests.defaultDeletionMethod.selectRemote.description": "When true, the option to delete the remote will be selected by default when deleting a branch from a pull request.", + "githubPullRequests.deleteBranchAfterMerge.description": "Automatically delete the branch after merging a pull request.", "githubPullRequests.terminalLinksHandler.description": "Default handler for terminal links.", "githubPullRequests.terminalLinksHandler.github": "Create the pull request on GitHub", "githubPullRequests.terminalLinksHandler.vscode": "Create the pull request in VS Code", diff --git a/src/common/settingKeys.ts b/src/common/settingKeys.ts index 1376c4c7bc..b3d358bf31 100644 --- a/src/common/settingKeys.ts +++ b/src/common/settingKeys.ts @@ -33,6 +33,7 @@ export const DEFAULT_MERGE_METHOD = 'defaultMergeMethod'; export const DEFAULT_DELETION_METHOD = 'defaultDeletionMethod'; export const SELECT_LOCAL_BRANCH = 'selectLocalBranch'; export const SELECT_REMOTE = 'selectRemote'; +export const DELETE_BRANCH_AFTER_MERGE = 'deleteBranchAfterMerge'; export const REMOTES = 'remotes'; export const PULL_PR_BRANCH_BEFORE_CHECKOUT = 'pullPullRequestBranchBeforeCheckout'; export type PullPRBranchVariants = 'never' | 'pull' | 'pullAndMergeBase' | 'pullAndUpdateBase' | true | false; diff --git a/src/github/activityBarViewProvider.ts b/src/github/activityBarViewProvider.ts index 00f623fa1f..bbea3fec0c 100644 --- a/src/github/activityBarViewProvider.ts +++ b/src/github/activityBarViewProvider.ts @@ -15,6 +15,7 @@ import { MergeArguments, PullRequest, ReviewType } from './views'; import { IComment } from '../common/comment'; import { emojify, ensureEmojis } from '../common/emoji'; import { disposeAll } from '../common/lifecycle'; +import { DELETE_BRANCH_AFTER_MERGE, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; import { ReviewEvent } from '../common/timelineEvent'; import { formatError } from '../common/utils'; import { generateUuid } from '../common/uuid'; @@ -409,6 +410,13 @@ 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.deleteBranch(this._folderRepositoryManager, this._item); + } } this._replyMessage(message, { diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 50aed1e41f..ea665679f9 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -31,7 +31,7 @@ import { COPILOT_SWE_AGENT, copilotEventToStatus, CopilotPRStatus, mostRecentCop import { commands, contexts } from '../common/executeCommands'; import { disposeAll } from '../common/lifecycle'; import Logger from '../common/logger'; -import { DEFAULT_MERGE_METHOD, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; +import { DEFAULT_MERGE_METHOD, DELETE_BRANCH_AFTER_MERGE, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; import { ITelemetry } from '../common/telemetry'; import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../common/timelineEvent'; import { asPromise, formatError } from '../common/utils'; @@ -626,6 +626,13 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel(DELETE_BRANCH_AFTER_MERGE, false); + if (deleteBranchAfterMerge) { + // Automatically delete the branch after successful merge + await PullRequestReviewCommon.deleteBranch(this._folderRepositoryManager, this._item); + } } const mergeResult: MergeResult = { From d7859a3a37a959cb5054c12aaf0d980b797eb55b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:29:23 +0000 Subject: [PATCH 4/9] Add autoDeleteBranchesAfterMerge function for silent branch deletion Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/activityBarViewProvider.ts | 2 +- src/github/pullRequestOverview.ts | 2 +- src/github/pullRequestReviewCommon.ts | 65 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/github/activityBarViewProvider.ts b/src/github/activityBarViewProvider.ts index bbea3fec0c..345582cd17 100644 --- a/src/github/activityBarViewProvider.ts +++ b/src/github/activityBarViewProvider.ts @@ -415,7 +415,7 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W 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.deleteBranch(this._folderRepositoryManager, this._item); + await PullRequestReviewCommon.autoDeleteBranchesAfterMerge(this._folderRepositoryManager, this._item); } } diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index ea665679f9..816f754880 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -631,7 +631,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel(DELETE_BRANCH_AFTER_MERGE, false); if (deleteBranchAfterMerge) { // Automatically delete the branch after successful merge - await PullRequestReviewCommon.deleteBranch(this._folderRepositoryManager, this._item); + await PullRequestReviewCommon.autoDeleteBranchesAfterMerge(this._folderRepositoryManager, this._item); } } diff --git a/src/github/pullRequestReviewCommon.ts b/src/github/pullRequestReviewCommon.ts index f6b76eacd1..ab3e790666 100644 --- a/src/github/pullRequestReviewCommon.ts +++ b/src/github/pullRequestReviewCommon.ts @@ -9,6 +9,7 @@ import { FolderRepositoryManager } from './folderRepositoryManager'; import { IAccount, isITeam, ITeam, MergeMethod, PullRequestMergeability, reviewerId, ReviewState } from './interface'; import { PullRequestModel } from './pullRequestModel'; import { PullRequest, ReadyForReviewReply, ReviewType, SubmitReviewReply } from './views'; +import Logger from '../common/logger'; import { DEFAULT_DELETION_METHOD, PR_SETTINGS_NAMESPACE, SELECT_LOCAL_BRANCH, SELECT_REMOTE } from '../common/settingKeys'; import { ReviewEvent, TimelineEvent } from '../common/timelineEvent'; import { Schemes } from '../common/uri'; @@ -403,4 +404,68 @@ export namespace PullRequestReviewCommon { }; } } + + /** + * Automatically delete branches after merge based on user preferences. + * This function does not show any prompts - it uses the default deletion method preferences. + */ + export async function autoDeleteBranchesAfterMerge(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel): Promise { + const branchInfo = await folderRepositoryManager.getBranchNameForPullRequest(item); + const defaultBranch = await folderRepositoryManager.getPullRequestRepositoryDefaultBranch(item); + + // Get user preferences for automatic deletion + const deleteLocalBranch = vscode.workspace + .getConfiguration(PR_SETTINGS_NAMESPACE) + .get(`${DEFAULT_DELETION_METHOD}.${SELECT_LOCAL_BRANCH}`, true); + + const deleteRemote = vscode.workspace + .getConfiguration(PR_SETTINGS_NAMESPACE) + .get(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`, true); + + const promises: Promise[] = []; + + // Delete remote head branch if it's not the default branch + if (item.isResolved()) { + const isDefaultBranch = defaultBranch === item.head.ref; + if (!isDefaultBranch && !item.isRemoteHeadDeleted) { + promises.push( + folderRepositoryManager.deleteBranch(item).then(() => { + return folderRepositoryManager.repository.fetch({ prune: true }); + }).catch(e => { + Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + }) + ); + } + } + + // Delete local branch if preference is set + if (branchInfo && deleteLocalBranch) { + const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || + (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo.branch); + + promises.push( + (async () => { + if (isBranchActive) { + // Checkout default branch before deleting the active branch + await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); + } + await folderRepositoryManager.repository.deleteBranch(branchInfo.branch, true); + })().catch(e => { + Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + }) + ); + } + + // Delete remote if it's no longer used and preference is set + if (branchInfo && branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse && deleteRemote) { + promises.push( + folderRepositoryManager.repository.removeRemote(branchInfo.remote).catch(e => { + Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + }) + ); + } + + // Execute all deletions in parallel + await Promise.all(promises); + } } From f5d0f99380b23667b87c2ee6a8ad7ae877094ba4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:32:58 +0000 Subject: [PATCH 5/9] Address code review feedback: extract helper and improve error handling Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- ...ode.proposed.chatParticipantAdditions.d.ts | 1 - src/github/pullRequestReviewCommon.ts | 45 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index aa7001a3d2..71520fa1ec 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,7 +105,6 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; - presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/github/pullRequestReviewCommon.ts b/src/github/pullRequestReviewCommon.ts index ab3e790666..33715b27a5 100644 --- a/src/github/pullRequestReviewCommon.ts +++ b/src/github/pullRequestReviewCommon.ts @@ -345,7 +345,7 @@ export namespace PullRequestReviewCommon { const deletedBranchTypes: string[] = []; if (selectedActions) { - const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo?.branch); + const isBranchActiveForDeletion = branchInfo && isBranchActive(folderRepositoryManager, item, branchInfo.branch); const promises = selectedActions.map(async action => { switch (action.type) { @@ -359,7 +359,7 @@ export namespace PullRequestReviewCommon { } return; case 'local': - if (isBranchActive) { + if (isBranchActiveForDeletion) { if (folderRepositoryManager.repository.state.workingTreeChanges.length) { const yes = vscode.l10n.t('Yes'); const response = await vscode.window.showWarningMessage( @@ -405,6 +405,14 @@ export namespace PullRequestReviewCommon { } } + /** + * Check if a pull request's branch is currently active (checked out). + */ + function isBranchActive(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel, branchName: string): boolean { + return item.equals(folderRepositoryManager.activePullRequest) || + (folderRepositoryManager.repository.state.HEAD?.name === branchName); + } + /** * Automatically delete branches after merge based on user preferences. * This function does not show any prompts - it uses the default deletion method preferences. @@ -422,50 +430,41 @@ export namespace PullRequestReviewCommon { .getConfiguration(PR_SETTINGS_NAMESPACE) .get(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`, true); - const promises: Promise[] = []; + const deletionTasks: Promise[] = []; // Delete remote head branch if it's not the default branch if (item.isResolved()) { const isDefaultBranch = defaultBranch === item.head.ref; if (!isDefaultBranch && !item.isRemoteHeadDeleted) { - promises.push( - folderRepositoryManager.deleteBranch(item).then(() => { - return folderRepositoryManager.repository.fetch({ prune: true }); - }).catch(e => { - Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); - }) + deletionTasks.push( + folderRepositoryManager.deleteBranch(item) + .then(() => folderRepositoryManager.repository.fetch({ prune: true })) + .catch(e => Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) ); } } // Delete local branch if preference is set if (branchInfo && deleteLocalBranch) { - const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || - (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo.branch); - - promises.push( + deletionTasks.push( (async () => { - if (isBranchActive) { - // Checkout default branch before deleting the active branch + if (isBranchActive(folderRepositoryManager, item, branchInfo.branch)) { await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); } await folderRepositoryManager.repository.deleteBranch(branchInfo.branch, true); - })().catch(e => { - Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); - }) + })().catch(e => Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) ); } // Delete remote if it's no longer used and preference is set if (branchInfo && branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse && deleteRemote) { - promises.push( - folderRepositoryManager.repository.removeRemote(branchInfo.remote).catch(e => { - Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); - }) + deletionTasks.push( + folderRepositoryManager.repository.removeRemote(branchInfo.remote) + .catch(e => Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) ); } // Execute all deletions in parallel - await Promise.all(promises); + await Promise.all(deletionTasks); } } From 4cb17f13f2c6a0f7e3ea6cf17f48635da4a16045 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:19:54 +0000 Subject: [PATCH 6/9] Refactor to reuse deleteBranch logic via shared performBranchDeletion helper Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- ...ode.proposed.chatParticipantAdditions.d.ts | 1 + src/github/pullRequestReviewCommon.ts | 98 +++++++++++++------ 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/github/pullRequestReviewCommon.ts b/src/github/pullRequestReviewCommon.ts index 33715b27a5..3d74459e4a 100644 --- a/src/github/pullRequestReviewCommon.ts +++ b/src/github/pullRequestReviewCommon.ts @@ -350,15 +350,11 @@ export namespace PullRequestReviewCommon { const promises = selectedActions.map(async action => { switch (action.type) { case 'remoteHead': - await folderRepositoryManager.deleteBranch(item); + await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'remoteHead'); deletedBranchTypes.push(action.type); - await folderRepositoryManager.repository.fetch({ prune: true }); - // If we're in a remote repository, then we should checkout the default branch. - if (folderRepositoryManager.repository.rootUri.scheme === Schemes.VscodeVfs) { - await folderRepositoryManager.repository.checkout(defaultBranch); - } return; case 'local': + // Interactive deletion has special handling for dirty working tree if (isBranchActiveForDeletion) { if (folderRepositoryManager.repository.state.workingTreeChanges.length) { const yes = vscode.l10n.t('Yes'); @@ -373,16 +369,18 @@ export namespace PullRequestReviewCommon { return; } } - await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); } - await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true); - return deletedBranchTypes.push(action.type); + await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'local', isBranchActiveForDeletion); + deletedBranchTypes.push(action.type); + return; case 'remote': + await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'remote'); deletedBranchTypes.push(action.type); - return folderRepositoryManager.repository.removeRemote(branchInfo!.remote!); + return; case 'suspend': + await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'suspend'); deletedBranchTypes.push(action.type); - return vscode.commands.executeCommand('github.codespaces.disconnectSuspend'); + return; } }); @@ -413,6 +411,47 @@ export namespace PullRequestReviewCommon { (folderRepositoryManager.repository.state.HEAD?.name === branchName); } + /** + * Core branch deletion logic shared between interactive and automatic deletion. + * @param folderRepositoryManager The folder repository manager + * @param item The pull request model + * @param branchInfo Branch information for the pull request + * @param defaultBranch The default branch name + * @param actionType The type of deletion action to perform + * @param checkoutBeforeDelete If true, checkout default branch before deleting local branch (for active branches) + */ + async function performBranchDeletion( + folderRepositoryManager: FolderRepositoryManager, + item: PullRequestModel, + branchInfo: { branch: string; remote?: string; createdForPullRequest?: boolean; remoteInUse?: boolean } | undefined, + defaultBranch: string, + actionType: 'remoteHead' | 'local' | 'remote' | 'suspend', + checkoutBeforeDelete: boolean = false + ): Promise { + switch (actionType) { + case 'remoteHead': + await folderRepositoryManager.deleteBranch(item); + await folderRepositoryManager.repository.fetch({ prune: true }); + // If we're in a remote repository, then we should checkout the default branch. + if (folderRepositoryManager.repository.rootUri.scheme === Schemes.VscodeVfs) { + await folderRepositoryManager.repository.checkout(defaultBranch); + } + break; + case 'local': + if (checkoutBeforeDelete) { + await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); + } + await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true); + break; + case 'remote': + await folderRepositoryManager.repository.removeRemote(branchInfo!.remote!); + break; + case 'suspend': + await vscode.commands.executeCommand('github.codespaces.disconnectSuspend'); + break; + } + } + /** * Automatically delete branches after merge based on user preferences. * This function does not show any prompts - it uses the default deletion method preferences. @@ -430,41 +469,38 @@ export namespace PullRequestReviewCommon { .getConfiguration(PR_SETTINGS_NAMESPACE) .get(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`, true); - const deletionTasks: Promise[] = []; + // Build list of actions to execute based on preferences + const actions: Array<{ type: 'remoteHead' | 'local' | 'remote'; checkoutFirst?: boolean }> = []; // Delete remote head branch if it's not the default branch if (item.isResolved()) { const isDefaultBranch = defaultBranch === item.head.ref; if (!isDefaultBranch && !item.isRemoteHeadDeleted) { - deletionTasks.push( - folderRepositoryManager.deleteBranch(item) - .then(() => folderRepositoryManager.repository.fetch({ prune: true })) - .catch(e => Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) - ); + actions.push({ type: 'remoteHead' }); } } // Delete local branch if preference is set if (branchInfo && deleteLocalBranch) { - deletionTasks.push( - (async () => { - if (isBranchActive(folderRepositoryManager, item, branchInfo.branch)) { - await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); - } - await folderRepositoryManager.repository.deleteBranch(branchInfo.branch, true); - })().catch(e => Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) - ); + const needsCheckout = isBranchActive(folderRepositoryManager, item, branchInfo.branch); + actions.push({ type: 'local', checkoutFirst: needsCheckout }); } // Delete remote if it's no longer used and preference is set if (branchInfo && branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse && deleteRemote) { - deletionTasks.push( - folderRepositoryManager.repository.removeRemote(branchInfo.remote) - .catch(e => Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) - ); + actions.push({ type: 'remote' }); } - // Execute all deletions in parallel - await Promise.all(deletionTasks); + // Execute deletions with error handling + try { + await Promise.all( + actions.map(action => + performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, action.type, action.checkoutFirst) + .catch(e => Logger.warn(`Failed to delete ${action.type} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) + ) + ); + } catch (e) { + Logger.warn(`Failed to auto-delete branches for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + } } } From 00b52afe5907c3a1b147c2fc3074a5e3e1b59a22 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:35:29 +0100 Subject: [PATCH 7/9] Revert "Refactor to reuse deleteBranch logic via shared performBranchDeletion helper" This reverts commit 4cb17f13f2c6a0f7e3ea6cf17f48635da4a16045. --- ...ode.proposed.chatParticipantAdditions.d.ts | 1 - src/github/pullRequestReviewCommon.ts | 98 ++++++------------- 2 files changed, 31 insertions(+), 68 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index aa7001a3d2..71520fa1ec 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,7 +105,6 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; - presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/github/pullRequestReviewCommon.ts b/src/github/pullRequestReviewCommon.ts index 3d74459e4a..33715b27a5 100644 --- a/src/github/pullRequestReviewCommon.ts +++ b/src/github/pullRequestReviewCommon.ts @@ -350,11 +350,15 @@ export namespace PullRequestReviewCommon { const promises = selectedActions.map(async action => { switch (action.type) { case 'remoteHead': - await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'remoteHead'); + await folderRepositoryManager.deleteBranch(item); deletedBranchTypes.push(action.type); + await folderRepositoryManager.repository.fetch({ prune: true }); + // If we're in a remote repository, then we should checkout the default branch. + if (folderRepositoryManager.repository.rootUri.scheme === Schemes.VscodeVfs) { + await folderRepositoryManager.repository.checkout(defaultBranch); + } return; case 'local': - // Interactive deletion has special handling for dirty working tree if (isBranchActiveForDeletion) { if (folderRepositoryManager.repository.state.workingTreeChanges.length) { const yes = vscode.l10n.t('Yes'); @@ -369,18 +373,16 @@ export namespace PullRequestReviewCommon { return; } } + await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); } - await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'local', isBranchActiveForDeletion); - deletedBranchTypes.push(action.type); - return; + await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true); + return deletedBranchTypes.push(action.type); case 'remote': - await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'remote'); deletedBranchTypes.push(action.type); - return; + return folderRepositoryManager.repository.removeRemote(branchInfo!.remote!); case 'suspend': - await performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, 'suspend'); deletedBranchTypes.push(action.type); - return; + return vscode.commands.executeCommand('github.codespaces.disconnectSuspend'); } }); @@ -411,47 +413,6 @@ export namespace PullRequestReviewCommon { (folderRepositoryManager.repository.state.HEAD?.name === branchName); } - /** - * Core branch deletion logic shared between interactive and automatic deletion. - * @param folderRepositoryManager The folder repository manager - * @param item The pull request model - * @param branchInfo Branch information for the pull request - * @param defaultBranch The default branch name - * @param actionType The type of deletion action to perform - * @param checkoutBeforeDelete If true, checkout default branch before deleting local branch (for active branches) - */ - async function performBranchDeletion( - folderRepositoryManager: FolderRepositoryManager, - item: PullRequestModel, - branchInfo: { branch: string; remote?: string; createdForPullRequest?: boolean; remoteInUse?: boolean } | undefined, - defaultBranch: string, - actionType: 'remoteHead' | 'local' | 'remote' | 'suspend', - checkoutBeforeDelete: boolean = false - ): Promise { - switch (actionType) { - case 'remoteHead': - await folderRepositoryManager.deleteBranch(item); - await folderRepositoryManager.repository.fetch({ prune: true }); - // If we're in a remote repository, then we should checkout the default branch. - if (folderRepositoryManager.repository.rootUri.scheme === Schemes.VscodeVfs) { - await folderRepositoryManager.repository.checkout(defaultBranch); - } - break; - case 'local': - if (checkoutBeforeDelete) { - await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); - } - await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true); - break; - case 'remote': - await folderRepositoryManager.repository.removeRemote(branchInfo!.remote!); - break; - case 'suspend': - await vscode.commands.executeCommand('github.codespaces.disconnectSuspend'); - break; - } - } - /** * Automatically delete branches after merge based on user preferences. * This function does not show any prompts - it uses the default deletion method preferences. @@ -469,38 +430,41 @@ export namespace PullRequestReviewCommon { .getConfiguration(PR_SETTINGS_NAMESPACE) .get(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`, true); - // Build list of actions to execute based on preferences - const actions: Array<{ type: 'remoteHead' | 'local' | 'remote'; checkoutFirst?: boolean }> = []; + const deletionTasks: Promise[] = []; // Delete remote head branch if it's not the default branch if (item.isResolved()) { const isDefaultBranch = defaultBranch === item.head.ref; if (!isDefaultBranch && !item.isRemoteHeadDeleted) { - actions.push({ type: 'remoteHead' }); + deletionTasks.push( + folderRepositoryManager.deleteBranch(item) + .then(() => folderRepositoryManager.repository.fetch({ prune: true })) + .catch(e => Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) + ); } } // Delete local branch if preference is set if (branchInfo && deleteLocalBranch) { - const needsCheckout = isBranchActive(folderRepositoryManager, item, branchInfo.branch); - actions.push({ type: 'local', checkoutFirst: needsCheckout }); + deletionTasks.push( + (async () => { + if (isBranchActive(folderRepositoryManager, item, branchInfo.branch)) { + await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); + } + await folderRepositoryManager.repository.deleteBranch(branchInfo.branch, true); + })().catch(e => Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) + ); } // Delete remote if it's no longer used and preference is set if (branchInfo && branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse && deleteRemote) { - actions.push({ type: 'remote' }); - } - - // Execute deletions with error handling - try { - await Promise.all( - actions.map(action => - performBranchDeletion(folderRepositoryManager, item, branchInfo, defaultBranch, action.type, action.checkoutFirst) - .catch(e => Logger.warn(`Failed to delete ${action.type} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) - ) + deletionTasks.push( + folderRepositoryManager.repository.removeRemote(branchInfo.remote) + .catch(e => Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) ); - } catch (e) { - Logger.warn(`Failed to auto-delete branches for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); } + + // Execute all deletions in parallel + await Promise.all(deletionTasks); } } From be8587fa3a825a8312f6a2e13c9f0957001acc3e Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:35:57 +0100 Subject: [PATCH 8/9] Revert "Address code review feedback: extract helper and improve error handling" This reverts commit f5d0f99380b23667b87c2ee6a8ad7ae877094ba4. --- ...ode.proposed.chatParticipantAdditions.d.ts | 1 + src/github/pullRequestReviewCommon.ts | 45 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/github/pullRequestReviewCommon.ts b/src/github/pullRequestReviewCommon.ts index 33715b27a5..ab3e790666 100644 --- a/src/github/pullRequestReviewCommon.ts +++ b/src/github/pullRequestReviewCommon.ts @@ -345,7 +345,7 @@ export namespace PullRequestReviewCommon { const deletedBranchTypes: string[] = []; if (selectedActions) { - const isBranchActiveForDeletion = branchInfo && isBranchActive(folderRepositoryManager, item, branchInfo.branch); + const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo?.branch); const promises = selectedActions.map(async action => { switch (action.type) { @@ -359,7 +359,7 @@ export namespace PullRequestReviewCommon { } return; case 'local': - if (isBranchActiveForDeletion) { + if (isBranchActive) { if (folderRepositoryManager.repository.state.workingTreeChanges.length) { const yes = vscode.l10n.t('Yes'); const response = await vscode.window.showWarningMessage( @@ -405,14 +405,6 @@ export namespace PullRequestReviewCommon { } } - /** - * Check if a pull request's branch is currently active (checked out). - */ - function isBranchActive(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel, branchName: string): boolean { - return item.equals(folderRepositoryManager.activePullRequest) || - (folderRepositoryManager.repository.state.HEAD?.name === branchName); - } - /** * Automatically delete branches after merge based on user preferences. * This function does not show any prompts - it uses the default deletion method preferences. @@ -430,41 +422,50 @@ export namespace PullRequestReviewCommon { .getConfiguration(PR_SETTINGS_NAMESPACE) .get(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`, true); - const deletionTasks: Promise[] = []; + const promises: Promise[] = []; // Delete remote head branch if it's not the default branch if (item.isResolved()) { const isDefaultBranch = defaultBranch === item.head.ref; if (!isDefaultBranch && !item.isRemoteHeadDeleted) { - deletionTasks.push( - folderRepositoryManager.deleteBranch(item) - .then(() => folderRepositoryManager.repository.fetch({ prune: true })) - .catch(e => Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) + promises.push( + folderRepositoryManager.deleteBranch(item).then(() => { + return folderRepositoryManager.repository.fetch({ prune: true }); + }).catch(e => { + Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + }) ); } } // Delete local branch if preference is set if (branchInfo && deleteLocalBranch) { - deletionTasks.push( + const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || + (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo.branch); + + promises.push( (async () => { - if (isBranchActive(folderRepositoryManager, item, branchInfo.branch)) { + if (isBranchActive) { + // Checkout default branch before deleting the active branch await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); } await folderRepositoryManager.repository.deleteBranch(branchInfo.branch, true); - })().catch(e => Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) + })().catch(e => { + Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + }) ); } // Delete remote if it's no longer used and preference is set if (branchInfo && branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse && deleteRemote) { - deletionTasks.push( - folderRepositoryManager.repository.removeRemote(branchInfo.remote) - .catch(e => Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon')) + promises.push( + folderRepositoryManager.repository.removeRemote(branchInfo.remote).catch(e => { + Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); + }) ); } // Execute all deletions in parallel - await Promise.all(deletionTasks); + await Promise.all(promises); } } From 8e577347c722e0c61277385e4e730c29595e44fd Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:49:38 +0100 Subject: [PATCH 9/9] Code reuse --- package.nls.json | 2 +- src/github/pullRequestGitHelper.ts | 14 +-- src/github/pullRequestReviewCommon.ts | 132 ++++++++++++-------------- 3 files changed, 68 insertions(+), 80 deletions(-) diff --git a/package.nls.json b/package.nls.json index e55e888940..a352820de6 100644 --- a/package.nls.json +++ b/package.nls.json @@ -39,7 +39,7 @@ "githubPullRequests.hideViewedFiles.description": "Hide files that have been marked as viewed in the pull request changes tree.", "githubPullRequests.defaultDeletionMethod.selectLocalBranch.description": "When true, the option to delete the local branch will be selected by default when deleting a branch from a pull request.", "githubPullRequests.defaultDeletionMethod.selectRemote.description": "When true, the option to delete the remote will be selected by default when deleting a branch from a pull request.", - "githubPullRequests.deleteBranchAfterMerge.description": "Automatically delete the branch after merging a pull request.", + "githubPullRequests.deleteBranchAfterMerge.description": "Automatically delete the branch after merging a pull request. This setting only applies when the pull request is merged through this extension.", "githubPullRequests.terminalLinksHandler.description": "Default handler for terminal links.", "githubPullRequests.terminalLinksHandler.github": "Create the pull request on GitHub", "githubPullRequests.terminalLinksHandler.vscode": "Create the pull request in VS Code", diff --git a/src/github/pullRequestGitHelper.ts b/src/github/pullRequestGitHelper.ts index 599a3eda6d..74239cf974 100644 --- a/src/github/pullRequestGitHelper.ts +++ b/src/github/pullRequestGitHelper.ts @@ -33,6 +33,13 @@ export interface BaseBranchMetadata { branch: string; } +export type BranchInfo = { + branch: string; + remote?: string; + createdForPullRequest?: boolean; + remoteInUse?: boolean; +}; + export class PullRequestGitHelper { static ID = 'PullRequestGitHelper'; static async checkoutFromFork( @@ -202,12 +209,7 @@ export class PullRequestGitHelper { static async getBranchNRemoteForPullRequest( repository: Repository, pullRequest: PullRequestModel, - ): Promise<{ - branch: string; - remote?: string; - createdForPullRequest?: boolean; - remoteInUse?: boolean; - } | null> { + ): Promise { let branchName: string | null = null; try { const key = PullRequestGitHelper.buildPullRequestMetadata(pullRequest); diff --git a/src/github/pullRequestReviewCommon.ts b/src/github/pullRequestReviewCommon.ts index ab3e790666..4ccab16546 100644 --- a/src/github/pullRequestReviewCommon.ts +++ b/src/github/pullRequestReviewCommon.ts @@ -7,9 +7,9 @@ import * as vscode from 'vscode'; import { FolderRepositoryManager } from './folderRepositoryManager'; import { IAccount, isITeam, ITeam, MergeMethod, PullRequestMergeability, reviewerId, ReviewState } from './interface'; +import { BranchInfo } from './pullRequestGitHelper'; import { PullRequestModel } from './pullRequestModel'; import { PullRequest, ReadyForReviewReply, ReviewType, SubmitReviewReply } from './views'; -import Logger from '../common/logger'; import { DEFAULT_DELETION_METHOD, PR_SETTINGS_NAMESPACE, SELECT_LOCAL_BRANCH, SELECT_REMOTE } from '../common/settingKeys'; import { ReviewEvent, TimelineEvent } from '../common/timelineEvent'; import { Schemes } from '../common/uri'; @@ -275,9 +275,13 @@ export namespace PullRequestReviewCommon { } } + interface SelectedAction { + type: 'remoteHead' | 'local' | 'remote' | 'suspend' + }; + export async function deleteBranch(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel): Promise<{ isReply: boolean, message: any }> { const branchInfo = await folderRepositoryManager.getBranchNameForPullRequest(item); - const actions: (vscode.QuickPickItem & { type: 'remoteHead' | 'local' | 'remote' | 'suspend' })[] = []; + const actions: (vscode.QuickPickItem & SelectedAction)[] = []; const defaultBranch = await folderRepositoryManager.getPullRequestRepositoryDefaultBranch(item); if (item.isResolved()) { @@ -342,51 +346,9 @@ export namespace PullRequestReviewCommon { ignoreFocusOut: true, }); - const deletedBranchTypes: string[] = []; if (selectedActions) { - const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo?.branch); - - const promises = selectedActions.map(async action => { - switch (action.type) { - case 'remoteHead': - await folderRepositoryManager.deleteBranch(item); - deletedBranchTypes.push(action.type); - await folderRepositoryManager.repository.fetch({ prune: true }); - // If we're in a remote repository, then we should checkout the default branch. - if (folderRepositoryManager.repository.rootUri.scheme === Schemes.VscodeVfs) { - await folderRepositoryManager.repository.checkout(defaultBranch); - } - return; - case 'local': - if (isBranchActive) { - if (folderRepositoryManager.repository.state.workingTreeChanges.length) { - const yes = vscode.l10n.t('Yes'); - const response = await vscode.window.showWarningMessage( - vscode.l10n.t('Your local changes will be lost, do you want to continue?'), - { modal: true }, - yes, - ); - if (response === yes) { - await vscode.commands.executeCommand('git.cleanAll'); - } else { - return; - } - } - await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); - } - await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true); - return deletedBranchTypes.push(action.type); - case 'remote': - deletedBranchTypes.push(action.type); - return folderRepositoryManager.repository.removeRemote(branchInfo!.remote!); - case 'suspend': - deletedBranchTypes.push(action.type); - return vscode.commands.executeCommand('github.codespaces.disconnectSuspend'); - } - }); - - await Promise.all(promises); + const deletedBranchTypes: string[] = await performBranchDeletion(folderRepositoryManager, item, defaultBranch, branchInfo!, selectedActions); return { isReply: false, @@ -405,6 +367,53 @@ export namespace 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[] = []; + + const promises = selectedActions.map(async action => { + switch (action.type) { + case 'remoteHead': + await folderRepositoryManager.deleteBranch(item); + deletedBranchTypes.push(action.type); + await folderRepositoryManager.repository.fetch({ prune: true }); + // If we're in a remote repository, then we should checkout the default branch. + if (folderRepositoryManager.repository.rootUri.scheme === Schemes.VscodeVfs) { + await folderRepositoryManager.repository.checkout(defaultBranch); + } + return; + case 'local': + if (isBranchActive) { + if (folderRepositoryManager.repository.state.workingTreeChanges.length) { + const yes = vscode.l10n.t('Yes'); + const response = await vscode.window.showWarningMessage( + vscode.l10n.t('Your local changes will be lost, do you want to continue?'), + { modal: true }, + yes, + ); + if (response === yes) { + await vscode.commands.executeCommand('git.cleanAll'); + } else { + return; + } + } + await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); + } + await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true); + return deletedBranchTypes.push(action.type); + case 'remote': + deletedBranchTypes.push(action.type); + return folderRepositoryManager.repository.removeRemote(branchInfo!.remote!); + case 'suspend': + deletedBranchTypes.push(action.type); + return vscode.commands.executeCommand('github.codespaces.disconnectSuspend'); + } + }); + + await Promise.all(promises); + return deletedBranchTypes; + } + /** * Automatically delete branches after merge based on user preferences. * This function does not show any prompts - it uses the default deletion method preferences. @@ -422,50 +431,27 @@ export namespace PullRequestReviewCommon { .getConfiguration(PR_SETTINGS_NAMESPACE) .get(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`, true); - const promises: Promise[] = []; + const selectedActions: SelectedAction[] = []; // Delete remote head branch if it's not the default branch if (item.isResolved()) { const isDefaultBranch = defaultBranch === item.head.ref; if (!isDefaultBranch && !item.isRemoteHeadDeleted) { - promises.push( - folderRepositoryManager.deleteBranch(item).then(() => { - return folderRepositoryManager.repository.fetch({ prune: true }); - }).catch(e => { - Logger.warn(`Failed to delete remote branch for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); - }) - ); + selectedActions.push({ type: 'remoteHead' }); } } // Delete local branch if preference is set if (branchInfo && deleteLocalBranch) { - const isBranchActive = item.equals(folderRepositoryManager.activePullRequest) || - (folderRepositoryManager.repository.state.HEAD?.name && folderRepositoryManager.repository.state.HEAD.name === branchInfo.branch); - - promises.push( - (async () => { - if (isBranchActive) { - // Checkout default branch before deleting the active branch - await folderRepositoryManager.checkoutDefaultBranch(defaultBranch); - } - await folderRepositoryManager.repository.deleteBranch(branchInfo.branch, true); - })().catch(e => { - Logger.warn(`Failed to delete local branch ${branchInfo.branch} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); - }) - ); + selectedActions.push({ type: 'local' }); } // Delete remote if it's no longer used and preference is set if (branchInfo && branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse && deleteRemote) { - promises.push( - folderRepositoryManager.repository.removeRemote(branchInfo.remote).catch(e => { - Logger.warn(`Failed to delete remote ${branchInfo.remote} for PR #${item.number}: ${e}`, 'PullRequestReviewCommon'); - }) - ); + selectedActions.push({ type: 'remote' }); } // Execute all deletions in parallel - await Promise.all(promises); + await performBranchDeletion(folderRepositoryManager, item, defaultBranch, branchInfo!, selectedActions); } }