Compare commits

..

1 Commits

Author SHA1 Message Date
Salman Muin Kayser Chishti be385d8fff Fix includeIf case sensitivity on Windows self-hosted runners
Switch to includeIf.gitdir/i: on Windows so path matching is
case-insensitive, matching the filesystem behavior. This fixes
auth failures on self-hosted Windows runners where the workspace
folder casing doesn't match between the runner config and disk.

Also update the cleanup regex to handle both gitdir: and gitdir/i:
variants.

Fixes #2345
2026-05-06 12:27:25 +00:00
9 changed files with 73 additions and 648 deletions
+40 -1
View File
@@ -974,6 +974,46 @@ describe('git-auth-helper tests', () => {
).toBe(false)
expect((authHelper as any).testCredentialsConfigPath('')).toBe(false)
})
const includeIfCleanupRegex_matchesBothVariants =
'includeIf cleanup regex matches both gitdir: and gitdir/i: keys'
it(includeIfCleanupRegex_matchesBothVariants, async () => {
// The cleanup regex must match both variants so credential
// removal works regardless of which was written
const regex = /^includeIf\.gitdir(\/i)?:/
expect(regex.test('includeIf.gitdir:D:/workspaces/repo/.git.path')).toBe(
true
)
expect(regex.test('includeIf.gitdir/i:D:/Workspaces/repo/.git.path')).toBe(
true
)
expect(regex.test('includeIf.gitdir/i:/github/workspace/.git.path')).toBe(
true
)
expect(regex.test('includeIf.gitdir:~/projects/foo/.git.path')).toBe(true)
expect(regex.test('includeIf.onbranch:main.path')).toBe(false)
expect(regex.test('include.path')).toBe(false)
})
const includeIfDirective_usesCorrectVariantForPlatform =
'includeIf directive uses gitdir/i on Windows and gitdir on other platforms'
it(includeIfDirective_usesCorrectVariantForPlatform, async () => {
await setup(includeIfDirective_usesCorrectVariantForPlatform)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const localConfigContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
if (isWindows) {
expect(localConfigContent).toContain('includeIf.gitdir/i:')
expect(localConfigContent).not.toContain('includeIf.gitdir:')
} else {
expect(localConfigContent).toContain('includeIf.gitdir:')
expect(localConfigContent).not.toContain('includeIf.gitdir/i:')
}
})
})
async function setup(testName: string): Promise<void> {
@@ -1103,7 +1143,6 @@ async function setup(testName: string): Promise<void> {
),
tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(),
tryGetObjectFormat: jest.fn(async () => ({format: '', succeeded: true})),
tryGetConfigValues: jest.fn(
async (
key: string,
-163
View File
@@ -378,169 +378,6 @@ describe('Test fetchDepth and fetchTags options', () => {
})
})
describe('repository object format', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
})
afterEach(() => {
jest.restoreAllMocks()
})
it('detects SHA-256 from a 64-character HEAD oid', async () => {
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
if (args.includes('ls-remote')) {
options.listeners.stdout(
Buffer.from(
'ref: refs/heads/main\tHEAD\n' +
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92\tHEAD\n'
)
)
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
git = await commandManager.createCommandManager('test', false, false)
const objectFormat = await git.tryGetObjectFormat(
'https://github.com/example/repo'
)
expect(objectFormat).toEqual({format: 'sha256', succeeded: true})
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'ls-remote',
'--quiet',
'--exit-code',
'--symref',
'https://github.com/example/repo',
'HEAD'
],
expect.objectContaining({
ignoreReturnCode: true,
silent: true
})
)
})
it('detects SHA-1 from a 40-character HEAD oid', async () => {
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
if (args.includes('ls-remote')) {
options.listeners.stdout(
Buffer.from(
'ref: refs/heads/main\tHEAD\n' +
'c988866043f035e6a46509872215f91d879044c9\tHEAD\n'
)
)
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
git = await commandManager.createCommandManager('test', false, false)
await expect(
git.tryGetObjectFormat('https://github.com/example/repo')
).resolves.toEqual({format: 'sha1', succeeded: true})
})
it('returns unsuccessful when HEAD does not resolve to a recognized object id', async () => {
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
if (args.includes('ls-remote')) {
options.listeners.stdout(Buffer.from('ref: refs/heads/main\tHEAD\n'))
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
git = await commandManager.createCommandManager('test', false, false)
await expect(
git.tryGetObjectFormat('https://github.com/example/repo')
).resolves.toEqual({format: '', succeeded: false})
})
it('returns unsuccessful when object format detection cannot reach the remote', async () => {
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
return 0
}
return 128
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
git = await commandManager.createCommandManager('test', false, false)
await expect(
git.tryGetObjectFormat('https://github.com/example/repo')
).resolves.toEqual({format: '', succeeded: false})
})
it('initializes SHA-256 repositories with the matching object format', async () => {
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
git = await commandManager.createCommandManager('test', false, false)
await git.init('sha256')
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
['init', '--object-format=sha256', 'test'],
expect.any(Object)
)
})
it('initializes SHA-1 repositories with existing default arguments', async () => {
mockExec.mockImplementation((path, args, options) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
git = await commandManager.createCommandManager('test', false, false)
await git.init('sha1')
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
['init', 'test'],
expect.any(Object)
)
})
})
describe('git user-agent with orchestration ID', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
-1
View File
@@ -501,7 +501,6 @@ async function setup(testName: string): Promise<void> {
await fs.promises.stat(path.join(repositoryPath, '.git'))
return repositoryUrl
}),
tryGetObjectFormat: jest.fn(async () => ({format: '', succeeded: true})),
tryGetConfigValues: jest.fn(),
tryGetConfigKeys: jest.fn(),
tryReset: jest.fn(async () => {
-164
View File
@@ -1,164 +0,0 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as githubApiHelper from '../lib/github-api-helper'
describe('github-api-helper object format', () => {
let getOctokitSpy: jest.SpyInstance
let debugSpy: jest.SpyInstance
let repoGet: jest.Mock
let branchGet: jest.Mock
function mockObjectFormatApi(defaultBranch: string, commitSha: string): void {
repoGet = jest.fn(async () => ({
data: {
default_branch: defaultBranch
}
}))
branchGet = jest.fn(async () => ({
data: {
commit: {
sha: commitSha
}
}
}))
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
rest: {
repos: {
get: repoGet,
getBranch: branchGet
}
}
} as any)
}
beforeEach(() => {
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
})
afterEach(() => {
jest.restoreAllMocks()
})
it('detects SHA-256 from the default branch commit SHA', async () => {
const commitSha =
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92'
mockObjectFormatApi('main', commitSha)
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({
defaultBranch: 'main',
format: 'sha256',
succeeded: true
})
expect(getOctokitSpy).toHaveBeenCalledWith(
'token',
expect.objectContaining({baseUrl: 'https://api.github.com'})
)
expect(repoGet).toHaveBeenCalledWith({owner: 'owner', repo: 'repo'})
expect(branchGet).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
branch: 'main'
})
})
it('detects SHA-1 from the default branch commit SHA', async () => {
mockObjectFormatApi('main', 'c988866043f035e6a46509872215f91d879044c9')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({defaultBranch: 'main', format: 'sha1', succeeded: true})
})
it('detects object format from an existing commit without API calls', async () => {
const commitSha =
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92'
getOctokitSpy = jest.spyOn(github, 'getOctokit')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat(
'token',
'owner',
'repo',
undefined,
undefined,
commitSha
)
).resolves.toEqual({format: 'sha256', succeeded: true})
expect(getOctokitSpy).not.toHaveBeenCalled()
})
it('uses a branch ref directly without looking up the default branch', async () => {
const commitSha = 'c988866043f035e6a46509872215f91d879044c9'
repoGet = jest.fn()
branchGet = jest.fn(async () => ({
data: {
commit: {
sha: commitSha
}
}
}))
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
rest: {
repos: {
get: repoGet,
getBranch: branchGet
}
}
} as any)
await expect(
githubApiHelper.tryGetRepositoryObjectFormat(
'token',
'owner',
'repo',
undefined,
'refs/heads/feature'
)
).resolves.toEqual({format: 'sha1', succeeded: true})
expect(repoGet).not.toHaveBeenCalled()
expect(branchGet).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
branch: 'feature'
})
})
it('returns unsuccessful when the default branch commit SHA is not recognized', async () => {
mockObjectFormatApi('main', 'not-a-sha')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: '', succeeded: false})
expect(debugSpy).toHaveBeenCalledWith(
'Unable to determine repository object format from commit SHA'
)
})
it('returns unsuccessful when the repository API lookup fails', async () => {
repoGet = jest.fn(async () => {
throw new Error('not found')
})
branchGet = jest.fn()
jest.spyOn(github, 'getOctokit').mockReturnValue({
rest: {
repos: {
get: repoGet,
getBranch: branchGet
}
}
} as any)
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: '', succeeded: false})
expect(branchGet).not.toHaveBeenCalled()
expect(debugSpy).toHaveBeenCalledWith(
'Unable to determine repository object format: not found'
)
})
})
+16 -136
View File
@@ -151,6 +151,12 @@ const stateHelper = __importStar(__nccwpck_require__(4866));
const urlHelper = __importStar(__nccwpck_require__(9437));
const uuid_1 = __nccwpck_require__(5840);
const IS_WINDOWS = process.platform === 'win32';
// Use case-insensitive gitdir matching on Windows to handle path casing mismatches
// between the runner's GITHUB_WORKSPACE and the actual filesystem casing.
// See: https://github.com/actions/checkout/issues/2345
const INCLUDE_IF_GITDIR = IS_WINDOWS
? 'includeIf.gitdir/i:'
: 'includeIf.gitdir:';
const SSH_COMMAND_KEY = 'core.sshCommand';
function createAuthHelper(git, settings) {
return new GitAuthHelper(git, settings);
@@ -270,7 +276,7 @@ class GitAuthHelper {
let submoduleGitDir = path.dirname(configPath); // The config file is at .git/modules/submodule-name/config
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf
yield this.git.config(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, false, // globalConfig?
yield this.git.config(`${INCLUDE_IF_GITDIR}${submoduleGitDir}.path`, credentialsConfigPath, false, // globalConfig?
false, // add?
configPath);
// Container submodule git directory
@@ -280,7 +286,7 @@ class GitAuthHelper {
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
const containerSubmoduleGitDir = path.posix.join('/github/workspace', relativeSubmoduleGitDir);
// Configure container includeIf
yield this.git.config(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, // globalConfig?
yield this.git.config(`${INCLUDE_IF_GITDIR}${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, // globalConfig?
false, // add?
configPath);
}
@@ -410,10 +416,10 @@ class GitAuthHelper {
let gitDir = path.join(this.git.getWorkingDirectory(), '.git');
gitDir = gitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
const hostIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}.path`;
yield this.git.config(hostIncludeKey, credentialsConfigPath);
// Configure host includeIf for worktrees
const hostWorktreeIncludeKey = `includeIf.gitdir:${gitDir}/worktrees/*.path`;
const hostWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}/worktrees/*.path`;
yield this.git.config(hostWorktreeIncludeKey, credentialsConfigPath);
// Container git directory
const workingDirectory = this.git.getWorkingDirectory();
@@ -425,10 +431,10 @@ class GitAuthHelper {
// Container credentials config path
const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath));
// Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`;
const containerIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}.path`;
yield this.git.config(containerIncludeKey, containerCredentialsPath);
// Configure container includeIf for worktrees
const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`;
const containerWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}/worktrees/*.path`;
yield this.git.config(containerWorktreeIncludeKey, containerCredentialsPath);
}
});
@@ -565,7 +571,7 @@ class GitAuthHelper {
const credentialsPaths = new Set();
try {
// Get all includeIf.gitdir keys
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, // globalConfig?
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir(/i)?:', false, // globalConfig?
configPath);
for (const key of keys) {
// Get all values for this key
@@ -896,14 +902,9 @@ class GitCommandManager {
getWorkingDirectory() {
return this.workingDirectory;
}
init(objectFormat) {
init() {
return __awaiter(this, void 0, void 0, function* () {
const args = ['init'];
if (objectFormat === 'sha256') {
args.push('--object-format=sha256');
}
args.push(this.workingDirectory);
yield this.execGit(args);
yield this.execGit(['init', this.workingDirectory]);
});
}
isDetached() {
@@ -1061,45 +1062,6 @@ class GitCommandManager {
return stdout;
});
}
tryGetObjectFormat(repositoryUrl) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const output = yield this.execGit([
'-c',
'protocol.version=2',
'ls-remote',
'--quiet',
'--exit-code',
'--symref',
repositoryUrl,
'HEAD'
], true, true);
if (output.exitCode !== 0) {
core.debug(`Unable to determine repository object format: git ls-remote exited with ${output.exitCode}`);
return { format: '', succeeded: false };
}
for (const line of output.stdout.trim().split('\n')) {
const [oid, ref] = line.split('\t');
if (ref !== 'HEAD') {
continue;
}
if (/^[0-9a-fA-F]{64}$/.test(oid)) {
return { format: 'sha256', succeeded: true };
}
if (/^[0-9a-fA-F]{40}$/.test(oid)) {
return { format: 'sha1', succeeded: true };
}
}
core.debug('Unable to determine repository object format from HEAD');
return { format: '', succeeded: false };
}
catch (err) {
core.debug(`Unable to determine repository object format: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
return { format: '', succeeded: false };
}
});
}
tryGetConfigValues(configKey, globalConfig, configFile) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['config'];
@@ -1528,24 +1490,10 @@ function getSource(settings) {
}
// Save state for POST action
stateHelper.setRepositoryPath(settings.repositoryPath);
let defaultBranch = '';
// Initialize the repository
if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) {
core.startGroup('Determining repository object format');
let objectFormatResult = yield githubApiHelper.tryGetRepositoryObjectFormat(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.githubServerUrl, settings.ref, settings.commit);
if (!objectFormatResult.succeeded) {
objectFormatResult = yield git.tryGetObjectFormat(repositoryUrl);
}
const objectFormat = objectFormatResult.succeeded
? objectFormatResult.format
: '';
defaultBranch = objectFormatResult.defaultBranch || '';
if (objectFormat === 'sha256') {
core.info('Detected SHA-256 repository object format');
}
core.endGroup();
core.startGroup('Initializing the repository');
yield git.init(objectFormat);
yield git.init();
yield git.remoteAdd('origin', repositoryUrl);
core.endGroup();
}
@@ -1569,10 +1517,6 @@ function getSource(settings) {
if (settings.sshKey) {
settings.ref = yield git.getDefaultBranch(repositoryUrl);
}
else if (defaultBranch) {
core.info(`Default branch '${defaultBranch}'`);
settings.ref = `refs/heads/${defaultBranch}`;
}
else {
settings.ref = yield githubApiHelper.getDefaultBranch(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.githubServerUrl);
}
@@ -1872,7 +1816,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.downloadRepository = downloadRepository;
exports.getDefaultBranch = getDefaultBranch;
exports.tryGetRepositoryObjectFormat = tryGetRepositoryObjectFormat;
const assert = __importStar(__nccwpck_require__(9491));
const core = __importStar(__nccwpck_require__(2186));
const fs = __importStar(__nccwpck_require__(7147));
@@ -1974,69 +1917,6 @@ function getDefaultBranch(authToken, owner, repo, baseUrl) {
}));
});
}
function tryGetRepositoryObjectFormat(authToken, owner, repo, baseUrl, ref, commit) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const commitFormat = getObjectFormat(commit);
if (commitFormat) {
return { format: commitFormat, succeeded: true };
}
const octokit = github.getOctokit(authToken, {
baseUrl: (0, url_helper_1.getServerApiUrl)(baseUrl)
});
let branchName = getBranchName(ref);
let defaultBranch = '';
if (!branchName) {
const repository = yield octokit.rest.repos.get({ owner, repo });
defaultBranch = repository.data.default_branch;
assert.ok(defaultBranch, 'default_branch cannot be empty');
branchName = defaultBranch;
}
const branch = yield octokit.rest.repos.getBranch({
owner,
repo,
branch: branchName
});
const branchFormat = getObjectFormat(branch.data.commit.sha);
if (branchFormat) {
return {
defaultBranch: defaultBranch || undefined,
format: branchFormat,
succeeded: true
};
}
core.debug('Unable to determine repository object format from commit SHA');
return { format: '', succeeded: false };
}
catch (err) {
core.debug(`Unable to determine repository object format: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
return { format: '', succeeded: false };
}
});
}
function getBranchName(ref) {
if (!ref) {
return '';
}
const headsPrefix = 'refs/heads/';
if (ref.startsWith(headsPrefix)) {
return ref.substring(headsPrefix.length);
}
if (!ref.startsWith('refs/') && !getObjectFormat(ref)) {
return ref;
}
return '';
}
function getObjectFormat(sha) {
if (/^[0-9a-fA-F]{64}$/.test(sha || '')) {
return 'sha256';
}
if (/^[0-9a-fA-F]{40}$/.test(sha || '')) {
return 'sha1';
}
return '';
}
function downloadArchive(authToken, owner, repo, ref, commit, baseUrl) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = github.getOctokit(authToken, {
+13 -7
View File
@@ -13,6 +13,12 @@ import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings'
const IS_WINDOWS = process.platform === 'win32'
// Use case-insensitive gitdir matching on Windows to handle path casing mismatches
// between the runner's GITHUB_WORKSPACE and the actual filesystem casing.
// See: https://github.com/actions/checkout/issues/2345
const INCLUDE_IF_GITDIR = IS_WINDOWS
? 'includeIf.gitdir/i:'
: 'includeIf.gitdir:'
const SSH_COMMAND_KEY = 'core.sshCommand'
export interface IGitAuthHelper {
@@ -182,7 +188,7 @@ class GitAuthHelper {
// Configure host includeIf
await this.git.config(
`includeIf.gitdir:${submoduleGitDir}.path`,
`${INCLUDE_IF_GITDIR}${submoduleGitDir}.path`,
credentialsConfigPath,
false, // globalConfig?
false, // add?
@@ -204,7 +210,7 @@ class GitAuthHelper {
// Configure container includeIf
await this.git.config(
`includeIf.gitdir:${containerSubmoduleGitDir}.path`,
`${INCLUDE_IF_GITDIR}${containerSubmoduleGitDir}.path`,
containerCredentialsPath,
false, // globalConfig?
false, // add?
@@ -371,11 +377,11 @@ class GitAuthHelper {
gitDir = gitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
// Configure host includeIf
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
const hostIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}.path`
await this.git.config(hostIncludeKey, credentialsConfigPath)
// Configure host includeIf for worktrees
const hostWorktreeIncludeKey = `includeIf.gitdir:${gitDir}/worktrees/*.path`
const hostWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}/worktrees/*.path`
await this.git.config(hostWorktreeIncludeKey, credentialsConfigPath)
// Container git directory
@@ -397,11 +403,11 @@ class GitAuthHelper {
)
// Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`
const containerIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}.path`
await this.git.config(containerIncludeKey, containerCredentialsPath)
// Configure container includeIf for worktrees
const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`
const containerWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}/worktrees/*.path`
await this.git.config(
containerWorktreeIncludeKey,
containerCredentialsPath
@@ -554,7 +560,7 @@ class GitAuthHelper {
try {
// Get all includeIf.gitdir keys
const keys = await this.git.tryGetConfigKeys(
'^includeIf\\.gitdir:',
'^includeIf\\.gitdir(/i)?:',
false, // globalConfig?
configPath
)
+3 -64
View File
@@ -15,11 +15,6 @@ import {GitVersion} from './git-version'
export const MinimumGitVersion = new GitVersion('2.18')
export const MinimumGitSparseCheckoutVersion = new GitVersion('2.28')
export interface GitObjectFormatResult {
format: string
succeeded: boolean
}
export interface IGitCommandManager {
branchDelete(remote: boolean, branch: string): Promise<void>
branchExists(remote: boolean, pattern: string): Promise<boolean>
@@ -48,7 +43,7 @@ export interface IGitCommandManager {
getDefaultBranch(repositoryUrl: string): Promise<string>
getSubmoduleConfigPaths(recursive: boolean): Promise<string[]>
getWorkingDirectory(): string
init(objectFormat?: string): Promise<void>
init(): Promise<void>
isDetached(): Promise<boolean>
lfsFetch(ref: string): Promise<void>
lfsInstall(): Promise<void>
@@ -73,7 +68,6 @@ export interface IGitCommandManager {
): Promise<boolean>
tryDisableAutomaticGarbageCollection(): Promise<boolean>
tryGetFetchUrl(): Promise<string>
tryGetObjectFormat(repositoryUrl: string): Promise<GitObjectFormatResult>
tryGetConfigValues(
configKey: string,
globalConfig?: boolean,
@@ -370,14 +364,8 @@ class GitCommandManager {
return this.workingDirectory
}
async init(objectFormat?: string): Promise<void> {
const args = ['init']
if (objectFormat === 'sha256') {
args.push('--object-format=sha256')
}
args.push(this.workingDirectory)
await this.execGit(args)
async init(): Promise<void> {
await this.execGit(['init', this.workingDirectory])
}
async isDetached(): Promise<boolean> {
@@ -548,55 +536,6 @@ class GitCommandManager {
return stdout
}
async tryGetObjectFormat(
repositoryUrl: string
): Promise<GitObjectFormatResult> {
try {
const output = await this.execGit(
[
'-c',
'protocol.version=2',
'ls-remote',
'--quiet',
'--exit-code',
'--symref',
repositoryUrl,
'HEAD'
],
true,
true
)
if (output.exitCode !== 0) {
core.debug(
`Unable to determine repository object format: git ls-remote exited with ${output.exitCode}`
)
return {format: '', succeeded: false}
}
for (const line of output.stdout.trim().split('\n')) {
const [oid, ref] = line.split('\t')
if (ref !== 'HEAD') {
continue
}
if (/^[0-9a-fA-F]{64}$/.test(oid)) {
return {format: 'sha256', succeeded: true}
}
if (/^[0-9a-fA-F]{40}$/.test(oid)) {
return {format: 'sha1', succeeded: true}
}
}
core.debug('Unable to determine repository object format from HEAD')
return {format: '', succeeded: false}
} catch (err) {
core.debug(
`Unable to determine repository object format: ${(err as any)?.message ?? err}`
)
return {format: '', succeeded: false}
}
}
async tryGetConfigValues(
configKey: string,
globalConfig?: boolean,
+1 -28
View File
@@ -105,36 +105,12 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Save state for POST action
stateHelper.setRepositoryPath(settings.repositoryPath)
let defaultBranch = ''
// Initialize the repository
if (
!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
) {
core.startGroup('Determining repository object format')
let objectFormatResult =
await githubApiHelper.tryGetRepositoryObjectFormat(
settings.authToken,
settings.repositoryOwner,
settings.repositoryName,
settings.githubServerUrl,
settings.ref,
settings.commit
)
if (!objectFormatResult.succeeded) {
objectFormatResult = await git.tryGetObjectFormat(repositoryUrl)
}
const objectFormat = objectFormatResult.succeeded
? objectFormatResult.format
: ''
defaultBranch = objectFormatResult.defaultBranch || ''
if (objectFormat === 'sha256') {
core.info('Detected SHA-256 repository object format')
}
core.endGroup()
core.startGroup('Initializing the repository')
await git.init(objectFormat)
await git.init()
await git.remoteAdd('origin', repositoryUrl)
core.endGroup()
}
@@ -162,9 +138,6 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
core.startGroup('Determining the default branch')
if (settings.sshKey) {
settings.ref = await git.getDefaultBranch(repositoryUrl)
} else if (defaultBranch) {
core.info(`Default branch '${defaultBranch}'`)
settings.ref = `refs/heads/${defaultBranch}`
} else {
settings.ref = await githubApiHelper.getDefaultBranch(
settings.authToken,
-84
View File
@@ -11,12 +11,6 @@ import {getServerApiUrl} from './url-helper'
const IS_WINDOWS = process.platform === 'win32'
export interface RepositoryObjectFormatResult {
defaultBranch?: string
format: string
succeeded: boolean
}
export async function downloadRepository(
authToken: string,
owner: string,
@@ -128,84 +122,6 @@ export async function getDefaultBranch(
})
}
export async function tryGetRepositoryObjectFormat(
authToken: string,
owner: string,
repo: string,
baseUrl?: string,
ref?: string,
commit?: string
): Promise<RepositoryObjectFormatResult> {
try {
const commitFormat = getObjectFormat(commit)
if (commitFormat) {
return {format: commitFormat, succeeded: true}
}
const octokit = github.getOctokit(authToken, {
baseUrl: getServerApiUrl(baseUrl)
})
let branchName = getBranchName(ref)
let defaultBranch = ''
if (!branchName) {
const repository = await octokit.rest.repos.get({owner, repo})
defaultBranch = repository.data.default_branch
assert.ok(defaultBranch, 'default_branch cannot be empty')
branchName = defaultBranch
}
const branch = await octokit.rest.repos.getBranch({
owner,
repo,
branch: branchName
})
const branchFormat = getObjectFormat(branch.data.commit.sha)
if (branchFormat) {
return {
defaultBranch: defaultBranch || undefined,
format: branchFormat,
succeeded: true
}
}
core.debug('Unable to determine repository object format from commit SHA')
return {format: '', succeeded: false}
} catch (err) {
core.debug(
`Unable to determine repository object format: ${(err as any)?.message ?? err}`
)
return {format: '', succeeded: false}
}
}
function getBranchName(ref?: string): string {
if (!ref) {
return ''
}
const headsPrefix = 'refs/heads/'
if (ref.startsWith(headsPrefix)) {
return ref.substring(headsPrefix.length)
}
if (!ref.startsWith('refs/') && !getObjectFormat(ref)) {
return ref
}
return ''
}
function getObjectFormat(sha?: string): string {
if (/^[0-9a-fA-F]{64}$/.test(sha || '')) {
return 'sha256'
}
if (/^[0-9a-fA-F]{40}$/.test(sha || '')) {
return 'sha1'
}
return ''
}
async function downloadArchive(
authToken: string,
owner: string,