Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
"githubPullRequests.fileListLayout.description": "The layout to use when displaying changed files list.",
"githubPullRequests.hideViewedFiles.description": "Hide files that have been marked as viewed in the pull request changes tree.",
"githubPullRequests.fileAutoReveal.description": "Automatically reveal open files 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.defaultDeletionMethod.selectWorktree.description": "When true, the option to remove the associated worktree will be selected by default when deleting a branch from a pull request.",
"githubPullRequests.defaultDeletionMethod.selectLocalBranch.description": "When true, delete the local branch during automatic branch cleanup after merging a pull request or adding it to a merge queue.",
"githubPullRequests.defaultDeletionMethod.selectRemote.description": "When true, delete the unused remote during automatic branch cleanup after merging a pull request.",
"githubPullRequests.defaultDeletionMethod.selectWorktree.description": "When true, remove the associated worktree during automatic branch cleanup after merging a pull request, and select worktree removal by default during bulk branch cleanup.",
"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. When using merge queues, this will only delete the local branch.",
"githubPullRequests.enableAttestationCommits.description": "Enables adding an attestation commit (an empty, signed commit) to the head of a pull request branch as a way to attest to a pull request even when its individual commits are unsigned. Requires commit signing to be configured for git. Set to `true` to enable with the default commit message, set to a string to use that string as the commit message, or set to `false` to disable.",
"githubPullRequests.terminalLinksHandler.description": "Default handler for terminal links.",
Expand Down
6 changes: 1 addition & 5 deletions src/github/activityBarViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,7 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W

private async deleteBranch(message: IRequestMessage<any>) {
const result = await PullRequestReviewCommon.deleteBranch(this._folderRepositoryManager, this._item);
if (result.isReply) {
this._replyMessage(message, result.message);
} else {
this._postMessage(result.message);
}
await this._replyMessage(message, result.message);
}

