Record both truncated and full git versions

This commit is contained in:
Henry Mercer
2025-12-17 17:27:14 +00:00
parent 056581e05b
commit a13b404670
16 changed files with 121 additions and 75 deletions

View File

@@ -15,6 +15,7 @@ import * as configUtils from "./config-utils";
import * as errorMessages from "./error-messages";
import { Feature } from "./feature-flags";
import * as gitUtils from "./git-utils";
import { GitVersionInfo } from "./git-utils";
import { KnownLanguage, Language } from "./languages";
import { getRunnerLogger } from "./logging";
import {
@@ -978,7 +979,7 @@ interface OverlayDatabaseModeTestSetup {
languages: Language[];
codeqlVersion: string;
gitRoot: string | undefined;
gitVersion: string | undefined;
gitVersion: GitVersionInfo | undefined;
codeScanningConfig: configUtils.UserConfig;
diskUsage: DiskUsage | undefined;
memoryFlagValue: number;
@@ -993,7 +994,10 @@ const defaultOverlayDatabaseModeTestSetup: OverlayDatabaseModeTestSetup = {
languages: [KnownLanguage.javascript],
codeqlVersion: CODEQL_OVERLAY_MINIMUM_VERSION,
gitRoot: "/some/git/root",
gitVersion: gitUtils.GIT_MINIMUM_VERSION_FOR_OVERLAY,
gitVersion: new GitVersionInfo(
gitUtils.GIT_MINIMUM_VERSION_FOR_OVERLAY,
gitUtils.GIT_MINIMUM_VERSION_FOR_OVERLAY,
),
codeScanningConfig: {},
diskUsage: {
numAvailableBytes: 50_000_000_000,
@@ -1781,7 +1785,7 @@ test(
"Fallback due to old git version",
{
overlayDatabaseEnvVar: "overlay",
gitVersion: "2.30.0", // Version below required 2.38.0
gitVersion: new GitVersionInfo("2.30.0", "2.30.0"), // Version below required 2.38.0
},
{
overlayDatabaseMode: OverlayDatabaseMode.None,

View File

@@ -3,7 +3,6 @@ import * as path from "path";
import { performance } from "perf_hooks";
import * as yaml from "js-yaml";
import * as semver from "semver";
import { getActionVersion, isAnalyzingPullRequest } from "./actions-util";
import {
@@ -32,6 +31,7 @@ import {
getGitRoot,
getGitVersionOrThrow,
GIT_MINIMUM_VERSION_FOR_OVERLAY,
GitVersionInfo,
isAnalyzingDefaultBranch,
} from "./git-utils";
import { KnownLanguage, Language } from "./languages";
@@ -717,7 +717,7 @@ export async function getOverlayDatabaseMode(
buildMode: BuildMode | undefined,
ramInput: string | undefined,
codeScanningConfig: UserConfig,
gitVersion: string | undefined,
gitVersion: GitVersionInfo | undefined,
logger: Logger,
): Promise<{
overlayDatabaseMode: OverlayDatabaseMode;
@@ -828,7 +828,7 @@ export async function getOverlayDatabaseMode(
);
return nonOverlayAnalysis;
}
if (!semver.gte(gitVersion, GIT_MINIMUM_VERSION_FOR_OVERLAY)) {
if (!gitVersion.isAtLeast(GIT_MINIMUM_VERSION_FOR_OVERLAY)) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because ` +
`the installed Git version is older than ${GIT_MINIMUM_VERSION_FOR_OVERLAY}. ` +
@@ -928,10 +928,10 @@ export async function initConfig(
config.computedConfig["query-filters"] = [];
}
let gitVersion: string | undefined = undefined;
let gitVersion: GitVersionInfo | undefined = undefined;
try {
gitVersion = await getGitVersionOrThrow();
logger.info(`Using Git version ${gitVersion}`);
logger.info(`Using Git version ${gitVersion.fullVersion}`);
await logGitVersionTelemetry(config, gitVersion);
} catch (e) {
logger.debug(`Could not determine Git version: ${getErrorMessage(e)}`);
@@ -1355,7 +1355,7 @@ export function getPrimaryAnalysisConfig(config: Config): AnalysisConfig {
/** Logs the Git version as a telemetry diagnostic. */
async function logGitVersionTelemetry(
config: Config,
gitVersion: string,
gitVersion: GitVersionInfo,
): Promise<void> {
if (config.languages.length > 0) {
addDiagnostic(
@@ -1366,7 +1366,10 @@ async function logGitVersionTelemetry(
makeTelemetryDiagnostic(
"codeql-action/git-version-telemetry",
"Git version telemetry",
{ gitVersion },
{
fullVersion: gitVersion.fullVersion,
truncatedVersion: gitVersion.truncatedVersion,
},
),
);
}

View File

@@ -376,10 +376,11 @@ test("getFileOidsUnderPath throws on unexpected output format", async (t) => {
});
test("getGitVersionOrThrow returns version for valid git output", async (t) => {
sinon.stub(gitUtils as any, "runGitCommand").resolves("git version 2.40.0\n");
sinon.stub(gitUtils as any, "runGitCommand").resolves("git version 2.40.0");
const version = await gitUtils.getGitVersionOrThrow();
t.is(version, "2.40.0");
t.is(version.truncatedVersion, "2.40.0");
t.is(version.fullVersion, "2.40.0");
});
test("getGitVersionOrThrow throws for invalid git output", async (t) => {
@@ -399,11 +400,12 @@ test("getGitVersionOrThrow throws for invalid git output", async (t) => {
test("getGitVersionOrThrow handles Windows-style git output", async (t) => {
sinon
.stub(gitUtils as any, "runGitCommand")
.resolves("git version 2.40.0.windows.1\n");
.resolves("git version 2.40.0.windows.1");
const version = await gitUtils.getGitVersionOrThrow();
// Should extract just the major.minor.patch portion
t.is(version, "2.40.0");
t.is(version.truncatedVersion, "2.40.0");
t.is(version.fullVersion, "2.40.0.windows.1");
});
test("getGitVersionOrThrow throws when git command fails", async (t) => {
@@ -421,3 +423,12 @@ test("getGitVersionOrThrow throws when git command fails", async (t) => {
},
);
});
test("GitVersionInfo.isAtLeast correctly compares versions", async (t) => {
const version = new gitUtils.GitVersionInfo("2.40.0", "2.40.0");
t.true(version.isAtLeast("2.38.0"));
t.true(version.isAtLeast("2.40.0"));
t.false(version.isAtLeast("2.41.0"));
t.false(version.isAtLeast("3.0.0"));
});

View File

@@ -1,6 +1,7 @@
import * as core from "@actions/core";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import * as semver from "semver";
import {
getOptionalInput,
@@ -15,6 +16,27 @@ import { ConfigurationError, getRequiredEnvParam } from "./util";
*/
export const GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.38.0";
/**
* Git version information
*
* The full version string as reported by `git --version` may not be
* semver-compatible (e.g., "2.40.0.windows.1"). This class captures both
* the full version string and a truncated semver-compatible version string
* (e.g., "2.40.0").
*/
export class GitVersionInfo {
constructor(
/** Truncated semver-compatible version */
public truncatedVersion: string,
/** Full version string as reported by `git --version` */
public fullVersion: string,
) {}
isAtLeast(minVersion: string): boolean {
return semver.gte(this.truncatedVersion, minVersion);
}
}
/**
* Gets the version of Git installed on the system and throws an error if
* the version cannot be determined.
@@ -22,7 +44,7 @@ export const GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.38.0";
* @returns The Git version string (e.g., "2.40.0").
* @throws {Error} if the version could not be determined.
*/
export async function getGitVersionOrThrow(): Promise<string> {
export async function getGitVersionOrThrow(): Promise<GitVersionInfo> {
const stdout = await runGitCommand(
undefined,
["--version"],
@@ -30,9 +52,9 @@ export async function getGitVersionOrThrow(): Promise<string> {
);
// Git version output can vary: "git version 2.40.0" or "git version 2.40.0.windows.1"
// We capture just the major.minor.patch portion to ensure semver compatibility.
const match = stdout.match(/git version (\d+\.\d+\.\d+)/);
const match = stdout.match(/^git version ((\d+\.\d+\.\d+).*)$/);
if (match?.[1]) {
return match[1];
return new GitVersionInfo(match[2], match[1]);
}
throw new Error(`Could not parse Git version from output: ${stdout.trim()}`);
}