From 0393b40c11bb91afaade449d39243128bb7a6252 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:21:40 +0100 Subject: [PATCH 01/10] Refactor: Add `getCheckoutPath` --- lib/entry-points.js | 9 ++++++--- src/git-utils.ts | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index e5a84f4d2b..708be27a2f 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -147147,10 +147147,13 @@ function getRefFromEnv() { } return refEnv; } -async function getRef() { +function getCheckoutPath(env) { + return getOptionalInput("checkout_path") || getOptionalInput("source-root") || env.getRequired("GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */); +} +async function getRef(env = getEnv()) { const refInput = getOptionalInput("ref"); const shaInput = getOptionalInput("sha"); - const checkoutPath = getOptionalInput("checkout_path") || getOptionalInput("source-root") || getRequiredEnvParam("GITHUB_WORKSPACE"); + const checkoutPath = getCheckoutPath(env); const hasRefInput = !!refInput; const hasShaInput = !!shaInput; if ((hasRefInput || hasShaInput) && !(hasRefInput && hasShaInput)) { @@ -147159,7 +147162,7 @@ async function getRef() { ); } const ref = refInput || getRefFromEnv(); - const sha = shaInput || getRequiredEnvParam("GITHUB_SHA"); + const sha = shaInput || env.getRequired("GITHUB_SHA" /* GITHUB_SHA */); if (refInput) { return refInput; } diff --git a/src/git-utils.ts b/src/git-utils.ts index 0f5bf52a47..1bca07eb7d 100644 --- a/src/git-utils.ts +++ b/src/git-utils.ts @@ -13,6 +13,7 @@ import { getWorkflowEvent, getWorkflowEventName, } from "./actions-util"; +import { ActionsEnvVars, getEnv, type ReadOnlyEnv } from "./environment"; import { ConfigurationError, getRequiredEnvParam } from "./util"; /** @@ -334,18 +335,27 @@ function getRefFromEnv(): string { return refEnv; } +/** + * Gets the path at which the repository is checked out at. In order of preference, this is determined by: + * the `checkout_path` input, the `source-root` input, the `GITHUB_WORKSPACE` environment variable. + */ +export function getCheckoutPath(env: ReadOnlyEnv) { + return ( + getOptionalInput("checkout_path") || + getOptionalInput("source-root") || + env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE) + ); +} + /** * Get the ref currently being analyzed. */ -export async function getRef(): Promise { +export async function getRef(env: ReadOnlyEnv = getEnv()): Promise { // Will be in the form "refs/heads/master" on a push event // or in the form "refs/pull/N/merge" on a pull_request event const refInput = getOptionalInput("ref"); const shaInput = getOptionalInput("sha"); - const checkoutPath = - getOptionalInput("checkout_path") || - getOptionalInput("source-root") || - getRequiredEnvParam("GITHUB_WORKSPACE"); + const checkoutPath = getCheckoutPath(env); const hasRefInput = !!refInput; const hasShaInput = !!shaInput; @@ -357,7 +367,7 @@ export async function getRef(): Promise { } const ref = refInput || getRefFromEnv(); - const sha = shaInput || getRequiredEnvParam("GITHUB_SHA"); + const sha = shaInput || env.getRequired(ActionsEnvVars.GITHUB_SHA); // If the ref is a user-provided input, we have to skip logic // and assume that it is really where they want to upload the results. From c5cd54340161c0ff1163ccceffeeb257cea4d1c7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:29:46 +0100 Subject: [PATCH 02/10] Replace `getRequiredEnvParam` calls in `init` and `setup-codeql` action --- lib/entry-points.js | 14 ++++++++------ src/init-action.ts | 13 +++++++------ src/setup-codeql-action.ts | 7 +++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 708be27a2f..bafe49f185 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -162024,8 +162024,8 @@ async function run3(actionState) { apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL") + url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) }; const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); @@ -162044,7 +162044,7 @@ async function run3(actionState) { const repositoryProperties = repositoryPropertiesResult.orElse({}); core22.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); sourceRoot = path25.resolve( - getRequiredEnvParam("GITHUB_WORKSPACE"), + actionState.env.getRequired("GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */), getOptionalInput("source-root") || "" ); let analysisKinds; @@ -162140,7 +162140,9 @@ async function run3(actionState) { repository: repositoryNwo, tempDir: getTemporaryDirectory(), codeql, - workspacePath: getRequiredEnvParam("GITHUB_WORKSPACE"), + workspacePath: actionState.env.getRequired( + "GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */ + ), sourceRoot, githubVersion: gitHubVersion, apiDetails, @@ -163048,8 +163050,8 @@ async function run6(actionState) { const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL") + url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) }; const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); diff --git a/src/init-action.ts b/src/init-action.ts index 8173d67aaa..40777f1a02 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -38,7 +38,7 @@ import { makeDiagnostic, makeTelemetryDiagnostic, } from "./diagnostics"; -import { EnvVar } from "./environment"; +import { ActionsEnvVars, EnvVar } from "./environment"; import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { loadRepositoryProperties } from "./feature-flags/properties"; import { @@ -81,7 +81,6 @@ import { DEFAULT_DEBUG_ARTIFACT_NAME, DEFAULT_DEBUG_DATABASE_NAME, getCodeQLMemoryLimit, - getRequiredEnvParam, getThreadsFlagValue, initializeEnvironment, ConfigurationError, @@ -238,8 +237,8 @@ async function run( apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL"), + url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), + apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL), }; const gitHubVersion = await getGitHubVersion(); @@ -268,7 +267,7 @@ async function run( // source-root is relative, it is relative to the GITHUB_WORKSPACE. If // source-root is absolute, it is used as given. sourceRoot = path.resolve( - getRequiredEnvParam("GITHUB_WORKSPACE"), + actionState.env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE), getOptionalInput("source-root") || "", ); @@ -396,7 +395,9 @@ async function run( repository: repositoryNwo, tempDir: getTemporaryDirectory(), codeql, - workspacePath: getRequiredEnvParam("GITHUB_WORKSPACE"), + workspacePath: actionState.env.getRequired( + ActionsEnvVars.GITHUB_WORKSPACE, + ), sourceRoot, githubVersion: gitHubVersion, apiDetails, diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index bb6b73c9aa..179de53533 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -12,7 +12,7 @@ import { getGitHubVersion } from "./api-client"; import { CodeQL } from "./codeql"; import { ComputedInput, getToolsInput } from "./config/inputs"; import { getRawLanguagesNoAutodetect } from "./config-utils"; -import { EnvVar } from "./environment"; +import { ActionsEnvVars, EnvVar } from "./environment"; import { initFeatures } from "./feature-flags"; import { loadRepositoryProperties } from "./feature-flags/properties"; import { initCodeQL } from "./init"; @@ -32,7 +32,6 @@ import { checkDiskUsage, checkForTimeout, checkGitHubVersionInRange, - getRequiredEnvParam, initializeEnvironment, ConfigurationError, wrapError, @@ -121,8 +120,8 @@ async function run( const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL"), + url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), + apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL), }; const gitHubVersion = await getGitHubVersion(); From 7ac2c102ff864ca4825a48a5289bcefaaab6da63 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:35:13 +0100 Subject: [PATCH 03/10] Refactor `setupDiffInformedQueryRun` querying `checkout_path` itself --- lib/entry-points.js | 16 +++++++++++----- src/analyze-action.ts | 14 +++++++++++--- src/analyze.ts | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index bafe49f185..e18907c905 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -153681,7 +153681,7 @@ async function finalizeDatabaseCreation(codeql, features, config, threadsFlag, m trap_import_duration_ms: Math.round(trapImportTime) }; } -async function setupDiffInformedQueryRun(logger) { +async function setupDiffInformedQueryRun(logger, checkoutPath) { return await withGroupAsync( "Generating diff range extension pack", async () => { @@ -153692,7 +153692,6 @@ async function setupDiffInformedQueryRun(logger) { ); return void 0; } - const checkoutPath = getRequiredInput("checkout_path"); const packDir = writeDiffRangeDataExtensionPack( logger, diffRanges, @@ -156341,7 +156340,11 @@ async function runAutobuildIfLegacyGoWorkflow(config, logger) { ); await runAutobuild(config, "go" /* go */, logger); } -async function run({ startedAt, logger }) { +async function run({ + startedAt, + logger, + actions +}) { let uploadResults = void 0; let runStats = void 0; let config = void 0; @@ -156407,7 +156410,11 @@ async function run({ startedAt, logger }) { getOptionalInput("ram") || process.env["CODEQL_RAM"], logger ); - const diffRangePackDir = await setupDiffInformedQueryRun(logger); + const checkoutPath = actions.getRequiredInput("checkout_path"); + const diffRangePackDir = await setupDiffInformedQueryRun( + logger, + checkoutPath + ); await warnIfGoInstalledAfterInit(config, logger); await runAutobuildIfLegacyGoWorkflow(config, logger); dbCreationTimings = await runFinalize( @@ -156447,7 +156454,6 @@ async function run({ startedAt, logger }) { getOptionalInput("upload") ); if (runStats) { - const checkoutPath = getRequiredInput("checkout_path"); const category = getOptionalInput("category"); uploadResults = await postProcessAndUploadSarif( logger, diff --git a/src/analyze-action.ts b/src/analyze-action.ts index c3c2e40e7f..2a64ed3c54 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -212,7 +212,11 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) { await runAutobuild(config, BuiltInLanguage.go, logger); } -async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { +async function run({ + startedAt, + logger, + actions, +}: ActionState<["Base", "Logger", "Actions"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. @@ -307,8 +311,13 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { logger, ); + const checkoutPath = actions.getRequiredInput("checkout_path"); + // Setup diff informed analysis if needed (based on whether init created the file) - const diffRangePackDir = await setupDiffInformedQueryRun(logger); + const diffRangePackDir = await setupDiffInformedQueryRun( + logger, + checkoutPath, + ); await warnIfGoInstalledAfterInit(config, logger); await runAutobuildIfLegacyGoWorkflow(config, logger); @@ -354,7 +363,6 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { actionsUtil.getOptionalInput("upload"), ); if (runStats) { - const checkoutPath = actionsUtil.getRequiredInput("checkout_path"); const category = actionsUtil.getOptionalInput("category"); uploadResults = await postProcessAndUploadSarif( diff --git a/src/analyze.ts b/src/analyze.ts index 411477b597..8f90711682 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -5,7 +5,7 @@ import { performance } from "perf_hooks"; import * as io from "@actions/io"; import * as yaml from "js-yaml"; -import { getTemporaryDirectory, getRequiredInput } from "./actions-util"; +import { getTemporaryDirectory } from "./actions-util"; import * as analyses from "./analyses"; import { setupCppAutobuild } from "./autobuild"; import { type CodeQL } from "./codeql"; @@ -233,6 +233,7 @@ async function finalizeDatabaseCreation( */ export async function setupDiffInformedQueryRun( logger: Logger, + checkoutPath: string, ): Promise { return await withGroupAsync( "Generating diff range extension pack", @@ -245,7 +246,6 @@ export async function setupDiffInformedQueryRun( return undefined; } - const checkoutPath = getRequiredInput("checkout_path"); const packDir = writeDiffRangeDataExtensionPack( logger, diffRanges, From 43adfcc08a0788c9245e48518eb4c839496ece91 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:37:49 +0100 Subject: [PATCH 04/10] Refactor `cleanupAndUploadOverlayBaseDatabaseToCache` querying `checkout_path` itself --- lib/entry-points.js | 10 +++++++--- src/analyze-action.ts | 7 ++++++- src/overlay/caching.ts | 11 ++++------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index e18907c905..2072acd570 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -151233,7 +151233,7 @@ async function checkOverlayBaseDatabase(codeql, config, logger, warningPrefix) { } return true; } -async function cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger) { +async function cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger, checkoutPath) { const overlayDatabaseMode = config.overlayDatabaseMode; if (overlayDatabaseMode !== "overlay-base" /* OverlayBase */) { logger.debug( @@ -151281,7 +151281,6 @@ async function cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger return false; } const codeQlVersion = (await codeql.getVersion()).version; - const checkoutPath = getRequiredInput("checkout_path"); const cacheSaveKey = await getCacheSaveKey( config, codeQlVersion, @@ -156479,7 +156478,12 @@ async function run({ } else { logger.info("Not uploading results"); } - await cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger); + await cleanupAndUploadOverlayBaseDatabaseToCache( + codeql, + config, + logger, + checkoutPath + ); databaseUploadResults = await cleanupAndUploadDatabases( repositoryNwo, codeql, diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 2a64ed3c54..55803d2611 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -396,7 +396,12 @@ async function run({ // Possibly upload the overlay-base database to actions cache. // Note: Take care with the ordering of this call since databases may be cleaned up // at the `overlay` level. - await cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger); + await cleanupAndUploadOverlayBaseDatabaseToCache( + codeql, + config, + logger, + checkoutPath, + ); // Possibly upload the database bundles for remote queries. // Note: Take care with the ordering of this call since databases may be cleaned up diff --git a/src/overlay/caching.ts b/src/overlay/caching.ts index c4557cd4ef..d246626780 100644 --- a/src/overlay/caching.ts +++ b/src/overlay/caching.ts @@ -3,11 +3,7 @@ import * as fs from "fs"; import * as actionsCache from "@actions/cache"; import * as semver from "semver"; -import { - getRequiredInput, - getWorkflowRunAttempt, - getWorkflowRunID, -} from "../actions-util"; +import { getWorkflowRunAttempt, getWorkflowRunID } from "../actions-util"; import { getAutomationID, listActionsCaches } from "../api-client"; import { createCacheKeyHash } from "../caching-utils"; import { type CodeQL } from "../codeql"; @@ -107,12 +103,13 @@ async function checkOverlayBaseDatabase( * Uploads the overlay-base database to the GitHub Actions cache. If conditions * for uploading are not met, the function does nothing and returns false. * - * This function uses the `checkout_path` input to determine the repository path + * This function uses the `checkoutPath` to determine the repository path * and works only when called from `analyze` or `upload-sarif`. * * @param codeql The CodeQL instance * @param config The configuration object * @param logger The logger instance + * @param checkoutPath The path at which the repository is checked out at. * @returns A promise that resolves to true if the upload was performed and * successfully completed, or false otherwise */ @@ -120,6 +117,7 @@ export async function cleanupAndUploadOverlayBaseDatabaseToCache( codeql: CodeQL, config: Config, logger: Logger, + checkoutPath: string, ): Promise { const overlayDatabaseMode = config.overlayDatabaseMode; if (overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase) { @@ -180,7 +178,6 @@ export async function cleanupAndUploadOverlayBaseDatabaseToCache( } const codeQlVersion = (await codeql.getVersion()).version; - const checkoutPath = getRequiredInput("checkout_path"); const cacheSaveKey = await getCacheSaveKey( config, codeQlVersion, From 30f0cd4ddac71d018c5ef9116209278c33e3e1dd Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:44:04 +0100 Subject: [PATCH 05/10] Refactor: Add `determineCheckoutPath` function for `analyze` action --- lib/entry-points.js | 13 +++++++------ src/analyze-action.ts | 11 +++++------ src/analyze.ts | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 2072acd570..a2bda61415 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -153595,6 +153595,9 @@ var CodeQLAnalysisError = class extends Error { message; error; }; +function determineCheckoutPath(action) { + return action.actions.getRequiredInput("checkout_path"); +} async function setupPythonExtractor(logger) { const codeqlPython = process.env["CODEQL_PYTHON"]; if (codeqlPython === void 0 || codeqlPython.length === 0) { @@ -156339,11 +156342,9 @@ async function runAutobuildIfLegacyGoWorkflow(config, logger) { ); await runAutobuild(config, "go" /* go */, logger); } -async function run({ - startedAt, - logger, - actions -}) { +async function run(action) { + const startedAt = action.startedAt; + const logger = action.logger; let uploadResults = void 0; let runStats = void 0; let config = void 0; @@ -156409,7 +156410,7 @@ async function run({ getOptionalInput("ram") || process.env["CODEQL_RAM"], logger ); - const checkoutPath = actions.getRequiredInput("checkout_path"); + const checkoutPath = determineCheckoutPath(action); const diffRangePackDir = await setupDiffInformedQueryRun( logger, checkoutPath diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 55803d2611..1e2ca2dc15 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -10,6 +10,7 @@ import * as analyses from "./analyses"; import { CodeQLAnalysisError, dbIsFinalized, + determineCheckoutPath, QueriesStatusReport, runFinalize, runQueries, @@ -212,13 +213,11 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) { await runAutobuild(config, BuiltInLanguage.go, logger); } -async function run({ - startedAt, - logger, - actions, -}: ActionState<["Base", "Logger", "Actions"]>) { +async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. + const startedAt = action.startedAt; + const logger = action.logger; let uploadResults: | Partial> @@ -311,7 +310,7 @@ async function run({ logger, ); - const checkoutPath = actions.getRequiredInput("checkout_path"); + const checkoutPath = determineCheckoutPath(action); // Setup diff informed analysis if needed (based on whether init created the file) const diffRangePackDir = await setupDiffInformedQueryRun( diff --git a/src/analyze.ts b/src/analyze.ts index 8f90711682..9671cf6776 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -5,6 +5,7 @@ import { performance } from "perf_hooks"; import * as io from "@actions/io"; import * as yaml from "js-yaml"; +import type { ActionState } from "./action-common"; import { getTemporaryDirectory } from "./actions-util"; import * as analyses from "./analyses"; import { setupCppAutobuild } from "./autobuild"; @@ -85,6 +86,19 @@ export interface QueriesStatusReport event_reports?: EventReport[]; } +/** + * Determines the path at which the repository being analysed is checked out at. + * Returns the value of the required `checkout_path` input and validates that it + * refers to the root of a repository. + * + * @param action The action state. + */ +export function determineCheckoutPath( + action: ActionState<["Actions", "ReadOnlyEnv"]>, +) { + return action.actions.getRequiredInput("checkout_path"); +} + async function setupPythonExtractor(logger: Logger) { const codeqlPython = process.env["CODEQL_PYTHON"]; if (codeqlPython === undefined || codeqlPython.length === 0) { From d8344b1cf8d6ded980fb7e9b23e904e8af14efb0 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:57:20 +0100 Subject: [PATCH 06/10] Refactor `cleanupAndUploadDatabases` querying `checkout_path` itself --- lib/entry-points.js | 13 ++++---- src/analyze-action.ts | 4 +-- src/database-upload.test.ts | 59 +++++++++++++++++++++---------------- src/database-upload.ts | 15 +++++----- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a2bda61415..8ba3b525a5 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -153969,7 +153969,8 @@ async function warnIfGoInstalledAfterInit(config, logger) { // src/database-upload.ts var fs18 = __toESM(require("fs")); -async function cleanupAndUploadDatabases(repositoryNwo, codeql, config, apiDetails, features, logger) { +async function cleanupAndUploadDatabases(action, repositoryNwo, codeql, config, apiDetails, checkoutPath) { + const logger = action.logger; if (getRequiredInput("upload-database") !== "true") { logger.debug("Database upload disabled in workflow. Skipping upload."); return []; @@ -153992,7 +153993,7 @@ async function cleanupAndUploadDatabases(repositoryNwo, codeql, config, apiDetai logger.debug("Not analyzing default branch. Skipping upload."); return []; } - const shouldUploadOverlayBase = config.overlayDatabaseMode === "overlay-base" /* OverlayBase */ && await features.getValue("upload_overlay_db_to_api" /* UploadOverlayDbToApi */, codeql); + const shouldUploadOverlayBase = config.overlayDatabaseMode === "overlay-base" /* OverlayBase */ && await action.features.getValue("upload_overlay_db_to_api" /* UploadOverlayDbToApi */, codeql); const cleanupLevel = shouldUploadOverlayBase ? "overlay" /* Overlay */ : "clear" /* Clear */; await withGroupAsync("Cleaning up databases", async () => { await codeql.databaseCleanupCluster(config, cleanupLevel); @@ -154005,9 +154006,7 @@ async function cleanupAndUploadDatabases(repositoryNwo, codeql, config, apiDetai includeDiagnostics: false }); bundledDbSize = fs18.statSync(bundledDb).size; - const commitOid = await getCommitOid( - getRequiredInput("checkout_path") - ); + const commitOid = await getCommitOid(checkoutPath); const maxAttempts = 4; let uploadDurationMs; for (let attempt = 1; attempt <= maxAttempts; attempt++) { @@ -156486,12 +156485,12 @@ async function run(action) { checkoutPath ); databaseUploadResults = await cleanupAndUploadDatabases( + { logger, features }, repositoryNwo, codeql, config, apiDetails, - features, - logger + checkoutPath ); const trapCacheUploadStartTime = import_perf_hooks4.performance.now(); didUploadTrapCaches = await uploadTrapCaches(codeql, config, logger); diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 1e2ca2dc15..67b025ead7 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -406,12 +406,12 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { // Note: Take care with the ordering of this call since databases may be cleaned up // at the `overlay` or `clear` level. databaseUploadResults = await cleanupAndUploadDatabases( + { logger, features }, repositoryNwo, codeql, config, apiDetails, - features, - logger, + checkoutPath, ); // Possibly upload the TRAP caches for later re-use diff --git a/src/database-upload.test.ts b/src/database-upload.test.ts index bcaf9f1c9e..5b76658bf9 100644 --- a/src/database-upload.test.ts +++ b/src/database-upload.test.ts @@ -20,7 +20,7 @@ import { checkExpectedLogMessages, createFeatures, createTestConfig, - getRecordingLogger, + initAllState, LoggedMessage, setupActionsVars, setupTests, @@ -99,12 +99,12 @@ test.serial( const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ "Database upload disabled in workflow. Skipping upload.", @@ -128,6 +128,7 @@ test.serial( const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), { @@ -135,8 +136,7 @@ test.serial( analysisKinds: [AnalysisKind.CodeQuality], }, testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ "Not uploading database because 'analysis-kinds: code-scanning' is not enabled.", @@ -159,12 +159,12 @@ test.serial("Abort database upload if running against GHES", async (t) => { const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), config, testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ "Not running against github.com or GHEC-DR. Skipping upload.", @@ -185,12 +185,12 @@ test.serial( const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ "Not analyzing default branch. Skipping upload.", @@ -214,12 +214,12 @@ test.serial( const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ @@ -253,12 +253,12 @@ test.serial( const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ @@ -290,12 +290,12 @@ test.serial("Successfully uploading a database to github.com", async (t) => { const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ "Successfully uploaded database for javascript", @@ -316,6 +316,7 @@ test.serial("Successfully uploading a database to GHEC-DR", async (t) => { const loggedMessages: LoggedMessage[] = []; await cleanupAndUploadDatabases( + initAllState(), testRepoName, getCodeQL(), getTestConfig(tmpDir), @@ -324,8 +325,7 @@ test.serial("Successfully uploading a database to GHEC-DR", async (t) => { url: "https://tenant.ghe.com", apiURL: undefined, }, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); checkExpectedLogMessages(t, loggedMessages, [ "Successfully uploaded database for javascript", @@ -375,14 +375,15 @@ test.serial( const config = getTestConfig(tmpDir); config.overlayDatabaseMode = OverlayDatabaseMode.OverlayBase; - const loggedMessages: LoggedMessage[] = []; const results = await cleanupAndUploadDatabases( + initAllState({ + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, config, testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger(loggedMessages), + "", ); // The database should be cleaned up at the `overlay` level for the upload @@ -422,12 +423,14 @@ test.serial( }); const results = await cleanupAndUploadDatabases( + initAllState({ + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, getTestConfig(tmpDir), testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger([]), + "", ); // A regular upload is cleaned only once, at the `clear` level. @@ -465,12 +468,14 @@ test.serial("Does not measure clear cleanup size in debug mode", async (t) => { config.debugMode = true; const results = await cleanupAndUploadDatabases( + initAllState({ + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, config, testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger([]), + "", ); // In debug mode we clean up at the `overlay` level for the upload but skip @@ -510,12 +515,14 @@ test.serial( config.overlayDatabaseMode = OverlayDatabaseMode.OverlayBase; const results = await cleanupAndUploadDatabases( + initAllState({ + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, config, testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger([]), + "", ); // When the `clear` cleanup fails, no size is measured, so we should not diff --git a/src/database-upload.ts b/src/database-upload.ts index 0189bef1e6..9e4339fd47 100644 --- a/src/database-upload.ts +++ b/src/database-upload.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; +import { ActionState } from "./action-common"; import * as actionsUtil from "./actions-util"; import { AnalysisKind } from "./analyses"; import { @@ -9,7 +10,7 @@ import { } from "./api-client"; import { type CodeQL } from "./codeql"; import { Config } from "./config-utils"; -import { Feature, FeatureEnablement } from "./feature-flags"; +import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; import { Logger, withGroupAsync } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; @@ -45,13 +46,15 @@ export interface DatabaseUploadResult { } export async function cleanupAndUploadDatabases( + action: ActionState<["Logger", "FeatureFlags"]>, repositoryNwo: RepositoryNwo, codeql: CodeQL, config: Config, apiDetails: GitHubApiDetails, - features: FeatureEnablement, - logger: Logger, + checkoutPath: string, ): Promise { + const logger = action.logger; + if (actionsUtil.getRequiredInput("upload-database") !== "true") { logger.debug("Database upload disabled in workflow. Skipping upload."); return []; @@ -87,7 +90,7 @@ export async function cleanupAndUploadDatabases( // If config.overlayDatabaseMode is OverlayBase, then we have overlay base databases for all languages. const shouldUploadOverlayBase = config.overlayDatabaseMode === OverlayDatabaseMode.OverlayBase && - (await features.getValue(Feature.UploadOverlayDbToApi, codeql)); + (await action.features.getValue(Feature.UploadOverlayDbToApi, codeql)); const cleanupLevel = shouldUploadOverlayBase ? CleanupLevel.Overlay : CleanupLevel.Clear; @@ -110,9 +113,7 @@ export async function cleanupAndUploadDatabases( includeDiagnostics: false, }); bundledDbSize = fs.statSync(bundledDb).size; - const commitOid = await gitUtils.getCommitOid( - actionsUtil.getRequiredInput("checkout_path"), - ); + const commitOid = await gitUtils.getCommitOid(checkoutPath); // Upload with manual retry logic. We disable Octokit's built-in retries // because the request body is a ReadStream, which can only be consumed // once. From c143a0f643e443fcf237f2b51f98bd59a2a09450 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 12:49:02 +0100 Subject: [PATCH 07/10] Persist repository root from `init` action in CodeQL Action state --- lib/entry-points.js | 18 +++++++++++------- src/config-utils.test.ts | 12 +++++++----- src/config-utils.ts | 16 ++++++++++++---- src/config/action-config.ts | 5 +++++ src/testing-utils.ts | 1 + 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 8ba3b525a5..93ee7ed27d 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -150438,7 +150438,7 @@ async function initActionState({ analysisKinds, logger, enableFileCoverageInformation -}, userConfig) { +}, userConfig, repositoryRoot) { const languages = await getLanguages( codeql, languagesInput, @@ -150474,6 +150474,7 @@ async function initActionState({ ); return { version: getActionVersion(), + repositoryRoot, analysisKinds, languages, buildMode, @@ -150624,7 +150625,7 @@ async function checkRunnerResources(codeql, features, diskUsage, ramInput, logge } return new Success(void 0); } -async function checkOverlayEnablement(codeql, features, languages, sourceRoot, buildMode, ramInput, codeScanningConfig, repositoryProperties, gitVersion, logger) { +async function checkOverlayEnablement(codeql, features, languages, repositoryRoot, sourceRoot, buildMode, ramInput, codeScanningConfig, repositoryProperties, gitVersion, logger) { const modeEnv = process.env.CODEQL_OVERLAY_DATABASE_MODE; if (modeEnv === "overlay" /* Overlay */ || modeEnv === "overlay-base" /* OverlayBase */ || modeEnv === "none" /* None */) { logger.info( @@ -150639,6 +150640,7 @@ async function checkOverlayEnablement(codeql, features, languages, sourceRoot, b true, codeql, languages, + repositoryRoot, sourceRoot, buildMode, gitVersion, @@ -150711,13 +150713,14 @@ async function checkOverlayEnablement(codeql, features, languages, sourceRoot, b false, codeql, languages, + repositoryRoot, sourceRoot, buildMode, gitVersion, logger ); } -async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDatabaseCaching, overlayModeSetExplicitly, codeql, languages, sourceRoot, buildMode, gitVersion, logger) { +async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDatabaseCaching, overlayModeSetExplicitly, codeql, languages, repositoryRoot, sourceRoot, buildMode, gitVersion, logger) { if (buildMode !== "none" /* None */ && (await Promise.all( languages.map( async (l) => l !== "go" /* go */ && // Workaround to allow overlay analysis for Go with any build @@ -150738,14 +150741,13 @@ async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDataba ); return new Failure("incompatible-codeql" /* IncompatibleCodeQl */); } - const gitRoot = await getGitRoot(sourceRoot); - if (gitRoot === void 0) { + if (repositoryRoot === void 0) { logger.warning( `Cannot build an ${overlayDatabaseMode} database because the source root "${sourceRoot}" is not inside a git repository. Falling back to creating a normal full database instead.` ); return new Failure("no-git-root" /* NoGitRoot */); } - if (hasSubmodules(gitRoot)) { + if (hasSubmodules(repositoryRoot)) { if (gitVersion === void 0) { logger.warning( `Cannot build an ${overlayDatabaseMode} database because the repository has submodules and the Git version could not be determined. Falling back to creating a normal full database instead.` @@ -150880,8 +150882,9 @@ async function determineUserConfig(action, tempDir, inputs) { async function initConfig(actionState, inputs) { const { logger, features } = actionState; const { tempDir } = inputs; + const repositoryRoot = await getGitRoot(inputs.sourceRoot); const userConfig = await determineUserConfig(actionState, tempDir, inputs); - const config = await initActionState(inputs, userConfig); + const config = await initActionState(inputs, userConfig, repositoryRoot); if (config.analysisKinds.length === 1 && isCodeQualityEnabled(config)) { if (hasQueryCustomisation(config.computedConfig)) { throw new ConfigurationError( @@ -150934,6 +150937,7 @@ async function initConfig(actionState, inputs) { inputs.codeql, inputs.features, config.languages, + repositoryRoot, inputs.sourceRoot, config.buildMode, inputs.ramInput, diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 29d72f3af4..ec18bec453 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -172,6 +172,7 @@ test.serial("load empty config", async (t) => { createTestInitConfigInputs({ languagesInput: languages, repository: { owner: "github", repo: "example" }, + sourceRoot: tempDir, tempDir, codeql, logger, @@ -186,6 +187,7 @@ test.serial("load empty config", async (t) => { logger, }), {}, + undefined, ); t.deepEqual(config, expectedConfig); @@ -216,6 +218,7 @@ test.serial("load code quality config", async (t) => { analysisKinds: [AnalysisKind.CodeQuality], languagesInput: languages, repository: { owner: "github", repo: "example" }, + sourceRoot: tempDir, tempDir, codeql, logger, @@ -296,6 +299,7 @@ test.serial( analysisKinds: [AnalysisKind.CodeQuality], languagesInput: languages, repository: { owner: "github", repo: "example" }, + sourceRoot: tempDir, tempDir, codeql, repositoryProperties, @@ -512,6 +516,7 @@ test.serial("load non-empty input", async (t) => { // And the config we expect it to parse to const expectedConfig = createTestConfig({ languages: [BuiltInLanguage.javascript], + repositoryRoot: undefined, buildMode: BuildMode.None, originalUserInput: userConfig, computedConfig: userConfig, @@ -532,6 +537,7 @@ test.serial("load non-empty input", async (t) => { state, createTestInitConfigInputs({ languagesInput, + sourceRoot: tempDir, buildModeInput: "none", configFile: configFilePath, debugArtifactName: "my-artifact", @@ -1092,11 +1098,6 @@ const checkOverlayEnablementMacro = makeMacro({ return lang === BuiltInLanguage.java; }); - // Mock git root detection - if (setup.gitRoot !== undefined) { - sinon.stub(gitUtils, "getGitRoot").resolves(setup.gitRoot); - } - // Mock submodule detection sinon.stub(gitUtils, "hasSubmodules").returns(setup.hasSubmodules); @@ -1109,6 +1110,7 @@ const checkOverlayEnablementMacro = makeMacro({ codeql, features, setup.languages, + setup.gitRoot, // repositoryRoot tempDir, // sourceRoot setup.buildMode, undefined, diff --git a/src/config-utils.ts b/src/config-utils.ts index 288b4f02fb..37337b68d3 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -385,6 +385,7 @@ export async function initActionState( enableFileCoverageInformation, }: InitConfigInputs, userConfig: UserConfig, + repositoryRoot: string | undefined, ): Promise { const languages = await getLanguages( codeql, @@ -436,6 +437,7 @@ export async function initActionState( return { version: getActionVersion(), + repositoryRoot, analysisKinds, languages, buildMode, @@ -718,6 +720,7 @@ export async function checkOverlayEnablement( codeql: CodeQL, features: FeatureEnablement, languages: Language[], + repositoryRoot: string | undefined, sourceRoot: string, buildMode: BuildMode | undefined, ramInput: string | undefined, @@ -747,6 +750,7 @@ export async function checkOverlayEnablement( true, codeql, languages, + repositoryRoot, sourceRoot, buildMode, gitVersion, @@ -836,6 +840,7 @@ export async function checkOverlayEnablement( false, codeql, languages, + repositoryRoot, sourceRoot, buildMode, gitVersion, @@ -855,6 +860,7 @@ async function validateOverlayDatabaseMode( overlayModeSetExplicitly: boolean, codeql: CodeQL, languages: Language[], + repositoryRoot: string | undefined, sourceRoot: string, buildMode: BuildMode | undefined, gitVersion: GitVersionInfo | undefined, @@ -890,8 +896,7 @@ async function validateOverlayDatabaseMode( ); return new Failure(OverlayDisabledReason.IncompatibleCodeQl); } - const gitRoot = await getGitRoot(sourceRoot); - if (gitRoot === undefined) { + if (repositoryRoot === undefined) { logger.warning( `Cannot build an ${overlayDatabaseMode} database because ` + `the source root "${sourceRoot}" is not inside a git repository. ` + @@ -899,7 +904,7 @@ async function validateOverlayDatabaseMode( ); return new Failure(OverlayDisabledReason.NoGitRoot); } - if (hasSubmodules(gitRoot)) { + if (hasSubmodules(repositoryRoot)) { if (gitVersion === undefined) { logger.warning( `Cannot build an ${overlayDatabaseMode} database because ` + @@ -1160,9 +1165,11 @@ export async function initConfig( const { logger, features } = actionState; const { tempDir } = inputs; + const repositoryRoot = await getGitRoot(inputs.sourceRoot); + const userConfig = await determineUserConfig(actionState, tempDir, inputs); - const config = await initActionState(inputs, userConfig); + const config = await initActionState(inputs, userConfig, repositoryRoot); // If Code Quality analysis is the only enabled analysis kind, then we will initialise // the database for Code Quality. That entails disabling the default queries and only @@ -1244,6 +1251,7 @@ export async function initConfig( inputs.codeql, inputs.features, config.languages, + repositoryRoot, inputs.sourceRoot, config.buildMode, inputs.ramInput, diff --git a/src/config/action-config.ts b/src/config/action-config.ts index de6882e77e..2b69141d2e 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -16,6 +16,11 @@ export interface Config { * The version of the CodeQL Action that the configuration is for. */ version: string; + /** + * The path at which the repository being analysed is checked out at, if available. + * Persisted in the CodeQL Action configuration state, so that we can consult it in later workflow steps. + */ + repositoryRoot: string | undefined; /** * Set of analysis kinds that are enabled. */ diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 7d33589ec6..b27f298c9b 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -974,6 +974,7 @@ export function createTestConfig(overrides: Partial): Config { {}, { version: getActionVersion(), + repositoryRoot: undefined, analysisKinds: [AnalysisKind.CodeScanning], languages: [], buildMode: undefined, From cad2c352dcc592bac65e0bad1420d5b2b293f632 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 14:30:57 +0100 Subject: [PATCH 08/10] Validate that `checkout_path` refers to a valid repo root --- lib/entry-points.js | 25 ++++++++++++++++++++++--- src/analyze-action.test.ts | 2 ++ src/analyze-action.ts | 2 +- src/analyze.ts | 30 +++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 93ee7ed27d..41d607ee22 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -153599,8 +153599,27 @@ var CodeQLAnalysisError = class extends Error { message; error; }; -function determineCheckoutPath(action) { - return action.actions.getRequiredInput("checkout_path"); +async function determineCheckoutPath(action) { + const checkoutPathInput = action.actions.getRequiredInput("checkout_path"); + const repositoryRoot = await getGitRoot(checkoutPathInput); + if (repositoryRoot === void 0) { + action.logger.warning( + [ + `The directory at '${checkoutPathInput}' is not in the work tree of a git repository.`, + "If the repository being analyzed is checked out elsewhere,", + "you must explicitly set the 'checkout_path' input for the 'codeql-action/analyze' step to", + "the checkout path." + ].join(" ") + ); + } else if (repositoryRoot !== path16.resolve(checkoutPathInput)) { + action.logger.warning( + [ + `The directory at '${checkoutPathInput}' is not the root of the repository ('${repositoryRoot}').`, + "Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout." + ].join(" ") + ); + } + return checkoutPathInput; } async function setupPythonExtractor(logger) { const codeqlPython = process.env["CODEQL_PYTHON"]; @@ -156413,7 +156432,7 @@ async function run(action) { getOptionalInput("ram") || process.env["CODEQL_RAM"], logger ); - const checkoutPath = determineCheckoutPath(action); + const checkoutPath = await determineCheckoutPath(action); const diffRangePackDir = await setupDiffInformedQueryRun( logger, checkoutPath diff --git a/src/analyze-action.test.ts b/src/analyze-action.test.ts index 923908a641..ffba5a03dc 100644 --- a/src/analyze-action.test.ts +++ b/src/analyze-action.test.ts @@ -45,6 +45,7 @@ test.serial( requiredInputStub.withArgs("token").returns("fake-token"); requiredInputStub.withArgs("upload-database").returns("false"); requiredInputStub.withArgs("output").returns("out"); + requiredInputStub.withArgs("checkout_path").returns(""); const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput"); optionalInputStub.withArgs("expect-error").returns("false"); sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion); @@ -104,6 +105,7 @@ test.serial( requiredInputStub.withArgs("token").returns("fake-token"); requiredInputStub.withArgs("upload-database").returns("false"); requiredInputStub.withArgs("output").returns("out"); + requiredInputStub.withArgs("checkout_path").returns(""); const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput"); optionalInputStub.withArgs("expect-error").returns("false"); sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion); diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 67b025ead7..7c117f75a8 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -310,7 +310,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { logger, ); - const checkoutPath = determineCheckoutPath(action); + const checkoutPath = await determineCheckoutPath(action); // Setup diff informed analysis if needed (based on whether init created the file) const diffRangePackDir = await setupDiffInformedQueryRun( diff --git a/src/analyze.ts b/src/analyze.ts index 9671cf6776..306ccd434f 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -22,6 +22,7 @@ import { } from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; import { FeatureEnablement, Feature } from "./feature-flags"; +import { getGitRoot } from "./git-utils"; import { BuiltInLanguage, Language } from "./languages"; import { Logger, withGroupAsync } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; @@ -93,10 +94,33 @@ export interface QueriesStatusReport * * @param action The action state. */ -export function determineCheckoutPath( - action: ActionState<["Actions", "ReadOnlyEnv"]>, +export async function determineCheckoutPath( + action: ActionState<["Logger", "Actions"]>, ) { - return action.actions.getRequiredInput("checkout_path"); + const checkoutPathInput = action.actions.getRequiredInput("checkout_path"); + + // Try to obtain the root path of the repository and validate that it matches the input. + const repositoryRoot = await getGitRoot(checkoutPathInput); + + if (repositoryRoot === undefined) { + action.logger.warning( + [ + `The directory at '${checkoutPathInput}' is not in the work tree of a git repository.`, + "If the repository being analyzed is checked out elsewhere,", + "you must explicitly set the 'checkout_path' input for the 'codeql-action/analyze' step to", + "the checkout path.", + ].join(" "), + ); + } else if (repositoryRoot !== path.resolve(checkoutPathInput)) { + action.logger.warning( + [ + `The directory at '${checkoutPathInput}' is not the root of the repository ('${repositoryRoot}').`, + "Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout.", + ].join(" "), + ); + } + + return checkoutPathInput; } async function setupPythonExtractor(logger: Logger) { From c03fba91f57a7b94ca018d4931cab9137e5e2a97 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 15:27:29 +0100 Subject: [PATCH 09/10] Verify persisted repository root path in `analyze` action --- lib/entry-points.js | 13 +++++++++++-- src/analyze-action.ts | 2 +- src/analyze.ts | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 41d607ee22..5d3f8d580e 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -153599,7 +153599,7 @@ var CodeQLAnalysisError = class extends Error { message; error; }; -async function determineCheckoutPath(action) { +async function determineCheckoutPath(action, config) { const checkoutPathInput = action.actions.getRequiredInput("checkout_path"); const repositoryRoot = await getGitRoot(checkoutPathInput); if (repositoryRoot === void 0) { @@ -153618,6 +153618,15 @@ async function determineCheckoutPath(action) { "Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout." ].join(" ") ); + } else if (config.repositoryRoot !== void 0 && repositoryRoot !== config.repositoryRoot) { + action.logger.warning( + [ + `The repository path at '${repositoryRoot}' does not match that found by the 'codeql-action/init' step: '${config.repositoryRoot}'.`, + "Ensure that the 'checkout_path' input for the 'codeql-action/analyze' step is set to the path of the same repository that", + "the 'codeql-action/init' step determined. This is either the GitHub Actions workspace or the repository root corresponding to", + "the 'source-root' input if that was provided." + ].join(" ") + ); } return checkoutPathInput; } @@ -156432,7 +156441,7 @@ async function run(action) { getOptionalInput("ram") || process.env["CODEQL_RAM"], logger ); - const checkoutPath = await determineCheckoutPath(action); + const checkoutPath = await determineCheckoutPath(action, config); const diffRangePackDir = await setupDiffInformedQueryRun( logger, checkoutPath diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 7c117f75a8..8cc13c1ba0 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -310,7 +310,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { logger, ); - const checkoutPath = await determineCheckoutPath(action); + const checkoutPath = await determineCheckoutPath(action, config); // Setup diff informed analysis if needed (based on whether init created the file) const diffRangePackDir = await setupDiffInformedQueryRun( diff --git a/src/analyze.ts b/src/analyze.ts index 306ccd434f..f1a1841f38 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -93,9 +93,11 @@ export interface QueriesStatusReport * refers to the root of a repository. * * @param action The action state. + * @param config The CodeQL Action configuration state. */ export async function determineCheckoutPath( action: ActionState<["Logger", "Actions"]>, + config: configUtils.Config, ) { const checkoutPathInput = action.actions.getRequiredInput("checkout_path"); @@ -118,6 +120,19 @@ export async function determineCheckoutPath( "Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout.", ].join(" "), ); + } else if ( + config.repositoryRoot !== undefined && + repositoryRoot !== config.repositoryRoot + ) { + // The repository root that was persisted by the `init` step doesn't match the one we have found here. + action.logger.warning( + [ + `The repository path at '${repositoryRoot}' does not match that found by the 'codeql-action/init' step: '${config.repositoryRoot}'.`, + "Ensure that the 'checkout_path' input for the 'codeql-action/analyze' step is set to the path of the same repository that", + "the 'codeql-action/init' step determined. This is either the GitHub Actions workspace or the repository root corresponding to", + "the 'source-root' input if that was provided.", + ].join(" "), + ); } return checkoutPathInput; From 9a590420d8c9b4e236c6423f5ae23871ce63dfca Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 16 Sep 2026 16:57:34 +0100 Subject: [PATCH 10/10] Avoid `getCheckoutPath` --- lib/entry-points.js | 91 +++++++++++--------- src/actions-util.ts | 6 +- src/analyze-action.ts | 2 +- src/codeql.ts | 2 +- src/config-utils.ts | 13 ++- src/database-upload.test.ts | 91 +++++++++++--------- src/database-upload.ts | 6 +- src/environment.ts | 7 ++ src/git-utils.test.ts | 161 ++++++++++++++++++++++-------------- src/git-utils.ts | 53 ++++++------ src/init-action-post.ts | 5 +- src/overlay/caching.ts | 3 +- src/status-report.ts | 2 +- src/trap-caching.test.ts | 1 + src/trap-caching.ts | 18 +++- src/upload-lib.ts | 9 +- 16 files changed, 283 insertions(+), 187 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 5d3f8d580e..13a522b3ec 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -142187,6 +142187,12 @@ var Env = class extends ReadOnlyEnv { this.vars[name] = value; this.changed = true; } + /** Sets all environment variables given by `vars`. */ + setAll(vars) { + for (const [key, val] of Object.entries(vars)) { + this.set(key, val); + } + } /** Gets a value indicating whether `set` was called at least once. */ hasChanged() { return this.changed; @@ -147015,7 +147021,7 @@ var runGitCommand = async function(workingDirectory, args, customErrorMessage, o throw error3; } }; -var getCommitOid = async function(checkoutPath, ref = "HEAD") { +var getCommitOid = async function(env, checkoutPath, ref = "HEAD") { try { const stdout = await runGitCommand( checkoutPath, @@ -147024,7 +147030,7 @@ var getCommitOid = async function(checkoutPath, ref = "HEAD") { ); return stdout.trim(); } catch { - return getOptionalInput("sha") || getRequiredEnvParam("GITHUB_SHA"); + return getOptionalInput("sha") || env.getRequired("GITHUB_SHA" /* GITHUB_SHA */); } }; var determineBaseBranchHeadCommitOid = async function(checkoutPathOverride) { @@ -147134,12 +147140,12 @@ var getFileOidsUnderPath = async function(basePath) { } return fileOidMap; }; -function getRefFromEnv() { +function getRefFromEnv(env) { let refEnv; try { - refEnv = getRequiredEnvParam("GITHUB_REF"); + refEnv = env.getRequired("GITHUB_REF" /* GITHUB_REF */); } catch (e) { - const maybeRef = process.env["CODE_SCANNING_REF"]; + const maybeRef = env.getOptional("CODE_SCANNING_REF" /* CODE_SCANNING_REF */); if (maybeRef === void 0 || maybeRef.length === 0) { throw e; } @@ -147147,13 +147153,10 @@ function getRefFromEnv() { } return refEnv; } -function getCheckoutPath(env) { - return getOptionalInput("checkout_path") || getOptionalInput("source-root") || env.getRequired("GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */); -} -async function getRef(env = getEnv()) { +async function getRef(env, checkoutPath) { const refInput = getOptionalInput("ref"); const shaInput = getOptionalInput("sha"); - const checkoutPath = getCheckoutPath(env); + checkoutPath = checkoutPath ?? env.getRequired("GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */); const hasRefInput = !!refInput; const hasShaInput = !!shaInput; if ((hasRefInput || hasShaInput) && !(hasRefInput && hasShaInput)) { @@ -147161,7 +147164,7 @@ async function getRef(env = getEnv()) { "Both 'ref' and 'sha' are required if one of them is provided." ); } - const ref = refInput || getRefFromEnv(); + const ref = refInput || getRefFromEnv(env); const sha = shaInput || env.getRequired("GITHUB_SHA" /* GITHUB_SHA */); if (refInput) { return refInput; @@ -147170,8 +147173,9 @@ async function getRef(env = getEnv()) { if (!pull_ref_regex.test(ref)) { return ref; } - const head = await getCommitOid(checkoutPath, "HEAD"); + const head = await getCommitOid(env, checkoutPath, "HEAD"); const hasChangedRef = sha !== head && await getCommitOid( + env, checkoutPath, ref.replace(/^refs\/pull\//, "refs/remotes/pull/") ) !== head; @@ -147188,16 +147192,16 @@ async function getRef(env = getEnv()) { function removeRefsHeadsPrefix(ref) { return ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref; } -async function isAnalyzingDefaultBranch() { - if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") { +async function isAnalyzingDefaultBranch(env, checkoutPath) { + if (env.getOptional("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH") === "true") { return true; } - let currentRef = await getRef(); + let currentRef = await getRef(env, checkoutPath); currentRef = removeRefsHeadsPrefix(currentRef); - const event = getWorkflowEvent(); + const event = getWorkflowEvent(env); let defaultBranch = event?.repository?.default_branch; - if (getWorkflowEventName() === "schedule") { - defaultBranch = removeRefsHeadsPrefix(getRefFromEnv()); + if (getWorkflowEventName(env) === "schedule") { + defaultBranch = removeRefsHeadsPrefix(getRefFromEnv(env)); } return currentRef === defaultBranch; } @@ -147469,7 +147473,7 @@ function getRegistryTypesFromEnv(logger, env = getEnv()) { async function createStatusReportBase(actionName, status, actionStartedAt, config, diskInfo, logger, cause, exception) { try { const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; - const ref = await getRef(); + const ref = await getRef(getEnv(), config?.repositoryRoot); const jobRunUUID = process.env["CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */] || ""; const workflowRunID = getWorkflowRunID(); const workflowRunAttempt = getWorkflowRunAttempt(); @@ -150095,7 +150099,7 @@ var CACHE_VERSION = 1; var CODEQL_TRAP_CACHE_PREFIX = "codeql-trap"; var MINIMUM_CACHE_MB_TO_UPLOAD = 10; var MAX_CACHE_OPERATION_MS2 = 12e4; -async function downloadTrapCaches(codeql, languages, logger) { +async function downloadTrapCaches(codeql, languages, logger, repositoryRoot) { const result = {}; const languagesSupportingCaching = await getLanguagesSupportingCaching( codeql, @@ -150115,7 +150119,7 @@ async function downloadTrapCaches(codeql, languages, logger) { fs9.mkdirSync(cacheDir2, { recursive: true }); result[language] = cacheDir2; } - if (await isAnalyzingDefaultBranch()) { + if (await isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) { logger.info( "Analyzing default branch. Skipping downloading of TRAP caches." ); @@ -150154,7 +150158,9 @@ async function downloadTrapCaches(codeql, languages, logger) { return result; } async function uploadTrapCaches(codeql, config, logger) { - if (!await isAnalyzingDefaultBranch()) return false; + if (!await isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot)) { + return false; + } for (const language of config.languages) { const cacheDir2 = config.trapCaches[language]; if (cacheDir2 === void 0) continue; @@ -150190,6 +150196,7 @@ async function uploadTrapCaches(codeql, config, logger) { return true; } async function cleanupTrapCaches(config, features, logger) { + const env = getEnv(); if (!await features.getValue("cleanup_trap_caches" /* CleanupTrapCaches */)) { return { trap_cache_cleanup_skipped_because: "feature disabled" @@ -150198,7 +150205,7 @@ async function cleanupTrapCaches(config, features, logger) { logger.warning( "TRAP cache cleanup is deprecated and will be removed in May 2026. We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action." ); - if (!await isAnalyzingDefaultBranch()) { + if (!await isAnalyzingDefaultBranch(env, config.repositoryRoot)) { return { trap_cache_cleanup_skipped_because: "not analyzing default branch" }; @@ -150207,7 +150214,7 @@ async function cleanupTrapCaches(config, features, logger) { let totalBytesCleanedUp = 0; const allCaches = await listActionsCaches( CODEQL_TRAP_CACHE_PREFIX, - await getRef() + await getRef(env, config.repositoryRoot) ); for (const language of config.languages) { if (config.trapCaches[language]) { @@ -150499,9 +150506,14 @@ async function initActionState({ enableFileCoverageInformation }; } -async function downloadCacheWithTime(codeQL, languages, logger) { +async function downloadCacheWithTime(codeQL, languages, logger, repositoryRoot) { const start = import_perf_hooks.performance.now(); - const trapCaches = await downloadTrapCaches(codeQL, languages, logger); + const trapCaches = await downloadTrapCaches( + codeQL, + languages, + logger, + repositoryRoot + ); const trapCacheDownloadTime = import_perf_hooks.performance.now() - start; return { trapCaches, trapCacheDownloadTime }; } @@ -150699,7 +150711,7 @@ async function checkOverlayEnablement(codeql, features, languages, repositoryRoo logger.info( `Setting overlay database mode to ${overlayDatabaseMode} with caching because we are analyzing a pull request.` ); - } else if (await isAnalyzingDefaultBranch()) { + } else if (await isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) { overlayDatabaseMode = "overlay-base" /* OverlayBase */; logger.info( `Setting overlay database mode to ${overlayDatabaseMode} with caching because we are analyzing the default branch.` @@ -150986,7 +150998,8 @@ async function initConfig(actionState, inputs) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( inputs.codeql, config.languages, - logger + logger, + repositoryRoot ); config.trapCaches = trapCaches; config.trapCacheDownloadTime = trapCacheDownloadTime; @@ -151433,7 +151446,7 @@ async function getCacheSaveKey(config, codeQlVersion, checkoutPath, logger) { `Failed to get workflow run ID or attempt ID. Reason: ${getErrorMessage(e)}` ); } - const sha = await getCommitOid(checkoutPath); + const sha = await getCommitOid(getEnv(), checkoutPath); const restoreKeyPrefix = await getCacheRestoreKeyPrefix( config, codeQlVersion @@ -153202,7 +153215,7 @@ async function getTrapCachingExtractorConfigArgs(config) { async function getTrapCachingExtractorConfigArgsForLang(config, language) { const cacheDir2 = config.trapCaches[language]; if (cacheDir2 === void 0) return []; - const write = await isAnalyzingDefaultBranch(); + const write = await isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot); return [ `-O=${language}.trap.cache.dir=${cacheDir2}`, `-O=${language}.trap.cache.bound=${TRAP_CACHE_SIZE_MB}`, @@ -154021,7 +154034,7 @@ async function cleanupAndUploadDatabases(action, repositoryNwo, codeql, config, logger.debug("Not running against github.com or GHEC-DR. Skipping upload."); return []; } - if (!await isAnalyzingDefaultBranch()) { + if (!await isAnalyzingDefaultBranch(action.env, checkoutPath)) { logger.debug("Not analyzing default branch. Skipping upload."); return []; } @@ -154038,7 +154051,7 @@ async function cleanupAndUploadDatabases(action, repositoryNwo, codeql, config, includeDiagnostics: false }); bundledDbSize = fs18.statSync(bundledDb).size; - const commitOid = await getCommitOid(checkoutPath); + const commitOid = await getCommitOid(action.env, checkoutPath); const maxAttempts = 4; let uploadDurationMs; for (let attempt = 1; attempt <= maxAttempts; attempt++) { @@ -156036,12 +156049,13 @@ async function uploadPostProcessedFiles(logger, checkoutPath, uploadTarget, post logger.debug(`Compressing serialized SARIF`); const zippedSarif = import_zlib.default.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; + const env = getEnv(); const payload = uploadTarget.transformPayload( buildPayload( - await getCommitOid(checkoutPath), - await getRef(), + await getCommitOid(env, checkoutPath), + await getRef(env, checkoutPath), postProcessingResults.analysisKey, - getRequiredEnvParam("GITHUB_WORKFLOW"), + env.getRequired("GITHUB_WORKFLOW" /* GITHUB_WORKFLOW */), zippedSarif, getWorkflowRunID(), getWorkflowRunAttempt(), @@ -156517,7 +156531,7 @@ async function run(action) { checkoutPath ); databaseUploadResults = await cleanupAndUploadDatabases( - { logger, features }, + { ...action, features }, repositoryNwo, codeql, config, @@ -162809,6 +162823,7 @@ async function removeUploadedSarif(uploadFailedSarifResult, logger) { // src/init-action-post.ts async function run4(startedAt) { const logger = getActionsLogger(); + const env = getEnv(); let config; let uploadFailedSarifResult; let dependencyCachingUsage; @@ -162839,10 +162854,10 @@ async function run4(startedAt) { repositoryNwo, features, jobStatus2, - getEnv(), + env, logger ); - if (await isAnalyzingDefaultBranch() && config.dependencyCachingEnabled !== "none" /* None */) { + if (await isAnalyzingDefaultBranch(env, config.repositoryRoot) && config.dependencyCachingEnabled !== "none" /* None */) { dependencyCachingUsage = await getDependencyCacheUsage(logger); } } diff --git a/src/actions-util.ts b/src/actions-util.ts index eb7d92b517..3061677107 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -7,7 +7,7 @@ import * as github from "@actions/github"; import * as io from "@actions/io"; import type { Config } from "./config-utils"; -import { Env, EnvVar, ActionsEnvVars } from "./environment"; +import { Env, EnvVar, ActionsEnvVars, ReadOnlyEnv } from "./environment"; import { Logger } from "./logging"; import { doesDirectoryExist, @@ -94,7 +94,7 @@ export function getActionVersion(): string { * * This will be "dynamic" for default setup workflow runs. */ -export function getWorkflowEventName(env: Env = getEnv()) { +export function getWorkflowEventName(env: ReadOnlyEnv = getEnv()) { return env.getRequired(ActionsEnvVars.GITHUB_EVENT_NAME); } @@ -121,7 +121,7 @@ function getRelativeScriptPath(env: Env): string { } /** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */ -export function getWorkflowEvent(env: Env = getEnv()): any { +export function getWorkflowEvent(env: ReadOnlyEnv = getEnv()): any { const eventJsonFile = env.getRequired(ActionsEnvVars.GITHUB_EVENT_PATH); try { return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8")); diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 8cc13c1ba0..25d62c2930 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -406,7 +406,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { // Note: Take care with the ordering of this call since databases may be cleaned up // at the `overlay` or `clear` level. databaseUploadResults = await cleanupAndUploadDatabases( - { logger, features }, + { ...action, features }, repositoryNwo, codeql, config, diff --git a/src/codeql.ts b/src/codeql.ts index 65e73d9451..66d387ba47 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -1212,7 +1212,7 @@ export async function getTrapCachingExtractorConfigArgsForLang( ): Promise { const cacheDir = config.trapCaches[language]; if (cacheDir === undefined) return []; - const write = await isAnalyzingDefaultBranch(); + const write = await isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot); return [ `-O=${language}.trap.cache.dir=${cacheDir}`, `-O=${language}.trap.cache.bound=${TRAP_CACHE_SIZE_MB}`, diff --git a/src/config-utils.ts b/src/config-utils.ts index 37337b68d3..3c4970c727 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -46,7 +46,7 @@ import { makeTelemetryDiagnostic, } from "./diagnostics"; import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils"; -import { EnvVar } from "./environment"; +import { EnvVar, getEnv } from "./environment"; import * as errorMessages from "./error-messages"; import { Feature, FeatureEnablement, FeatureWithoutCLI } from "./feature-flags"; import { @@ -467,12 +467,18 @@ async function downloadCacheWithTime( codeQL: CodeQL, languages: Language[], logger: Logger, + repositoryRoot: string | undefined, ): Promise<{ trapCaches: { [language: string]: string }; trapCacheDownloadTime: number; }> { const start = performance.now(); - const trapCaches = await downloadTrapCaches(codeQL, languages, logger); + const trapCaches = await downloadTrapCaches( + codeQL, + languages, + logger, + repositoryRoot, + ); const trapCacheDownloadTime = performance.now() - start; return { trapCaches, trapCacheDownloadTime }; } @@ -824,7 +830,7 @@ export async function checkOverlayEnablement( `Setting overlay database mode to ${overlayDatabaseMode} ` + "with caching because we are analyzing a pull request.", ); - } else if (await isAnalyzingDefaultBranch()) { + } else if (await isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) { overlayDatabaseMode = OverlayDatabaseMode.OverlayBase; logger.info( `Setting overlay database mode to ${overlayDatabaseMode} ` + @@ -1305,6 +1311,7 @@ export async function initConfig( inputs.codeql, config.languages, logger, + repositoryRoot, ); config.trapCaches = trapCaches; config.trapCacheDownloadTime = trapCacheDownloadTime; diff --git a/src/database-upload.test.ts b/src/database-upload.test.ts index 5b76658bf9..b6ac5c8115 100644 --- a/src/database-upload.test.ts +++ b/src/database-upload.test.ts @@ -20,8 +20,9 @@ import { checkExpectedLogMessages, createFeatures, createTestConfig, + getTestEnv, initAllState, - LoggedMessage, + RecordingLogger, setupActionsVars, setupTests, } from "./testing-utils"; @@ -90,23 +91,24 @@ test.serial( "Abort database upload if 'upload-database' input set to false", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") .returns("false"); sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Database upload disabled in workflow. Skipping upload.", ]); }); @@ -117,7 +119,8 @@ test.serial( "Abort database upload if 'analysis-kinds: code-scanning' is not enabled", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -126,9 +129,9 @@ test.serial( await mockHttpRequests(201); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), { @@ -138,7 +141,7 @@ test.serial( testApiDetails, "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Not uploading database because 'analysis-kinds: code-scanning' is not enabled.", ]); }); @@ -147,7 +150,8 @@ test.serial( test.serial("Abort database upload if running against GHES", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -157,16 +161,16 @@ test.serial("Abort database upload if running against GHES", async (t) => { const config = getTestConfig(tmpDir); config.gitHubVersion = { type: GitHubVariant.GHES, version: "3.0" }; - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), config, testApiDetails, "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Not running against github.com or GHEC-DR. Skipping upload.", ]); }); @@ -176,23 +180,24 @@ test.serial( "Abort database upload if not analyzing default branch", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") .returns("true"); sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Not analyzing default branch. Skipping upload.", ]); }); @@ -203,7 +208,8 @@ test.serial( "Don't crash if uploading a database fails with a non-retryable error", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -212,9 +218,9 @@ test.serial( const databaseUploadSpy = await mockHttpRequests(422); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), @@ -222,7 +228,7 @@ test.serial( "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Failed to upload database for javascript: some error message", ]); @@ -236,7 +242,8 @@ test.serial( "Don't crash if uploading a database fails with a retryable error", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -251,9 +258,9 @@ test.serial( .stub(global, "setTimeout") .callsFake((fn: () => void) => originalSetTimeout(fn, 0)); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), @@ -261,7 +268,7 @@ test.serial( "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Failed to upload database for javascript: some error message", ]); @@ -279,7 +286,8 @@ test.serial( test.serial("Successfully uploading a database to github.com", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -288,16 +296,16 @@ test.serial("Successfully uploading a database to github.com", async (t) => { await mockHttpRequests(201); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Successfully uploaded database for javascript", ]); }); @@ -305,7 +313,8 @@ test.serial("Successfully uploading a database to github.com", async (t) => { test.serial("Successfully uploading a database to GHEC-DR", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -314,9 +323,9 @@ test.serial("Successfully uploading a database to GHEC-DR", async (t) => { const databaseUploadSpy = await mockHttpRequests(201); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( - initAllState(), + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), @@ -327,7 +336,7 @@ test.serial("Successfully uploading a database to GHEC-DR", async (t) => { }, "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Successfully uploaded database for javascript", ]); t.assert( @@ -343,7 +352,8 @@ test.serial( "Records overlay and clear cleanup sizes when uploading an overlay-base database", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -377,6 +387,7 @@ test.serial( const results = await cleanupAndUploadDatabases( initAllState({ + env, features: createFeatures([Feature.UploadOverlayDbToApi]), }), testRepoName, @@ -403,7 +414,8 @@ test.serial( "Does not measure clear cleanup size for a regular (non-overlay-base) upload", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -424,6 +436,7 @@ test.serial( const results = await cleanupAndUploadDatabases( initAllState({ + env, features: createFeatures([Feature.UploadOverlayDbToApi]), }), testRepoName, @@ -444,7 +457,8 @@ test.serial( test.serial("Does not measure clear cleanup size in debug mode", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -469,6 +483,7 @@ test.serial("Does not measure clear cleanup size in debug mode", async (t) => { const results = await cleanupAndUploadDatabases( initAllState({ + env, features: createFeatures([Feature.UploadOverlayDbToApi]), }), testRepoName, @@ -491,7 +506,8 @@ test.serial( "Does not record a clear cleanup duration when the clear cleanup fails", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -516,6 +532,7 @@ test.serial( const results = await cleanupAndUploadDatabases( initAllState({ + env, features: createFeatures([Feature.UploadOverlayDbToApi]), }), testRepoName, diff --git a/src/database-upload.ts b/src/database-upload.ts index 9e4339fd47..35dfd90785 100644 --- a/src/database-upload.ts +++ b/src/database-upload.ts @@ -46,7 +46,7 @@ export interface DatabaseUploadResult { } export async function cleanupAndUploadDatabases( - action: ActionState<["Logger", "FeatureFlags"]>, + action: ActionState<["ReadOnlyEnv", "Logger", "FeatureFlags"]>, repositoryNwo: RepositoryNwo, codeql: CodeQL, config: Config, @@ -81,7 +81,7 @@ export async function cleanupAndUploadDatabases( return []; } - if (!(await gitUtils.isAnalyzingDefaultBranch())) { + if (!(await gitUtils.isAnalyzingDefaultBranch(action.env, checkoutPath))) { // We only want to upload a database if we are analyzing the default branch. logger.debug("Not analyzing default branch. Skipping upload."); return []; @@ -113,7 +113,7 @@ export async function cleanupAndUploadDatabases( includeDiagnostics: false, }); bundledDbSize = fs.statSync(bundledDb).size; - const commitOid = await gitUtils.getCommitOid(checkoutPath); + const commitOid = await gitUtils.getCommitOid(action.env, checkoutPath); // Upload with manual retry logic. We disable Octokit's built-in retries // because the request body is a ReadStream, which can only be consumed // once. diff --git a/src/environment.ts b/src/environment.ts index bf4bb4f717..90a9d43588 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -307,6 +307,13 @@ export class Env< this.changed = true; } + /** Sets all environment variables given by `vars`. */ + public setAll(vars: Record): void { + for (const [key, val] of Object.entries(vars)) { + this.set(key, val); + } + } + /** Gets a value indicating whether `set` was called at least once. */ public hasChanged(): boolean { return this.changed; diff --git a/src/git-utils.test.ts b/src/git-utils.test.ts index b77d40a7ec..8679f49a77 100644 --- a/src/git-utils.test.ts +++ b/src/git-utils.test.ts @@ -7,31 +7,36 @@ import test from "ava"; import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; +import { ActionsEnvVars, EnvVar } from "./environment"; import * as gitUtils from "./git-utils"; -import { setupActionsVars, setupTests } from "./testing-utils"; +import { getTestEnv, setupActionsVars, setupTests } from "./testing-utils"; import { withTmpDir } from "./util"; setupTests(test); -test.serial("getRef() throws on the empty string", async (t) => { - process.env["GITHUB_REF"] = ""; - await t.throwsAsync(gitUtils.getRef); +test("getRef() throws on the empty string", async (t) => { + const env = getTestEnv({ [ActionsEnvVars.GITHUB_REF]: "" }); + await t.throwsAsync(() => gitUtils.getRef(env, "")); }); test.serial( "getRef() returns merge PR ref if GITHUB_SHA still checked out", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); const expectedRef = "refs/pull/1/merge"; const currentSha = "a".repeat(40); - process.env["GITHUB_REF"] = expectedRef; - process.env["GITHUB_SHA"] = currentSha; + + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [ActionsEnvVars.GITHUB_REF]: expectedRef, + [ActionsEnvVars.GITHUB_SHA]: currentSha, + }); const callback = sinon.stub(gitUtils, "getCommitOid"); - callback.withArgs("HEAD").resolves(currentSha); + callback.withArgs(sinon.match.any, "HEAD").resolves(currentSha); - const actualRef = await gitUtils.getRef(); + const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, expectedRef); }); }, @@ -41,17 +46,22 @@ test.serial( "getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); const expectedRef = "refs/pull/1/merge"; - process.env["GITHUB_REF"] = expectedRef; - process.env["GITHUB_SHA"] = "b".repeat(40); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [ActionsEnvVars.GITHUB_REF]: expectedRef, + [ActionsEnvVars.GITHUB_SHA]: "b".repeat(40), + }); const sha = "a".repeat(40); const callback = sinon.stub(gitUtils, "getCommitOid"); - callback.withArgs("refs/remotes/pull/1/merge").resolves(sha); - callback.withArgs("HEAD").resolves(sha); + callback + .withArgs(sinon.match.any, "refs/remotes/pull/1/merge") + .resolves(sha); + callback.withArgs(sinon.match.any, "HEAD").resolves(sha); - const actualRef = await gitUtils.getRef(); + const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, expectedRef); }); }, @@ -61,15 +71,22 @@ test.serial( "getRef() returns head PR ref if GITHUB_REF no longer checked out", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); - process.env["GITHUB_REF"] = "refs/pull/1/merge"; - process.env["GITHUB_SHA"] = "a".repeat(40); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge", + [ActionsEnvVars.GITHUB_SHA]: "a".repeat(40), + }); const callback = sinon.stub(gitUtils, "getCommitOid"); - callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("a".repeat(40)); - callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40)); - - const actualRef = await gitUtils.getRef(); + callback + .withArgs(sinon.match.any, tmpDir, "refs/pull/1/merge") + .resolves("a".repeat(40)); + callback + .withArgs(sinon.match.any, tmpDir, "HEAD") + .resolves("b".repeat(40)); + + const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, "refs/pull/1/head"); }); }, @@ -79,7 +96,6 @@ test.serial( "getRef() returns ref provided as an input and ignores current HEAD", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); const getAdditionalInputStub = sinon.stub( actionsUtil, "getOptionalInput", @@ -88,14 +104,20 @@ test.serial( getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40)); // These values are be ignored - process.env["GITHUB_REF"] = "refs/pull/1/merge"; - process.env["GITHUB_SHA"] = "a".repeat(40); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge", + [ActionsEnvVars.GITHUB_SHA]: "a".repeat(40), + }); const callback = sinon.stub(gitUtils, "getCommitOid"); - callback.withArgs("refs/pull/1/merge").resolves("b".repeat(40)); - callback.withArgs("HEAD").resolves("b".repeat(40)); + callback + .withArgs(sinon.match.any, "refs/pull/1/merge") + .resolves("b".repeat(40)); + callback.withArgs(sinon.match.any, "HEAD").resolves("b".repeat(40)); - const actualRef = await gitUtils.getRef(); + const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, "refs/pull/2/merge"); }); }, @@ -105,14 +127,17 @@ test.serial( "getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); const expectedRef = "refs/pull/1/HEAD"; const currentSha = "a".repeat(40); - process.env["CODE_SCANNING_REF"] = expectedRef; - process.env["GITHUB_REF"] = ""; - process.env["GITHUB_SHA"] = currentSha; + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [EnvVar.CODE_SCANNING_REF]: expectedRef, + [ActionsEnvVars.GITHUB_REF]: "", + [ActionsEnvVars.GITHUB_SHA]: currentSha, + }); - const actualRef = await gitUtils.getRef(); + const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, expectedRef); }); }, @@ -122,14 +147,17 @@ test.serial( "getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); const expectedRef = "refs/pull/1/merge"; const currentSha = "a".repeat(40); - process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD"; - process.env["GITHUB_REF"] = expectedRef; - process.env["GITHUB_SHA"] = currentSha; + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [EnvVar.CODE_SCANNING_REF]: "refs/pull/1/HEAD", + [ActionsEnvVars.GITHUB_REF]: expectedRef, + [ActionsEnvVars.GITHUB_SHA]: currentSha, + }); - const actualRef = await gitUtils.getRef(); + const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, expectedRef); }); }, @@ -139,7 +167,9 @@ test.serial( "getRef() throws an error if only `ref` is provided as an input", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + const getAdditionalInputStub = sinon.stub( actionsUtil, "getOptionalInput", @@ -148,7 +178,7 @@ test.serial( await t.throwsAsync( async () => { - await gitUtils.getRef(); + await gitUtils.getRef(env, tmpDir); }, { instanceOf: Error, @@ -164,8 +194,12 @@ test.serial( "getRef() throws an error if only `sha` is provided as an input", async (t) => { await withTmpDir(async (tmpDir: string) => { - setupActionsVars(tmpDir, tmpDir); - process.env["GITHUB_WORKSPACE"] = "/tmp"; + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); + env.setAll({ + [ActionsEnvVars.GITHUB_WORKSPACE]: "/tmp", + }); + const getAdditionalInputStub = sinon.stub( actionsUtil, "getOptionalInput", @@ -174,7 +208,7 @@ test.serial( await t.throwsAsync( async () => { - await gitUtils.getRef(); + await gitUtils.getRef(env, tmpDir); }, { instanceOf: Error, @@ -187,13 +221,16 @@ test.serial( ); test.serial("isAnalyzingDefaultBranch()", async (t) => { - process.env["GITHUB_EVENT_NAME"] = "push"; - process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true"; - t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true); - process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "false"; + const env = getTestEnv({ + [ActionsEnvVars.GITHUB_EVENT_NAME]: "push", + CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH: "true", + }); + t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, ""), true); + + env.set("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH", "false"); await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + setupActionsVars(tmpDir, tmpDir, {}, env); const envFile = path.join(tmpDir, "event.json"); fs.writeFileSync( envFile, @@ -203,17 +240,17 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => { }, }), ); - process.env["GITHUB_EVENT_PATH"] = envFile; + env.set(ActionsEnvVars.GITHUB_EVENT_PATH, envFile); - process.env["GITHUB_REF"] = "main"; - process.env["GITHUB_SHA"] = "1234"; - t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true); + env.set(ActionsEnvVars.GITHUB_REF, "main"); + env.set(ActionsEnvVars.GITHUB_SHA, "1234"); + t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true); - process.env["GITHUB_REF"] = "refs/heads/main"; - t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true); + env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main"); + t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true); - process.env["GITHUB_REF"] = "feature"; - t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false); + env.set(ActionsEnvVars.GITHUB_REF, "feature"); + t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false); fs.writeFileSync( envFile, @@ -221,9 +258,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => { schedule: "0 0 * * *", }), ); - process.env["GITHUB_EVENT_NAME"] = "schedule"; - process.env["GITHUB_REF"] = "refs/heads/main"; - t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true); + env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule"); + env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main"); + t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true); const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput"); getAdditionalInputStub @@ -232,9 +269,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => { getAdditionalInputStub .withArgs("sha") .resolves("0000000000000000000000000000000000000000"); - process.env["GITHUB_EVENT_NAME"] = "schedule"; - process.env["GITHUB_REF"] = "refs/heads/main"; - t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false); + env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule"); + env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main"); + t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false); }); }); diff --git a/src/git-utils.ts b/src/git-utils.ts index 1bca07eb7d..2e042e361a 100644 --- a/src/git-utils.ts +++ b/src/git-utils.ts @@ -13,7 +13,7 @@ import { getWorkflowEvent, getWorkflowEventName, } from "./actions-util"; -import { ActionsEnvVars, getEnv, type ReadOnlyEnv } from "./environment"; +import { ActionsEnvVars, EnvVar, type ReadOnlyEnv } from "./environment"; import { ConfigurationError, getRequiredEnvParam } from "./util"; /** @@ -102,6 +102,7 @@ export const runGitCommand = async function ( * Gets the SHA of the commit that is currently checked out. */ export const getCommitOid = async function ( + env: ReadOnlyEnv, checkoutPath: string, ref = "HEAD", ): Promise { @@ -120,7 +121,9 @@ export const getCommitOid = async function ( ); return stdout.trim(); } catch { - return getOptionalInput("sha") || getRequiredEnvParam("GITHUB_SHA"); + return ( + getOptionalInput("sha") || env.getRequired(ActionsEnvVars.GITHUB_SHA) + ); } }; @@ -315,18 +318,18 @@ export const getFileOidsUnderPath = async function ( return fileOidMap; }; -function getRefFromEnv(): string { +function getRefFromEnv(env: ReadOnlyEnv): string { // To workaround a limitation of Actions dynamic workflows not setting // the GITHUB_REF in some cases, we accept also the ref within the // CODE_SCANNING_REF variable. When possible, however, we prefer to use // the GITHUB_REF as that is a protected variable and cannot be overwritten. let refEnv: string; try { - refEnv = getRequiredEnvParam("GITHUB_REF"); + refEnv = env.getRequired(ActionsEnvVars.GITHUB_REF); } catch (e) { // If the GITHUB_REF is not set, we try to rescue by getting the // CODE_SCANNING_REF. - const maybeRef = process.env["CODE_SCANNING_REF"]; + const maybeRef = env.getOptional(EnvVar.CODE_SCANNING_REF); if (maybeRef === undefined || maybeRef.length === 0) { throw e; } @@ -335,27 +338,19 @@ function getRefFromEnv(): string { return refEnv; } -/** - * Gets the path at which the repository is checked out at. In order of preference, this is determined by: - * the `checkout_path` input, the `source-root` input, the `GITHUB_WORKSPACE` environment variable. - */ -export function getCheckoutPath(env: ReadOnlyEnv) { - return ( - getOptionalInput("checkout_path") || - getOptionalInput("source-root") || - env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE) - ); -} - /** * Get the ref currently being analyzed. */ -export async function getRef(env: ReadOnlyEnv = getEnv()): Promise { +export async function getRef( + env: ReadOnlyEnv, + checkoutPath: string | undefined, +): Promise { // Will be in the form "refs/heads/master" on a push event // or in the form "refs/pull/N/merge" on a pull_request event const refInput = getOptionalInput("ref"); const shaInput = getOptionalInput("sha"); - const checkoutPath = getCheckoutPath(env); + checkoutPath = + checkoutPath ?? env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE); const hasRefInput = !!refInput; const hasShaInput = !!shaInput; @@ -366,7 +361,7 @@ export async function getRef(env: ReadOnlyEnv = getEnv()): Promise { ); } - const ref = refInput || getRefFromEnv(); + const ref = refInput || getRefFromEnv(env); const sha = shaInput || env.getRequired(ActionsEnvVars.GITHUB_SHA); // If the ref is a user-provided input, we have to skip logic @@ -384,7 +379,7 @@ export async function getRef(env: ReadOnlyEnv = getEnv()): Promise { return ref; } - const head = await getCommitOid(checkoutPath, "HEAD"); + const head = await getCommitOid(env, checkoutPath, "HEAD"); // in actions/checkout@v2+ we can check if git rev-parse HEAD == GITHUB_SHA // in actions/checkout@v1 this may not be true as it checks out the repository @@ -394,6 +389,7 @@ export async function getRef(env: ReadOnlyEnv = getEnv()): Promise { const hasChangedRef = sha !== head && (await getCommitOid( + env, checkoutPath, ref.replace(/^refs\/pull\//, "refs/remotes/pull/"), )) !== head; @@ -420,20 +416,23 @@ function removeRefsHeadsPrefix(ref: string): string { * environment variable can be set in cases where repository information might not be available, for * example dynamic workflows. */ -export async function isAnalyzingDefaultBranch(): Promise { - if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") { +export async function isAnalyzingDefaultBranch( + env: ReadOnlyEnv, + checkoutPath: string | undefined, +): Promise { + if (env.getOptional("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH") === "true") { return true; } // Get the current ref and trim and refs/heads/ prefix - let currentRef = await getRef(); + let currentRef = await getRef(env, checkoutPath); currentRef = removeRefsHeadsPrefix(currentRef); - const event = getWorkflowEvent(); + const event = getWorkflowEvent(env); let defaultBranch = event?.repository?.default_branch; - if (getWorkflowEventName() === "schedule") { - defaultBranch = removeRefsHeadsPrefix(getRefFromEnv()); + if (getWorkflowEventName(env) === "schedule") { + defaultBranch = removeRefsHeadsPrefix(getRefFromEnv(env)); } return currentRef === defaultBranch; diff --git a/src/init-action-post.ts b/src/init-action-post.ts index 749020ac64..985538b8a9 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -50,6 +50,7 @@ async function run(startedAt: Date) { // possible, and only use safe functions outside. const logger = getActionsLogger(); + const env = getEnv(); let config: Config | undefined; let uploadFailedSarifResult: | initActionPostHelper.UploadFailedSarifResult @@ -91,7 +92,7 @@ async function run(startedAt: Date) { repositoryNwo, features, jobStatus, - getEnv(), + env, logger, ); @@ -100,7 +101,7 @@ async function run(startedAt: Date) { // do this under these circumstances to avoid slowing down analyses for PRs // and where caching may not be enabled. if ( - (await gitUtils.isAnalyzingDefaultBranch()) && + (await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot)) && config.dependencyCachingEnabled !== CachingKind.None ) { dependencyCachingUsage = await getDependencyCacheUsage(logger); diff --git a/src/overlay/caching.ts b/src/overlay/caching.ts index d246626780..9b5906dde3 100644 --- a/src/overlay/caching.ts +++ b/src/overlay/caching.ts @@ -15,6 +15,7 @@ import { CleanupLevel, getBaseDatabaseOidsFilePath, getCodeQLDatabasePath, + getEnv, getErrorMessage, isInTestMode, tryGetFolderBytes, @@ -377,7 +378,7 @@ export async function getCacheSaveKey( `Failed to get workflow run ID or attempt ID. Reason: ${getErrorMessage(e)}`, ); } - const sha = await getCommitOid(checkoutPath); + const sha = await getCommitOid(getEnv(), checkoutPath); const restoreKeyPrefix = await getCacheRestoreKeyPrefix( config, codeQlVersion, diff --git a/src/status-report.ts b/src/status-report.ts index a2acd631d6..c23bf4c818 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -366,7 +366,7 @@ export async function createStatusReportBase( try { const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; - const ref = await getRef(); + const ref = await getRef(getEnv(), config?.repositoryRoot); const jobRunUUID = process.env[EnvVar.JOB_RUN_UUID] || ""; const workflowRunID = getWorkflowRunID(); const workflowRunAttempt = getWorkflowRunAttempt(); diff --git a/src/trap-caching.test.ts b/src/trap-caching.test.ts index 478305e577..15ab494812 100644 --- a/src/trap-caching.test.ts +++ b/src/trap-caching.test.ts @@ -182,6 +182,7 @@ test.serial( stubCodeql, [BuiltInLanguage.javascript, BuiltInLanguage.cpp], logger, + undefined, ); t.assert( stubRestore.calledOnceWith( diff --git a/src/trap-caching.ts b/src/trap-caching.ts index a802aac892..fbcd9951b9 100644 --- a/src/trap-caching.ts +++ b/src/trap-caching.ts @@ -14,6 +14,7 @@ import { Language } from "./languages"; import { Logger } from "./logging"; import { asHTTPError, + getEnv, getErrorMessage, tryGetFolderBytes, waitForResultWithTimeLimit, @@ -43,6 +44,7 @@ const MAX_CACHE_OPERATION_MS = 120_000; // Two minutes * @param codeql The CodeQL instance to use. * @param languages The languages being analyzed. * @param logger A logger to record some informational messages to. + * @param repositoryRoot The path at which the repository is checked out at. * @returns A partial map from languages to TRAP cache paths on disk, with * languages for which we shouldn't use TRAP caching omitted. */ @@ -50,6 +52,7 @@ export async function downloadTrapCaches( codeql: CodeQL, languages: Language[], logger: Logger, + repositoryRoot: string | undefined, ): Promise<{ [language: string]: string }> { const result: { [language: string]: string } = {}; const languagesSupportingCaching = await getLanguagesSupportingCaching( @@ -72,7 +75,7 @@ export async function downloadTrapCaches( result[language] = cacheDir; } - if (await gitUtils.isAnalyzingDefaultBranch()) { + if (await gitUtils.isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) { logger.info( "Analyzing default branch. Skipping downloading of TRAP caches.", ); @@ -132,7 +135,12 @@ export async function uploadTrapCaches( config: Config, logger: Logger, ): Promise { - if (!(await gitUtils.isAnalyzingDefaultBranch())) return false; // Only upload caches from the default branch + // Only upload caches from the default branch + if ( + !(await gitUtils.isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot)) + ) { + return false; + } for (const language of config.languages) { const cacheDir = config.trapCaches[language]; @@ -180,6 +188,8 @@ export async function cleanupTrapCaches( features: FeatureEnablement, logger: Logger, ): Promise { + const env = getEnv(); + if (!(await features.getValue(Feature.CleanupTrapCaches))) { return { trap_cache_cleanup_skipped_because: "feature disabled", @@ -189,7 +199,7 @@ export async function cleanupTrapCaches( "TRAP cache cleanup is deprecated and will be removed in May 2026. " + "We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action.", ); - if (!(await gitUtils.isAnalyzingDefaultBranch())) { + if (!(await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot))) { return { trap_cache_cleanup_skipped_because: "not analyzing default branch", }; @@ -200,7 +210,7 @@ export async function cleanupTrapCaches( const allCaches = await apiClient.listActionsCaches( CODEQL_TRAP_CACHE_PREFIX, - await gitUtils.getRef(), + await gitUtils.getRef(env, config.repositoryRoot), ); for (const language of config.languages) { diff --git a/src/upload-lib.ts b/src/upload-lib.ts index da5552cf24..e323ae73f4 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -14,7 +14,7 @@ import { getGitHubVersion, wrapApiConfigurationError } from "./api-client"; import { CodeQL, getCodeQL } from "./codeql"; import { getConfig } from "./config-utils"; import { readDiffRangesJsonFile } from "./diff-informed-analysis-utils"; -import { EnvVar } from "./environment"; +import { ActionsEnvVars, EnvVar } from "./environment"; import { FeatureEnablement } from "./feature-flags"; import * as fingerprints from "./fingerprints"; import * as gitUtils from "./git-utils"; @@ -761,12 +761,13 @@ export async function uploadPostProcessedFiles( const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; + const env = util.getEnv(); const payload = uploadTarget.transformPayload( buildPayload( - await gitUtils.getCommitOid(checkoutPath), - await gitUtils.getRef(), + await gitUtils.getCommitOid(env, checkoutPath), + await gitUtils.getRef(env, checkoutPath), postProcessingResults.analysisKey, - util.getRequiredEnvParam("GITHUB_WORKFLOW"), + env.getRequired(ActionsEnvVars.GITHUB_WORKFLOW), zippedSarif, actionsUtil.getWorkflowRunID(), actionsUtil.getWorkflowRunAttempt(),