Skip to content

Commit 1e17911

Browse files
committed
Offer to delete worktrees from clean up command
Fixes #8652
1 parent d734150 commit 1e17911

File tree

2 files changed

+90
-22
lines changed

2 files changed

+90
-22
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/github/folderRepositoryManager.ts

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,35 +2148,83 @@ export class FolderRepositoryManager extends Disposable {
21482148
quickPick.items = [{ label: vscode.l10n.t('No local branches to delete'), picked: false }];
21492149
}
21502150

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

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-
});
2191+
if (step === 'branches') {
2192+
branchPicks = quickPick.selectedItems;
2193+
nonExistantBranches = new Set<string>();
2194+
2195+
// Find which selected branches have worktrees (must be deleted before the branch)
2196+
const worktreeItems: (vscode.QuickPickItem & { worktreePath: string })[] = [];
2197+
for (const pick of branchPicks) {
2198+
const worktreeUri = this.getWorktreeForBranch(pick.label);
2199+
if (worktreeUri) {
2200+
worktreeItems.push({
2201+
label: pick.label,
2202+
description: worktreeUri.fsPath,
2203+
picked: true,
2204+
worktreePath: worktreeUri.fsPath,
2205+
});
2206+
}
21672207
}
21682208

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);
2209+
if (worktreeItems.length && this.repository.deleteWorktree) {
2210+
showWorktreeStep(worktreeItems);
21772211
} else {
2178-
quickPick.hide();
2212+
await deleteBranchesAndShowRemoteStep();
2213+
}
2214+
} else if (step === 'worktrees') {
2215+
const picks = quickPick.selectedItems as readonly (vscode.QuickPickItem & { worktreePath: string })[];
2216+
if (picks.length) {
2217+
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Deleting {0} worktrees...', picks.length) }, async () => {
2218+
for (const pick of picks) {
2219+
try {
2220+
await this.removeWorktree(pick.worktreePath);
2221+
} catch (e) {
2222+
Logger.error(`Failed to delete worktree ${pick.worktreePath}: ${e}`, this.id);
2223+
}
2224+
}
2225+
});
21792226
}
2227+
await deleteBranchesAndShowRemoteStep();
21802228
} else {
21812229
// batch deleting the remotes to avoid consuming all available resources
21822230
const picks = quickPick.selectedItems;

0 commit comments

Comments
 (0)