private async setReadyForReview(message: IRequestMessage<Record<string, unknown>>): Promise<void> {
Expand Down
6 changes: 2 additions & 4 deletions src/github/pullRequestOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,12 +1033,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode

private async deleteBranch(message: IRequestMessage<any>) {
const result = await PullRequestReviewCommon.deleteBranch(this._folderRepositoryManager, this._item);
if (result.isReply) {
this._replyMessage(message, result.message);
} else {
if (!result.isReply) {
this.refreshPanel();
this._postMessage(result.message);
}
await this._replyMessage(message, result.message);
}

private async setReadyForReview(message: IRequestMessage<{}>): Promise<void> {
Expand Down
83 changes: 53 additions & 30 deletions src/github/pullRequestReviewCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,15 @@ export namespace PullRequestReviewCommon {
});
}

function isBranchNotFoundError(error: unknown): boolean {
const stderr = error && typeof error === 'object' ? Reflect.get(error, 'stderr') : undefined;
return typeof stderr === 'string' && stderr.includes('not found');
}

export async function deleteBranch(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel): Promise<{ isReply: boolean, message: any }> {
const branchInfo = await folderRepositoryManager.getBranchNameForPullRequest(item);
const actions: (vscode.QuickPickItem & SelectedAction)[] = [];
const actions: (vscode.MessageItem & SelectedAction)[] = [];
const cleanupDetails: string[] = [];
const defaultBranch = await folderRepositoryManager.getPullRequestRepositoryDefaultBranch(item);

if (item.isResolved()) {
Expand All @@ -317,56 +323,52 @@ export namespace PullRequestReviewCommon {

const isDefaultBranch = defaultBranch === item.head.ref;
if (!isDefaultBranch && !item.isRemoteHeadDeleted) {
const remoteBranch = headRepo ? `${headRepo.remote.remoteName}/${branchHeadRef}` : branchHeadRef;
const remoteRepository = item.head.repositoryCloneUrl.toString() ??
`${item.remote.normalizedHost}/${item.head.repositoryCloneUrl.owner}/${item.head.repositoryCloneUrl.repositoryName}`;
actions.push({
label: vscode.l10n.t('Delete remote branch {0}', `${headRepo?.remote.remoteName}/${branchHeadRef}`),
description: `${item.remote.normalizedHost}/${item.head.repositoryCloneUrl.owner}/${item.remote.repositoryName}`,
title: vscode.l10n.t('Delete Remote Branch'),
type: 'remoteHead',
picked: true,
});
cleanupDetails.push(
vscode.l10n.t('Remote branch: {0}', remoteBranch),
vscode.l10n.t('Remote repository: {0}', remoteRepository),
);
}
}

if (branchInfo) {
const preferredLocalBranchDeletionMethod = vscode.workspace
.getConfiguration(PR_SETTINGS_NAMESPACE)
.get<boolean>(`${DEFAULT_DELETION_METHOD}.${SELECT_LOCAL_BRANCH}`);
actions.push({
label: vscode.l10n.t('Delete local branch {0}', branchInfo.branch),
title: vscode.l10n.t('Delete Local Branch'),
type: 'local',
picked: !!preferredLocalBranchDeletionMethod,
});

const preferredRemoteDeletionMethod = vscode.workspace
.getConfiguration(PR_SETTINGS_NAMESPACE)
.get<boolean>(`${DEFAULT_DELETION_METHOD}.${SELECT_REMOTE}`);
cleanupDetails.push(vscode.l10n.t('Local branch: {0}', branchInfo.branch));

if (branchInfo.remote && branchInfo.createdForPullRequest && !branchInfo.remoteInUse) {
actions.push({
label: vscode.l10n.t('Delete remote {0}, which is no longer used by any other branch', branchInfo.remote),
title: vscode.l10n.t('Delete Remote'),
type: 'remote',
picked: !!preferredRemoteDeletionMethod,
});
cleanupDetails.push(vscode.l10n.t('Unused Git remote: {0}', branchInfo.remote));
}

const worktreePath = folderRepositoryManager.getWorktreeForBranch(branchInfo.branch);
if (worktreePath && !isWorktreeInWorkspace(worktreePath)) {
const preferredWorktreeDeletion = vscode.workspace
.getConfiguration(PR_SETTINGS_NAMESPACE)
.get<boolean>(`${DEFAULT_DELETION_METHOD}.${SELECT_WORKTREE}`);
actions.push({
label: vscode.l10n.t('Remove worktree {0}', worktreePath.fsPath),
title: vscode.l10n.t('Remove Worktree'),
type: 'worktree',
worktreePath: worktreePath.fsPath,
picked: !!preferredWorktreeDeletion,
});
cleanupDetails.push(vscode.l10n.t('Worktree: {0}', worktreePath.fsPath));
}
}

if (vscode.env.remoteName === 'codespaces') {
actions.push({
label: vscode.l10n.t('Suspend Codespace'),
title: vscode.l10n.t('Suspend Codespace'),
type: 'suspend'
});
cleanupDetails.push(vscode.l10n.t('Codespace: current Codespace'));
}

if (!actions.length) {
Expand All @@ -381,14 +383,28 @@ export namespace PullRequestReviewCommon {
};
}

const selectedActions = await vscode.window.showQuickPick(actions, {
canPickMany: true,
ignoreFocusOut: true,
});


if (selectedActions) {
const deletedBranchTypes: string[] = await performBranchDeletion(folderRepositoryManager, item, defaultBranch, branchInfo!, selectedActions);
const options: (vscode.MessageItem & { actions: SelectedAction[] })[] = actions.map(action => ({
title: action.title,
actions: [action],
}));
const deletionActions = actions.filter(action => action.type !== 'suspend');
if (deletionActions.length > 1) {
options.unshift({ title: vscode.l10n.t('Delete All'), actions: deletionActions });
}
const selectedOption = await vscode.window.showWarningMessage(
vscode.l10n.t('Choose what to delete for Pull Request #{0}', item.number),
{
modal: true,
detail: vscode.l10n.t(
'Choose an action below to clean up the resources associated with this pull request.\n\n{0}',
cleanupDetails.join('\n'),
)
},
...options,
);

if (selectedOption) {
const deletedBranchTypes: string[] = await performBranchDeletion(folderRepositoryManager, item, defaultBranch, branchInfo!, selectedOption.actions);

return {
isReply: false,
Expand Down Expand Up @@ -462,7 +478,14 @@ export namespace PullRequestReviewCommon {
}
await folderRepositoryManager.checkoutDefaultBranch(defaultBranch, item);
}
await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true);
try {
await folderRepositoryManager.repository.deleteBranch(branchInfo!.branch, true);
} catch (error) {
if (!isBranchNotFoundError(error)) {
throw error;
}
Logger.debug(`Local branch ${branchInfo!.branch} no longer exists.`, 'PullRequestReviewCommon');
}
return deletedBranchTypes.push(action.type);
case 'remote':
deletedBranchTypes.push(action.type);
Expand Down
Loading