From d48d054533447bbfe0ec95160dab4b643fdc05b8 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Mon, 23 Mar 2026 18:33:59 +0000 Subject: [PATCH 1/3] Use `--stage` instead of `--format` in `git ls-files` --- lib/analyze-action-post.js | 4 ++-- lib/analyze-action.js | 4 ++-- lib/autobuild-action.js | 4 ++-- lib/init-action-post.js | 4 ++-- lib/init-action.js | 4 ++-- lib/resolve-environment-action.js | 4 ++-- lib/setup-codeql-action.js | 4 ++-- lib/upload-lib.js | 4 ++-- lib/upload-sarif-action.js | 4 ++-- src/git-utils.test.ts | 18 +++++++++--------- src/git-utils.ts | 16 ++++++++++------ 11 files changed, 37 insertions(+), 33 deletions(-) diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index b501469f5e..75be56ced1 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -162158,11 +162158,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 7f7f4fbe88..268cfad571 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107778,11 +107778,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index 42d955963e..7720f03cb0 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -104212,11 +104212,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 27cb1e9b40..341449f7e1 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165672,11 +165672,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/init-action.js b/lib/init-action.js index 625562ed91..cf2669305a 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105306,11 +105306,11 @@ var getGitRoot = async function(sourceRoot) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index df13d019d4..cefba2d562 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -104205,11 +104205,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index e710db4911..8fcb624980 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104072,11 +104072,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 485021d43d..3527c53649 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -107363,11 +107363,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 484025f829..da345bf9a9 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -107047,11 +107047,11 @@ var decodeGitFilePath = function(filePath) { var getFileOidsUnderPath = async function(basePath) { const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files." ); const fileOidMap = {}; - const regex = /^([0-9a-f]{40})_(.+)$/; + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); diff --git a/src/git-utils.test.ts b/src/git-utils.test.ts index 4c51bc1d9e..21327b5495 100644 --- a/src/git-utils.test.ts +++ b/src/git-utils.test.ts @@ -347,9 +347,9 @@ test.serial("getFileOidsUnderPath returns correct file mapping", async (t) => { const runGitCommandStub = sinon .stub(gitUtils as any, "runGitCommand") .resolves( - "30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" + - "d89514599a9a99f22b4085766d40af7b99974827_lib/git-utils.js.map\n" + - "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts", + "100644 30d998ded095371488be3a729eb61d86ed721a18 0\tlib/git-utils.js\n" + + "100644 d89514599a9a99f22b4085766d40af7b99974827 0\tlib/git-utils.js.map\n" + + "100644 a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96 0\tsrc/git-utils.ts", ); const result = await gitUtils.getFileOidsUnderPath("/fake/path"); @@ -362,7 +362,7 @@ test.serial("getFileOidsUnderPath returns correct file mapping", async (t) => { t.deepEqual(runGitCommandStub.firstCall.args, [ "/fake/path", - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files.", ]); }); @@ -371,9 +371,9 @@ test.serial("getFileOidsUnderPath handles quoted paths", async (t) => { sinon .stub(gitUtils as any, "runGitCommand") .resolves( - "30d998ded095371488be3a729eb61d86ed721a18_lib/normal-file.js\n" + - 'd89514599a9a99f22b4085766d40af7b99974827_"lib/file with spaces.js"\n' + - 'a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_"lib/file\\twith\\ttabs.js"', + "100644 30d998ded095371488be3a729eb61d86ed721a18 0\tlib/normal-file.js\n" + + '100644 d89514599a9a99f22b4085766d40af7b99974827 0\t"lib/file with spaces.js"\n' + + '100644 a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96 0\t"lib/file\\twith\\ttabs.js"', ); const result = await gitUtils.getFileOidsUnderPath("/fake/path"); @@ -398,9 +398,9 @@ test.serial( sinon .stub(gitUtils as any, "runGitCommand") .resolves( - "30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" + + "100644 30d998ded095371488be3a729eb61d86ed721a18 0\tlib/git-utils.js\n" + "invalid-line-format\n" + - "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts", + "100644 a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96 0\tsrc/git-utils.ts", ); await t.throwsAsync( diff --git a/src/git-utils.ts b/src/git-utils.ts index 6b792ea543..16bf01f597 100644 --- a/src/git-utils.ts +++ b/src/git-utils.ts @@ -252,24 +252,28 @@ export const getGitRoot = async function ( * * @param basePath A path into the Git repository. * @returns a map from file paths (relative to `basePath`) to Git OIDs. - * @throws {Error} if "git ls-tree" produces unexpected output. + * @throws {Error} if "git ls-files" produces unexpected output. */ export const getFileOidsUnderPath = async function ( basePath: string, ): Promise<{ [key: string]: string }> { // Without the --full-name flag, the path is relative to the current working // directory of the git command, which is basePath. + // + // We use --stage rather than --format here because --stage has been available since Git 2.11.0, + // while --format was only introduced in Git 2.38.0, which would limit overlay rollout. const stdout = await runGitCommand( basePath, - ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], + ["ls-files", "--recurse-submodules", "--stage"], "Cannot list Git OIDs of tracked files.", ); const fileOidMap: { [key: string]: string } = {}; - // With --format=%(objectname)_%(path), the output is a list of lines like: - // 30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js - // d89514599a9a99f22b4085766d40af7b99974827_lib/git-utils.js.map - const regex = /^([0-9a-f]{40})_(.+)$/; + // With --stage, the output is a list of lines like: + // 100644 4c51bc1d9e86cd86e01b0f340cb8ce095c33b283 0\tsrc/git-utils.test.ts + // 100644 6b792ea543ce75d7a8a03df591e3c85311ecb64f 0\tsrc/git-utils.ts + // The fields are: \t + const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/; for (const line of stdout.split("\n")) { if (line) { const match = line.match(regex); From 28f56f2bed36b3ef3bb15e0e97d6b792805d3198 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Mon, 23 Mar 2026 18:35:34 +0000 Subject: [PATCH 2/3] Update minimum Git version required for overlay --- lib/init-action.js | 2 +- src/config-utils.test.ts | 2 +- src/git-utils.ts | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index cf2669305a..ec26268cb9 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105196,7 +105196,7 @@ var core8 = __toESM(require_core()); var toolrunner2 = __toESM(require_toolrunner()); var io3 = __toESM(require_io()); var semver3 = __toESM(require_semver2()); -var GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.38.0"; +var GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.11.0"; var GitVersionInfo = class { constructor(truncatedVersion, fullVersion) { this.truncatedVersion = truncatedVersion; diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 0c14dcf9c4..0d7c747251 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -1936,7 +1936,7 @@ test.serial( "Fallback due to old git version", { overlayDatabaseEnvVar: "overlay", - gitVersion: new GitVersionInfo("2.30.0", "2.30.0"), // Version below required 2.38.0 + gitVersion: new GitVersionInfo("2.10.0", "2.10.0"), // Version below required 2.11.0 }, { disabledReason: OverlayDisabledReason.IncompatibleGit, diff --git a/src/git-utils.ts b/src/git-utils.ts index 16bf01f597..80e49f2f63 100644 --- a/src/git-utils.ts +++ b/src/git-utils.ts @@ -14,10 +14,11 @@ import { import { ConfigurationError, getRequiredEnvParam } from "./util"; /** - * Minimum Git version required for overlay analysis. The `git ls-files --format` - * option, which is used by `getFileOidsUnderPath`, was introduced in Git 2.38.0. + * Minimum Git version required for overlay analysis. The + * `git ls-files --recurse-submodules` option, which is used by + * `getFileOidsUnderPath`, was introduced in Git 2.11.0. */ -export const GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.38.0"; +export const GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.11.0"; /** * Git version information From 8c023a6b0794a16428450c8aaa13f723632ee447 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Mon, 23 Mar 2026 18:40:55 +0000 Subject: [PATCH 3/3] Add changelog note --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d94f28e616..7283c0fb06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] -No user facing changes. +- Reduced the minimum Git version required for [improved incremental analysis](https://github.com/github/roadmap/issues/1158) from 2.38.0 to 2.11.0. [#3767](https://github.com/github/codeql-action/pull/3767) ## 4.34.1 - 20 Mar 2026