Skip to content

Commit d092e69

Browse files
committed
Add detection of root workspace directory using .git folder
1 parent f32a240 commit d092e69

File tree

2 files changed

+82
-5
lines changed

2 files changed

+82
-5
lines changed

extensions/ql-vscode/src/data-extensions-editor/extensions-workspace-folder.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Uri, window, workspace, WorkspaceFolder } from "vscode";
1+
import { FileType, Uri, window, workspace, WorkspaceFolder } from "vscode";
22
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
33
import { extLogger } from "../common";
44
import { tmpdir } from "../pure/files";
@@ -20,7 +20,7 @@ function getAncestors(uri: Uri): Uri[] {
2020
return ancestors;
2121
}
2222

23-
function getRootWorkspaceDirectory(): Uri | undefined {
23+
async function getRootWorkspaceDirectory(): Promise<Uri | undefined> {
2424
// If there is a valid workspace file, just use its directory as the directory for the extensions
2525
const workspaceFile = workspace.workspaceFile;
2626
if (workspaceFile?.scheme === "file") {
@@ -56,7 +56,7 @@ function getRootWorkspaceDirectory(): Uri | undefined {
5656
}, getAncestors(workspaceFolders[0].uri));
5757

5858
if (commonRoot.length === 0) {
59-
return undefined;
59+
return await findGitFolder(workspaceFolders);
6060
}
6161

6262
// The path closest to the workspace folders is the last element of the common root
@@ -65,12 +65,65 @@ function getRootWorkspaceDirectory(): Uri | undefined {
6565
// If we are at the root of the filesystem, we can't go up any further and there's something
6666
// wrong, so just return undefined
6767
if (commonRootUri.fsPath === Uri.joinPath(commonRootUri, "..").fsPath) {
68-
return undefined;
68+
return await findGitFolder(workspaceFolders);
6969
}
7070

7171
return commonRootUri;
7272
}
7373

74+
async function findGitFolder(
75+
workspaceFolders: WorkspaceFolder[],
76+
): Promise<Uri | undefined> {
77+
// Go through all workspace folders one-by-one and try to find the closest .git folder for each one
78+
const folders = await Promise.all(
79+
workspaceFolders.map(async (folder) => {
80+
const ancestors = getAncestors(folder.uri);
81+
82+
// Reverse the ancestors so we're going from closest to furthest
83+
ancestors.reverse();
84+
85+
const gitFoldersExists = await Promise.all(
86+
ancestors.map(async (uri) => {
87+
const gitFolder = Uri.joinPath(uri, ".git");
88+
try {
89+
const stat = await workspace.fs.stat(gitFolder);
90+
// Check whether it's a directory
91+
return (stat.type & FileType.Directory) !== 0;
92+
} catch (e) {
93+
return false;
94+
}
95+
}),
96+
);
97+
98+
// Find the first ancestor that has a .git folder
99+
const ancestorIndex = gitFoldersExists.findIndex((exists) => exists);
100+
101+
if (ancestorIndex === -1) {
102+
return undefined;
103+
}
104+
105+
return [ancestorIndex, ancestors[ancestorIndex]];
106+
}),
107+
);
108+
109+
const validFolders = folders.filter(
110+
(folder): folder is [number, Uri] => folder !== undefined,
111+
);
112+
if (validFolders.length === 0) {
113+
return undefined;
114+
}
115+
116+
// Find the .git folder which is closest to a workspace folder
117+
const closestFolder = validFolders.reduce((closestFolder, folder) => {
118+
if (folder[0] < closestFolder[0]) {
119+
return folder;
120+
}
121+
return closestFolder;
122+
}, validFolders[0]);
123+
124+
return closestFolder?.[1];
125+
}
126+
74127
export async function autoPickExtensionsDirectory(): Promise<Uri | undefined> {
75128
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
76129

@@ -94,7 +147,7 @@ export async function autoPickExtensionsDirectory(): Promise<Uri | undefined> {
94147
}
95148

96149
// Get the root workspace directory, i.e. the common root directory of all workspace folders
97-
const rootDirectory = getRootWorkspaceDirectory();
150+
const rootDirectory = await getRootWorkspaceDirectory();
98151
if (!rootDirectory) {
99152
void extLogger.log("Unable to determine root workspace directory");
100153

extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extensions-workspace-folder.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { dir, DirectoryResult } from "tmp-promise";
33
import { join } from "path";
44
import { autoPickExtensionsDirectory } from "../../../../src/data-extensions-editor/extensions-workspace-folder";
55
import * as files from "../../../../src/pure/files";
6+
import { mkdirp } from "fs-extra";
67

78
describe("autoPickExtensionsDirectory", () => {
89
let tmpDir: DirectoryResult;
@@ -187,4 +188,27 @@ describe("autoPickExtensionsDirectory", () => {
187188
expect(await autoPickExtensionsDirectory()).toEqual(undefined);
188189
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
189190
});
191+
192+
it("when a workspace file does not exist and there is a .git folder", async () => {
193+
await mkdirp(join(rootDirectory.fsPath, ".git"));
194+
195+
workspaceFoldersSpy.mockReturnValue([
196+
{
197+
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
198+
name: "codeql-custom-queries-java",
199+
index: 0,
200+
},
201+
{
202+
uri: Uri.file("/a/b/c"),
203+
name: "codeql-custom-queries-python",
204+
index: 1,
205+
},
206+
]);
207+
208+
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
209+
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
210+
name: "CodeQL Extension Packs",
211+
uri: extensionsDirectory,
212+
});
213+
});
190214
});

0 commit comments

Comments
 (0)