refactor: fall back to non-overlay analysis when diff-informed analysis is unavailable

This commit is contained in:
Sam Robson
2026-04-14 09:09:52 +01:00
parent 65216971a1
commit 3d50556531
5 changed files with 268 additions and 95 deletions
+59 -37
View File
@@ -106110,9 +106110,6 @@ function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) {
}
// src/diff-informed-analysis-utils.ts
async function shouldPerformDiffInformedAnalysis(codeql, features, logger) {
return await getDiffInformedAnalysisBranches(codeql, features, logger) !== void 0;
}
async function getDiffInformedAnalysisBranches(codeql, features, logger) {
if (!await features.getValue("diff_informed_queries" /* DiffInformedQueries */, codeql)) {
return void 0;
@@ -106129,6 +106126,37 @@ async function getDiffInformedAnalysisBranches(codeql, features, logger) {
}
return branches;
}
async function prepareDiffInformedAnalysis(codeql, features, logger) {
try {
const branches = await getDiffInformedAnalysisBranches(
codeql,
features,
logger
);
if (!branches) {
return { shouldRun: false, isAvailable: false };
}
const isAvailable = await withGroupAsync(
"Computing PR diff ranges",
async () => {
try {
return await computeAndPersistDiffRanges(branches, logger);
} catch (e) {
logger.warning(
`Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`
);
return false;
}
}
);
return { shouldRun: true, isAvailable };
} catch (e) {
logger.warning(
`Failed to determine diff-informed analysis availability: ${getErrorMessage(e)}`
);
return { shouldRun: true, isAvailable: false };
}
}
function writeDiffRangesJsonFile(logger, ranges) {
const jsonContents = JSON.stringify(ranges, null, 2);
const jsonFilePath = getDiffRangesJsonFilePath();
@@ -106159,6 +106187,18 @@ async function getPullRequestEditedDiffRanges(branches, logger) {
}
return results;
}
async function computeAndPersistDiffRanges(branches, logger) {
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
if (ranges === void 0) {
return false;
}
writeDiffRangesJsonFile(logger, ranges);
const distinctFiles = new Set(ranges.map((r) => r.path)).size;
logger.info(
`Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`
);
return true;
}
async function getFileDiffsWithBasehead(branches, logger) {
const repositoryNwo = getRepositoryNwoFromEnv(
"CODE_SCANNING_REPOSITORY",
@@ -106974,6 +107014,19 @@ function userConfigFromActionPath(tempDir) {
function hasQueryCustomisation(userConfig) {
return isDefined2(userConfig["disable-default-queries"]) || isDefined2(userConfig.queries) || isDefined2(userConfig["query-filters"]);
}
function applyIncrementalAnalysisSettings(config, diffInformedAnalysis, logger) {
if (config.overlayDatabaseMode === "overlay" /* Overlay */ && diffInformedAnalysis.shouldRun && !diffInformedAnalysis.isAvailable) {
logger.warning(
`Diff-informed analysis is not available for this pull request. Reverting overlay database mode to ${"none" /* None */}.`
);
config.overlayDatabaseMode = "none" /* None */;
}
if (config.overlayDatabaseMode === "overlay" /* Overlay */ || diffInformedAnalysis.isAvailable) {
config.extraQueryExclusions.push({
exclude: { tags: "exclude-from-incremental" }
});
}
}
async function initConfig(features, inputs) {
const { logger, tempDir } = inputs;
if (inputs.configInput) {
@@ -107083,15 +107136,12 @@ async function initConfig(features, inputs) {
overlayDisabledReason
);
}
if (config.overlayDatabaseMode === "overlay" /* Overlay */ || await shouldPerformDiffInformedAnalysis(
const diffInformedAnalysis = await prepareDiffInformedAnalysis(
inputs.codeql,
inputs.features,
logger
)) {
config.extraQueryExclusions.push({
exclude: { tags: "exclude-from-incremental" }
});
}
);
applyIncrementalAnalysisSettings(config, diffInformedAnalysis, logger);
if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) {
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
inputs.codeql,
@@ -110359,7 +110409,6 @@ async function run(startedAt) {
logFileCoverageOnPrsDeprecationWarning(logger);
}
await checkInstallPython311(config.languages, codeql);
await computeAndPersistDiffRanges(codeql, features, logger);
} catch (unwrappedError) {
const error3 = wrapError(unwrappedError);
core15.setFailed(error3.message);
@@ -110627,33 +110676,6 @@ async function loadRepositoryProperties(repositoryNwo, logger) {
return new Failure(error3);
}
}
async function computeAndPersistDiffRanges(codeql, features, logger) {
await withGroupAsync("Computing PR diff ranges", async () => {
try {
const branches = await getDiffInformedAnalysisBranches(
codeql,
features,
logger
);
if (!branches) {
return;
}
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
if (ranges === void 0) {
return;
}
writeDiffRangesJsonFile(logger, ranges);
const distinctFiles = new Set(ranges.map((r) => r.path)).size;
logger.info(
`Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`
);
} catch (e) {
logger.warning(
`Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}`
);
}
});
}
async function recordZstdAvailability(config, zstdAvailability) {
addNoLanguageDiagnostic(
config,
+77
View File
@@ -2200,3 +2200,80 @@ test.serial(
});
},
);
test.serial(
"applyIncrementalAnalysisSettings: no-op when mode is not Overlay and diff-informed is unavailable",
(t) => {
const config = createTestConfig({});
config.overlayDatabaseMode = OverlayDatabaseMode.None;
const logger = getRunnerLogger(true);
configUtils.applyIncrementalAnalysisSettings(
config,
{ shouldRun: false, isAvailable: false },
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None);
t.deepEqual(config.extraQueryExclusions, []);
},
);
test.serial(
"applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions when diff-informed analysis is disabled",
(t) => {
const config = createTestConfig({});
config.overlayDatabaseMode = OverlayDatabaseMode.Overlay;
const logger = getRunnerLogger(true);
configUtils.applyIncrementalAnalysisSettings(
config,
{ shouldRun: false, isAvailable: false },
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.Overlay);
t.deepEqual(config.extraQueryExclusions, [
{ exclude: { tags: "exclude-from-incremental" } },
]);
},
);
test.serial(
"applyIncrementalAnalysisSettings: reverts to None without exclusions when diff-informed analysis is unavailable",
(t) => {
const config = createTestConfig({});
config.overlayDatabaseMode =
OverlayDatabaseMode.Overlay as OverlayDatabaseMode;
const logger = getRunnerLogger(true);
configUtils.applyIncrementalAnalysisSettings(
config,
{ shouldRun: true, isAvailable: false },
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None);
t.deepEqual(config.extraQueryExclusions, []);
},
);
test.serial(
"applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only runs",
(t) => {
const config = createTestConfig({});
config.overlayDatabaseMode = OverlayDatabaseMode.None;
const logger = getRunnerLogger(true);
configUtils.applyIncrementalAnalysisSettings(
config,
{ shouldRun: true, isAvailable: true },
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None);
t.deepEqual(config.extraQueryExclusions, [
{ exclude: { tags: "exclude-from-incremental" } },
]);
},
);
+47 -13
View File
@@ -31,7 +31,10 @@ import {
addNoLanguageDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils";
import {
type DiffInformedAnalysisPreparation,
prepareDiffInformedAnalysis,
} from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import * as errorMessages from "./error-messages";
import { Feature, FeatureEnablement } from "./feature-flags";
@@ -1076,6 +1079,42 @@ function hasQueryCustomisation(userConfig: UserConfig): boolean {
);
}
/**
* Finalize the incremental-analysis configuration for this run.
*
* If overlay mode was selected for a PR but diff-informed analysis should have
* run and could not be prepared, fall back to a full non-overlay analysis.
* Query exclusions for incremental-only queries are applied only when the final
* configuration still uses overlay analysis or diff-informed analysis is
* actually available.
*/
export function applyIncrementalAnalysisSettings(
config: Config,
diffInformedAnalysis: DiffInformedAnalysisPreparation,
logger: Logger,
): void {
if (
config.overlayDatabaseMode === OverlayDatabaseMode.Overlay &&
diffInformedAnalysis.shouldRun &&
!diffInformedAnalysis.isAvailable
) {
logger.warning(
"Diff-informed analysis is not available for this pull request. " +
`Reverting overlay database mode to ${OverlayDatabaseMode.None}.`,
);
config.overlayDatabaseMode = OverlayDatabaseMode.None;
}
if (
config.overlayDatabaseMode === OverlayDatabaseMode.Overlay ||
diffInformedAnalysis.isAvailable
) {
config.extraQueryExclusions.push({
exclude: { tags: "exclude-from-incremental" },
});
}
}
/**
* Load and return the config.
*
@@ -1230,18 +1269,13 @@ export async function initConfig(
);
}
if (
config.overlayDatabaseMode === OverlayDatabaseMode.Overlay ||
(await shouldPerformDiffInformedAnalysis(
inputs.codeql,
inputs.features,
logger,
))
) {
config.extraQueryExclusions.push({
exclude: { tags: "exclude-from-incremental" },
});
}
const diffInformedAnalysis = await prepareDiffInformedAnalysis(
inputs.codeql,
inputs.features,
logger,
);
applyIncrementalAnalysisSettings(config, diffInformedAnalysis, logger);
if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) {
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
+84 -2
View File
@@ -5,9 +5,9 @@ import type { PullRequestBranches } from "./actions-util";
import { getApiClient, getGitHubVersion } from "./api-client";
import type { CodeQL } from "./codeql";
import { Feature, FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging";
import { Logger, withGroupAsync } from "./logging";
import { getRepositoryNwoFromEnv } from "./repository";
import { GitHubVariant, satisfiesGHESVersion } from "./util";
import { getErrorMessage, GitHubVariant, satisfiesGHESVersion } from "./util";
/**
* This interface is an abbreviated version of the file diff object returned by
@@ -69,6 +69,61 @@ export async function getDiffInformedAnalysisBranches(
return branches;
}
export interface DiffInformedAnalysisPreparation {
/**
* Whether diff-informed analysis applies to this workflow run.
*/
shouldRun: boolean;
/**
* Whether the diff ranges were successfully prepared and can be used.
*/
isAvailable: boolean;
}
/**
* Prepares the diff ranges needed for diff-informed analysis for the current
* run.
*
* @returns Whether diff-informed analysis applies to this run, and whether it
* was successfully prepared for use.
*/
export async function prepareDiffInformedAnalysis(
codeql: CodeQL,
features: FeatureEnablement,
logger: Logger,
): Promise<DiffInformedAnalysisPreparation> {
try {
const branches = await getDiffInformedAnalysisBranches(
codeql,
features,
logger,
);
if (!branches) {
return { shouldRun: false, isAvailable: false };
}
const isAvailable = await withGroupAsync(
"Computing PR diff ranges",
async () => {
try {
return await computeAndPersistDiffRanges(branches, logger);
} catch (e) {
logger.warning(
`Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`,
);
return false;
}
},
);
return { shouldRun: true, isAvailable };
} catch (e) {
logger.warning(
`Failed to determine diff-informed analysis availability: ${getErrorMessage(e)}`,
);
return { shouldRun: true, isAvailable: false };
}
}
export interface DiffThunkRange {
/** Relative path from the repository root, using forward slashes as separators. */
path: string;
@@ -151,6 +206,33 @@ export async function getPullRequestEditedDiffRanges(
return results;
}
/**
* Compute and persist the diff ranges for a pull request. This fetches the
* diff from the GitHub API and writes it to the diff ranges JSON file so that
* CodeQL can use it for diff-informed analysis.
*
* @param branches The base and head branches of the pull request, as returned
* by `getDiffInformedAnalysisBranches`.
* @param logger
* @returns `true` if the diff ranges were successfully computed and persisted,
* otherwise `false`.
*/
export async function computeAndPersistDiffRanges(
branches: PullRequestBranches,
logger: Logger,
): Promise<boolean> {
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
if (ranges === undefined) {
return false;
}
writeDiffRangesJsonFile(logger, ranges);
const distinctFiles = new Set(ranges.map((r) => r.path)).size;
logger.info(
`Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`,
);
return true;
}
async function getFileDiffsWithBasehead(
branches: PullRequestBranches,
logger: Logger,
+1 -43
View File
@@ -37,11 +37,6 @@ import {
makeDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import {
getDiffInformedAnalysisBranches,
getPullRequestEditedDiffRanges,
writeDiffRangesJsonFile,
} from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
import {
@@ -59,7 +54,7 @@ import {
runDatabaseInitCluster,
} from "./init";
import { JavaEnvVars, KnownLanguage } from "./languages";
import { getActionsLogger, Logger, withGroupAsync } from "./logging";
import { getActionsLogger, Logger } from "./logging";
import {
downloadOverlayBaseDatabaseFromCache,
OverlayBaseDatabaseDownloadStats,
@@ -427,7 +422,6 @@ async function run(startedAt: Date) {
}
await checkInstallPython311(config.languages, codeql);
await computeAndPersistDiffRanges(codeql, features, logger);
} catch (unwrappedError) {
const error = wrapError(unwrappedError);
core.setFailed(error.message);
@@ -818,42 +812,6 @@ async function loadRepositoryProperties(
}
}
/**
* Compute and persist diff ranges when diff-informed analysis is enabled
* (feature flag + PR context). This writes the standard pr-diff-range.json
* file for later reuse in the analyze step. Failures are logged but non-fatal.
*/
async function computeAndPersistDiffRanges(
codeql: CodeQL,
features: FeatureEnablement,
logger: Logger,
): Promise<void> {
await withGroupAsync("Computing PR diff ranges", async () => {
try {
const branches = await getDiffInformedAnalysisBranches(
codeql,
features,
logger,
);
if (!branches) {
return;
}
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
if (ranges === undefined) {
return;
}
writeDiffRangesJsonFile(logger, ranges);
const distinctFiles = new Set(ranges.map((r) => r.path)).size;
logger.info(
`Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`,
);
} catch (e) {
logger.warning(
`Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}`,
);
}
});
}
async function recordZstdAvailability(
config: configUtils.Config,
zstdAvailability: ZstdAvailability,