Skip to content

Commit de9675e

Browse files
authored
Offer to delete worktrees from clean up command (#8653)
* Offer to delete worktrees from clean up command Fixes #8652 * CCR feedback
1 parent dea21f8 commit de9675e

File tree

3 files changed

+100
-23
lines changed

3 files changed

+100
-23
lines changed

src/@types/git.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ export interface Remote {
7676
readonly isReadOnly: boolean;
7777
}
7878

79+
export interface Worktree {
80+
readonly name: string;
81+
readonly path: string;
82+
readonly ref: string;
83+
readonly main: boolean;
84+
readonly detached: boolean;
85+
}
86+
7987
export const enum Status {
8088
INDEX_MODIFIED,
8189
INDEX_ADDED,
@@ -113,11 +121,14 @@ export interface Change {
113121
readonly status: Status;
114122
}
115123

124+
export type RepositoryKind = 'repository' | 'submodule' | 'worktree';
125+
116126
export interface RepositoryState {
117127
readonly HEAD: Branch | undefined;
118128
readonly refs: Ref[];
119129
readonly remotes: Remote[];
120130
readonly submodules: Submodule[];
131+
readonly worktrees: Worktree[];
121132
readonly rebaseCommit: Commit | undefined;
122133

123134
readonly mergeChanges: Change[];
@@ -287,6 +298,15 @@ export interface Repository {
287298
applyStash(index?: number): Promise<void>;
288299
popStash(index?: number): Promise<void>;
289300
dropStash(index?: number): Promise<void>;
301+
302+
createWorktree?(options?: { path?: string; commitish?: string; branch?: string }): Promise<string>;
303+
deleteWorktree?(path: string, options?: { force?: boolean }): Promise<void>;
304+
305+
migrateChanges?(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise<void>;
306+
307+
generateRandomBranchName?(): Promise<string | undefined>;
308+
309+
isBranchProtected?(branch?: Branch): boolean;
290310
}
291311

292312
export interface RemoteSource {

src/gitProviders/vslsguest.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as vscode from 'vscode';
77

88
import { LiveShare, SharedServiceProxy } from 'vsls/vscode.js';
9-
import { Branch, Change, Commit, Ref, Remote, RepositoryState, Submodule } from '../@types/git';
9+
import { Branch, Change, Commit, Ref, Remote, RepositoryState, Submodule, Worktree } from '../@types/git';
1010
import { IGit, Repository } from '../api/api';
1111
import { Disposable } from '../common/lifecycle';
1212
import {
@@ -138,6 +138,7 @@ class LiveShareRepositoryState implements RepositoryState {
138138
HEAD: Branch | undefined;
139139
remotes: Remote[];
140140
submodules: Submodule[] = [];
141+
worktrees: Worktree[] = [];
141142
rebaseCommit: Commit | undefined;
142143
mergeChanges: Change[] = [];
143144
indexChanges: Change[] = [];

src/github/folderRepositoryManager.ts

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ import {
4646
CHAT_SETTINGS_NAMESPACE,
4747
CHECKOUT_DEFAULT_BRANCH,
4848
CHECKOUT_PULL_REQUEST_BASE_BRANCH,
49+
DEFAULT_DELETION_METHOD,
4950
DISABLE_AI_FEATURES,
5051
GIT,
5152
POST_DONE,
5253
PR_SETTINGS_NAMESPACE,
5354
PULL_BEFORE_CHECKOUT,
5455
PULL_BRANCH,
5556
REMOTES,
57+
SELECT_WORKTREE,
5658
UPSTREAM_REMOTE,
5759
} from '../common/settingKeys';
5860
import { ITelemetry } from '../common/telemetry';
@@ -2148,35 +2150,89 @@ export class FolderRepositoryManager extends Disposable {
21482150
quickPick.items = [{ label: vscode.l10n.t('No local branches to delete'), picked: false }];
21492151
}
21502152

2151-
let firstStep = true;
2153+
let step: 'branches' | 'worktrees' | 'remotes' = 'branches';
2154+
let nonExistantBranches = new Set<string>();
2155+
let branchPicks: readonly vscode.QuickPickItem[] = [];
2156+
2157+
const showWorktreeStep = (worktreeItems: (vscode.QuickPickItem & { worktreePath: string })[]) => {
2158+
quickPick.canSelectMany = true;
2159+
quickPick.value = '';
2160+
quickPick.placeholder = vscode.l10n.t('Do you want to delete the associated worktrees?');
2161+
quickPick.items = worktreeItems;
2162+
quickPick.selectedItems = worktreeItems.filter(item => item.picked);
2163+
step = 'worktrees';
2164+
};
2165+
2166+
const deleteBranchesAndShowRemoteStep = async () => {
2167+
if (branchPicks.length) {
2168+
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Cleaning up') }, async (progress) => {
2169+
try {
2170+
await this.deleteBranches(branchPicks, nonExistantBranches, progress, branchPicks.length, 0, []);
2171+
} catch (e) {
2172+
quickPick.hide();
2173+
vscode.window.showErrorMessage(vscode.l10n.t('Deleting branches failed: {0} {1}', e.message, e.stderr));
2174+
}
2175+
});
2176+
}
2177+
2178+
const remoteItems = await this.getRemoteDeletionItems(nonExistantBranches);
2179+
if (remoteItems && remoteItems.length) {
2180+
quickPick.canSelectMany = true;
2181+
quickPick.placeholder = vscode.l10n.t('Choose remotes you want to delete permanently');
2182+
quickPick.items = remoteItems;
2183+
quickPick.selectedItems = remoteItems.filter(item => item.picked);
2184+
step = 'remotes';
2185+
} else {
2186+
quickPick.hide();
2187+
}
2188+
};
2189+
21522190
quickPick.onDidAccept(async () => {
21532191
quickPick.busy = true;
21542192

2155-
if (firstStep) {
2156-
const picks = quickPick.selectedItems;
2157-
const nonExistantBranches = new Set<string>();
2158-
if (picks.length) {
2159-
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Cleaning up') }, async (progress) => {
2160-
try {
2161-
await this.deleteBranches(picks, nonExistantBranches, progress, picks.length, 0, []);
2162-
} catch (e) {
2163-
quickPick.hide();
2164-
vscode.window.showErrorMessage(vscode.l10n.t('Deleting branches failed: {0} {1}', e.message, e.stderr));
2165-
}
2166-
});
2193+
if (step === 'branches') {
2194+
branchPicks = quickPick.selectedItems;
2195+
nonExistantBranches = new Set<string>();
2196+
2197+
// Find which selected branches have worktrees (must be deleted before the branch)
2198+
const preferredWorktreeDeletion = !!vscode.workspace
2199+
.getConfiguration(PR_SETTINGS_NAMESPACE)
2200+
.get<boolean>(`${DEFAULT_DELETION_METHOD}.${SELECT_WORKTREE}`);
2201+
const worktreeItems: (vscode.QuickPickItem & { worktreePath: string })[] = [];
2202+
for (const pick of branchPicks) {
2203+
const worktreeUri = this.getWorktreeForBranch(pick.label);
2204+
if (worktreeUri && !vscode.workspace.workspaceFolders?.some(folder =>
2205+
folder.uri.fsPath === worktreeUri.fsPath ||
2206+
(process.platform === 'win32' && folder.uri.fsPath.toLowerCase() === worktreeUri.fsPath.toLowerCase())
2207+
)) {
2208+
worktreeItems.push({
2209+
label: pick.label,
2210+
description: worktreeUri.fsPath,
2211+
picked: preferredWorktreeDeletion,
2212+
worktreePath: worktreeUri.fsPath,
2213+
});
2214+
}
21672215
}
21682216

2169-
firstStep = false;
2170-
const remoteItems = await this.getRemoteDeletionItems(nonExistantBranches);
2171-
2172-
if (remoteItems && remoteItems.length) {
2173-
quickPick.canSelectMany = true;
2174-
quickPick.placeholder = vscode.l10n.t('Choose remotes you want to delete permanently');
2175-
quickPick.items = remoteItems;
2176-
quickPick.selectedItems = remoteItems.filter(item => item.picked);
2217+
if (worktreeItems.length && this.repository.deleteWorktree) {
2218+
showWorktreeStep(worktreeItems);
21772219
} else {
2178-
quickPick.hide();
2220+
await deleteBranchesAndShowRemoteStep();
2221+
}
2222+
} else if (step === 'worktrees') {
2223+
const picks = quickPick.selectedItems as readonly (vscode.QuickPickItem & { worktreePath: string })[];
2224+
if (picks.length) {
2225+
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Deleting {0} worktrees...', picks.length) }, async () => {
2226+
for (const pick of picks) {
2227+
try {
2228+
await this.removeWorktree(pick.worktreePath);
2229+
} catch (e) {
2230+
Logger.error(`Failed to delete worktree ${pick.worktreePath}: ${e}`, this.id);
2231+
}
2232+
}
2233+
});
21792234
}
2235+
await deleteBranchesAndShowRemoteStep();
21802236
} else {
21812237
// batch deleting the remotes to avoid consuming all available resources
21822238
const picks = quickPick.selectedItems;

0 commit comments

Comments
 (0)