diff --git a/extensions/ql-vscode/src/data-extensions-editor/extensions-workspace-folder.ts b/extensions/ql-vscode/src/data-extensions-editor/extensions-workspace-folder.ts index 6960ab163..422329203 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extensions-workspace-folder.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extensions-workspace-folder.ts @@ -1,4 +1,4 @@ -import { Uri, window, workspace, WorkspaceFolder } from "vscode"; +import { FileType, Uri, window, workspace, WorkspaceFolder } from "vscode"; import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders"; import { extLogger } from "../common"; import { tmpdir } from "../pure/files"; @@ -20,7 +20,7 @@ function getAncestors(uri: Uri): Uri[] { return ancestors; } -function getRootWorkspaceDirectory(): Uri | undefined { +async function getRootWorkspaceDirectory(): Promise { // If there is a valid workspace file, just use its directory as the directory for the extensions const workspaceFile = workspace.workspaceFile; if (workspaceFile?.scheme === "file") { @@ -56,7 +56,7 @@ function getRootWorkspaceDirectory(): Uri | undefined { }, getAncestors(workspaceFolders[0].uri)); if (commonRoot.length === 0) { - return undefined; + return await findGitFolder(workspaceFolders); } // The path closest to the workspace folders is the last element of the common root @@ -65,12 +65,65 @@ function getRootWorkspaceDirectory(): Uri | undefined { // If we are at the root of the filesystem, we can't go up any further and there's something // wrong, so just return undefined if (commonRootUri.fsPath === Uri.joinPath(commonRootUri, "..").fsPath) { - return undefined; + return await findGitFolder(workspaceFolders); } return commonRootUri; } +async function findGitFolder( + workspaceFolders: WorkspaceFolder[], +): Promise { + // Go through all workspace folders one-by-one and try to find the closest .git folder for each one + const folders = await Promise.all( + workspaceFolders.map(async (folder) => { + const ancestors = getAncestors(folder.uri); + + // Reverse the ancestors so we're going from closest to furthest + ancestors.reverse(); + + const gitFoldersExists = await Promise.all( + ancestors.map(async (uri) => { + const gitFolder = Uri.joinPath(uri, ".git"); + try { + const stat = await workspace.fs.stat(gitFolder); + // Check whether it's a directory + return (stat.type & FileType.Directory) !== 0; + } catch (e) { + return false; + } + }), + ); + + // Find the first ancestor that has a .git folder + const ancestorIndex = gitFoldersExists.findIndex((exists) => exists); + + if (ancestorIndex === -1) { + return undefined; + } + + return [ancestorIndex, ancestors[ancestorIndex]]; + }), + ); + + const validFolders = folders.filter( + (folder): folder is [number, Uri] => folder !== undefined, + ); + if (validFolders.length === 0) { + return undefined; + } + + // Find the .git folder which is closest to a workspace folder + const closestFolder = validFolders.reduce((closestFolder, folder) => { + if (folder[0] < closestFolder[0]) { + return folder; + } + return closestFolder; + }, validFolders[0]); + + return closestFolder?.[1]; +} + export async function autoPickExtensionsDirectory(): Promise { const workspaceFolders = getOnDiskWorkspaceFoldersObjects(); @@ -94,7 +147,7 @@ export async function autoPickExtensionsDirectory(): Promise { } // Get the root workspace directory, i.e. the common root directory of all workspace folders - const rootDirectory = getRootWorkspaceDirectory(); + const rootDirectory = await getRootWorkspaceDirectory(); if (!rootDirectory) { void extLogger.log("Unable to determine root workspace directory"); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extensions-workspace-folder.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extensions-workspace-folder.test.ts index b6e7cae59..bffa1d659 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extensions-workspace-folder.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extensions-workspace-folder.test.ts @@ -3,6 +3,7 @@ import { dir, DirectoryResult } from "tmp-promise"; import { join } from "path"; import { autoPickExtensionsDirectory } from "../../../../src/data-extensions-editor/extensions-workspace-folder"; import * as files from "../../../../src/pure/files"; +import { mkdirp } from "fs-extra"; describe("autoPickExtensionsDirectory", () => { let tmpDir: DirectoryResult; @@ -187,4 +188,27 @@ describe("autoPickExtensionsDirectory", () => { expect(await autoPickExtensionsDirectory()).toEqual(undefined); expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled(); }); + + it("when a workspace file does not exist and there is a .git folder", async () => { + await mkdirp(join(rootDirectory.fsPath, ".git")); + + workspaceFoldersSpy.mockReturnValue([ + { + uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"), + name: "codeql-custom-queries-java", + index: 0, + }, + { + uri: Uri.file("/a/b/c"), + name: "codeql-custom-queries-python", + index: 1, + }, + ]); + + expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory); + expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, { + name: "CodeQL Extension Packs", + uri: extensionsDirectory, + }); + }); });