diff --git a/QuickShell.Raycast/src/components/discover-git-repos-view.tsx b/QuickShell.Raycast/src/components/discover-git-repos-view.tsx index df48edec..6070c231 100644 --- a/QuickShell.Raycast/src/components/discover-git-repos-view.tsx +++ b/QuickShell.Raycast/src/components/discover-git-repos-view.tsx @@ -101,6 +101,7 @@ export default function DiscoverGitReposView({ onWorkspaceAdded, popOnAdd = true } let cancelled = false; + const controller = new AbortController(); const timer = setTimeout(() => { setTargetedLoadingQuery(query); void (async () => { @@ -108,15 +109,15 @@ export default function DiscoverGitReposView({ onWorkspaceAdded, popOnAdd = true const existing = await storage.getWorkspaces(); const existingDirs = new Set(existing.map((workspace) => workspace.directory.toLowerCase())); const extraRoots = searchRootsFromWorkspaces(existing.map((workspace) => workspace.directory)); - const repos = (await discoverGitReposForQueryAsync(query, extraRoots)).filter( + const repos = (await discoverGitReposForQueryAsync(query, extraRoots, { signal: controller.signal })).filter( (repo) => !existingDirs.has(repo.directory.toLowerCase()), ); if (!cancelled) { setTargetedSearch({ query, repos }); } - } catch { - if (!cancelled) { - setTargetedSearch({ query, repos: [] }); + } catch (error) { + if (!cancelled && !controller.signal.aborted) { + throw error; } } finally { if (!cancelled) { @@ -128,6 +129,7 @@ export default function DiscoverGitReposView({ onWorkspaceAdded, popOnAdd = true return () => { cancelled = true; + controller.abort(); clearTimeout(timer); }; }, [data, searchText]); diff --git a/QuickShell.Raycast/src/components/set-target-branch-form.tsx b/QuickShell.Raycast/src/components/set-target-branch-form.tsx index c1456332..c2ef5829 100644 --- a/QuickShell.Raycast/src/components/set-target-branch-form.tsx +++ b/QuickShell.Raycast/src/components/set-target-branch-form.tsx @@ -21,7 +21,7 @@ export default function SetTargetBranchForm({ directory, workspaceName, blockDir const { pop } = useNavigation(); const storage = getQuickShellStorage(); - const { data: branchChoices, isLoading } = usePromise(async () => { + const { data: branchChoices, isLoading, error, revalidate } = usePromise(async () => { const branches = await listLocalBranches(directory); const worktreeKey = await resolveWorktreeKey(directory); const target = worktreeKey ? await storage.getBranchTarget(worktreeKey) : null; @@ -104,15 +104,17 @@ export default function SetTargetBranchForm({ directory, workspaceName, blockDir isLoading={isLoading} actions={ + {error ? : null} } > + {error ? : null} {branchChoices && branchChoices.branches.length === 0 ? ( ) : ( diff --git a/QuickShell.Raycast/src/lib/discovered-workspace-seed.ts b/QuickShell.Raycast/src/lib/discovered-workspace-seed.ts index 4dcc2e02..1a835443 100644 --- a/QuickShell.Raycast/src/lib/discovered-workspace-seed.ts +++ b/QuickShell.Raycast/src/lib/discovered-workspace-seed.ts @@ -1,4 +1,5 @@ import { deriveAbbreviationFromName, deriveNameFromDirectory } from "./directory-helpers"; +import { tryGetGitRemoteUrl } from "./git-repo-discovery"; import { createStableId } from "./ids"; import type { WorkspaceSetupTask } from "./project-setup-suggestion"; import type { CompanionAppEntry, Workspace } from "./schema"; @@ -21,6 +22,7 @@ type DiscoveredWorkspaceSeed = { export function createWorkspaceFromDiscoveredGitRepo(seed: DiscoveredWorkspaceSeed): Workspace { const directory = seed.directory.trim(); const name = seed.name.trim() || deriveNameFromDirectory(directory); + const repoUrl = seed.remoteUrl ?? tryGetGitRemoteUrl(directory) ?? null; const launches = launchRowsFromSuggestions(seed.tasks).map((row, index) => ({ id: row.id, label: row.label, @@ -45,7 +47,7 @@ export function createWorkspaceFromDiscoveredGitRepo(seed: DiscoveredWorkspaceSe wtProfile: null, command: null, runAsAdmin: false, - repoUrl: seed.remoteUrl ?? null, + repoUrl, devServerUrl: seed.devServerUrl ?? null, // normalizeWorkspace supplies a usable blank launch when discovery finds no tasks. launches, diff --git a/QuickShell.Raycast/src/lib/git-repo-discovery.ts b/QuickShell.Raycast/src/lib/git-repo-discovery.ts index ad05cf13..04f61186 100644 --- a/QuickShell.Raycast/src/lib/git-repo-discovery.ts +++ b/QuickShell.Raycast/src/lib/git-repo-discovery.ts @@ -50,6 +50,7 @@ type DiscoveryOptions = { maxScanned?: number; query?: string; rootDirectories?: string[]; + signal?: AbortSignal; }; export function discoverGitRepos(extraRoots: string[] = []): GitRepoCandidate[] { @@ -88,9 +89,10 @@ export async function discoverGitReposAsync( const maxRepos = options?.maxRepos ?? MAX_REPOS; const maxScanned = options?.maxScanned ?? MAX_SCANNED; const query = normalizeDiscoveryQuery(options?.query ?? ""); + const signal = options?.signal; async function worker(): Promise { - while (results.length < maxRepos && scanned < maxScanned) { + while (!signal?.aborted && results.length < maxRepos && scanned < maxScanned) { const work = queue.shift(); if (!work) { return; @@ -99,6 +101,7 @@ export async function discoverGitReposAsync( continue; } + if (signal?.aborted) return; let isDirectory = false; try { isDirectory = (await fs.stat(work.directory)).isDirectory(); @@ -120,6 +123,7 @@ export async function discoverGitReposAsync( return; } + if (signal?.aborted) return; let entries: Array<{ name: string; isDirectory: () => boolean }> = []; try { entries = await fs.readdir(work.directory, { withFileTypes: true }); @@ -128,6 +132,7 @@ export async function discoverGitReposAsync( } for (const entry of entries) { + if (signal?.aborted) return; if (!entry.isDirectory() || SKIP_DIRS.has(entry.name.toLowerCase())) { continue; } @@ -154,7 +159,7 @@ export async function discoverGitReposAsync( export async function discoverGitReposForQueryAsync( searchText: string, extraRoots: string[] = [], - options?: { rootDirectories?: string[]; concurrency?: number; maxScanned?: number }, + options?: { rootDirectories?: string[]; concurrency?: number; maxScanned?: number; signal?: AbortSignal }, ): Promise { const query = normalizeDiscoveryQuery(searchText); if (!query) { @@ -172,6 +177,7 @@ export async function discoverGitReposForQueryAsync( maxScanned: options?.maxScanned ?? MAX_TARGETED_SCANNED, query, rootDirectories: options?.rootDirectories, + signal: options?.signal, }); } @@ -280,8 +286,12 @@ function repoCandidateFromPathQuery(value: string): GitRepoCandidate | null { if (!statSync(candidate).isDirectory()) { candidate = path.dirname(candidate); } - } catch { - return null; + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT" || code === "ENOTDIR") { + return null; + } + throw error; } while (true) {