Propagate checkoutPath to getRef

This commit is contained in:
Michael B. Gale
2026-09-16 12:21:40 +01:00
parent ba615b5d9d
commit 54a43494ca
15 changed files with 229 additions and 137 deletions

90
lib/entry-points.js generated
View File

@@ -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;
@@ -147019,7 +147025,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,
@@ -147028,7 +147034,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) {
@@ -147138,12 +147144,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;
}
@@ -147151,10 +147157,10 @@ function getRefFromEnv() {
}
return refEnv;
}
async function getRef() {
async function getRef(env, checkoutPath) {
const refInput = getOptionalInput("ref");
const shaInput = getOptionalInput("sha");
const checkoutPath = getOptionalInput("checkout_path") || getOptionalInput("source-root") || getRequiredEnvParam("GITHUB_WORKSPACE");
checkoutPath = checkoutPath ?? env.getRequired("GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */);
const hasRefInput = !!refInput;
const hasShaInput = !!shaInput;
if ((hasRefInput || hasShaInput) && !(hasRefInput && hasShaInput)) {
@@ -147162,8 +147168,8 @@ async function getRef() {
"Both 'ref' and 'sha' are required if one of them is provided."
);
}
const ref = refInput || getRefFromEnv();
const sha = shaInput || getRequiredEnvParam("GITHUB_SHA");
const ref = refInput || getRefFromEnv(env);
const sha = shaInput || env.getRequired("GITHUB_SHA" /* GITHUB_SHA */);
if (refInput) {
return refInput;
}
@@ -147171,8 +147177,9 @@ async function getRef() {
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;
@@ -147189,16 +147196,16 @@ async function getRef() {
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;
}
@@ -147470,7 +147477,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();
@@ -150101,7 +150108,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,
@@ -150121,7 +150128,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."
);
@@ -150160,7 +150167,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;
@@ -150196,6 +150205,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"
@@ -150204,7 +150214,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"
};
@@ -150213,7 +150223,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]) {
@@ -150505,9 +150515,14 @@ async function initActionState({
enableFileCoverageInformation
};
}
async function downloadCacheWithTime(codeQL, languages, logger) {
async function downloadCacheWithTime(codeQL, languages, logger, repositoryRoot) {
const start = import_perf_hooks2.performance.now();
const trapCaches = await downloadTrapCaches(codeQL, languages, logger);
const trapCaches = await downloadTrapCaches(
codeQL,
languages,
logger,
repositoryRoot
);
const trapCacheDownloadTime = import_perf_hooks2.performance.now() - start;
return { trapCaches, trapCacheDownloadTime };
}
@@ -150705,7 +150720,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.`
@@ -150992,7 +151007,8 @@ async function initConfig(actionState, inputs) {
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
inputs.codeql,
config.languages,
logger
logger,
repositoryRoot
);
config.trapCaches = trapCaches;
config.trapCacheDownloadTime = trapCacheDownloadTime;
@@ -151455,7 +151471,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
@@ -153408,7 +153424,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}`,
@@ -154227,7 +154243,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 [];
}
@@ -154244,7 +154260,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++) {
@@ -156242,12 +156258,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(),
@@ -156723,7 +156740,7 @@ async function run(action) {
checkoutPath
);
databaseUploadResults = await cleanupAndUploadDatabases(
{ logger, features },
{ ...action, features },
repositoryNwo,
codeql,
config,
@@ -163024,6 +163041,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;
@@ -163054,10 +163072,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);
}
}

View File

@@ -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"));

View File

@@ -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,

View File

@@ -1212,7 +1212,7 @@ export async function getTrapCachingExtractorConfigArgsForLang(
): Promise<string[]> {
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}`,

View File

@@ -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;

View File

@@ -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.

View File

@@ -307,6 +307,13 @@ export class Env<
this.changed = true;
}
/** Sets all environment variables given by `vars`. */
public setAll(vars: Record<string, T>): 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;

View File

@@ -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));
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();
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);
});
});

View File

@@ -13,6 +13,7 @@ import {
getWorkflowEvent,
getWorkflowEventName,
} from "./actions-util";
import { ActionsEnvVars, EnvVar, type ReadOnlyEnv } from "./environment";
import { ConfigurationError, getRequiredEnvParam } from "./util";
/**
@@ -101,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<string> {
@@ -119,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)
);
}
};
@@ -314,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;
}
@@ -337,15 +341,16 @@ function getRefFromEnv(): string {
/**
* Get the ref currently being analyzed.
*/
export async function getRef(): Promise<string> {
export async function getRef(
env: ReadOnlyEnv,
checkoutPath: string | undefined,
): Promise<string> {
// 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");
checkoutPath =
checkoutPath ?? env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE);
const hasRefInput = !!refInput;
const hasShaInput = !!shaInput;
@@ -356,8 +361,8 @@ export async function getRef(): Promise<string> {
);
}
const ref = refInput || getRefFromEnv();
const sha = shaInput || getRequiredEnvParam("GITHUB_SHA");
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
// and assume that it is really where they want to upload the results.
@@ -374,7 +379,7 @@ export async function getRef(): Promise<string> {
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
@@ -384,6 +389,7 @@ export async function getRef(): Promise<string> {
const hasChangedRef =
sha !== head &&
(await getCommitOid(
env,
checkoutPath,
ref.replace(/^refs\/pull\//, "refs/remotes/pull/"),
)) !== head;
@@ -410,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<boolean> {
if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") {
export async function isAnalyzingDefaultBranch(
env: ReadOnlyEnv,
checkoutPath: string | undefined,
): Promise<boolean> {
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;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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();

View File

@@ -182,6 +182,7 @@ test.serial(
stubCodeql,
[BuiltInLanguage.javascript, BuiltInLanguage.cpp],
logger,
undefined,
);
t.assert(
stubRestore.calledOnceWith(

View File

@@ -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<boolean> {
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<TrapCacheCleanupStatusReport> {
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) {

View File

@@ -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(),