Compare commits

..

1 Commits

Author SHA1 Message Date
Thomas Sprayberry ddafdc4666
Merge 56a41f36e5 into f548e57e54 2026-09-15 04:29:44 -04:00
5 changed files with 13 additions and 170 deletions

View File

@ -35,11 +35,7 @@ jest.unstable_mockModule('../src/git-command-manager.js', () => ({
MinimumGitSparseCheckoutVersion: '2.28' MinimumGitSparseCheckoutVersion: '2.28'
})) }))
// downloadRepository returns the commit it downloaded: the one requested, or the one the ref resolved to. const mockDownloadRepository = jest.fn()
const resolvedCommitSha = 'abcdefabcdefabcdefabcdefabcdefabcdefabcd'
const mockDownloadRepository = jest.fn(
async (...args: any[]) => (args[4] as string) || resolvedCommitSha
)
jest.unstable_mockModule('../src/github-api-helper.js', () => ({ jest.unstable_mockModule('../src/github-api-helper.js', () => ({
downloadRepository: mockDownloadRepository, downloadRepository: mockDownloadRepository,
getDefaultBranch: jest.fn(async () => 'refs/heads/main'), getDefaultBranch: jest.fn(async () => 'refs/heads/main'),
@ -164,7 +160,7 @@ describe('git-source-provider tests', () => {
expect(mockSetOutput).toHaveBeenCalledWith('commit', commitSha) expect(mockSetOutput).toHaveBeenCalledWith('commit', commitSha)
}) })
it('sets the commit output to the resolved commit when downloading using the REST API without a commit', async () => { it('sets the commit output when downloading using the REST API without a commit', async () => {
// Arrange // Arrange
mockCreateCommandManager.mockImplementation(async () => { mockCreateCommandManager.mockImplementation(async () => {
throw new Error('Git is not installed') throw new Error('Git is not installed')
@ -177,10 +173,10 @@ describe('git-source-provider tests', () => {
// Assert // Assert
expect(mockDownloadRepository).toHaveBeenCalled() expect(mockDownloadRepository).toHaveBeenCalled()
expect(mockSetOutput).toHaveBeenCalledWith('commit', resolvedCommitSha) expect(mockSetOutput).toHaveBeenCalledWith('commit', '')
}) })
it('sets the commit output to the resolved commit when downloading a ref that is not a SHA', async () => { it('sets an empty commit output when downloading a ref that is not a SHA', async () => {
// Arrange // Arrange
mockCreateCommandManager.mockImplementation(async () => { mockCreateCommandManager.mockImplementation(async () => {
throw new Error('Git is not installed') throw new Error('Git is not installed')
@ -194,7 +190,7 @@ describe('git-source-provider tests', () => {
// Assert // Assert
expect(mockDownloadRepository).toHaveBeenCalled() expect(mockDownloadRepository).toHaveBeenCalled()
expect(mockSetOutput).toHaveBeenCalledWith('commit', resolvedCommitSha) expect(mockSetOutput).toHaveBeenCalledWith('commit', undefined)
}) })
it('sets the commit output when downloading a SHA-256 object format repository', async () => { it('sets the commit output when downloading a SHA-256 object format repository', async () => {

View File

@ -2,11 +2,10 @@ import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals'
// Mock @actions/core // Mock @actions/core
const mockDebug = jest.fn() const mockDebug = jest.fn()
const mockWarning = jest.fn()
jest.unstable_mockModule('@actions/core', () => ({ jest.unstable_mockModule('@actions/core', () => ({
debug: mockDebug, debug: mockDebug,
info: jest.fn(), info: jest.fn(),
warning: mockWarning, warning: jest.fn(),
error: jest.fn() error: jest.fn()
})) }))
@ -16,11 +15,6 @@ jest.unstable_mockModule('@actions/github', () => ({
getOctokit: mockGetOctokit getOctokit: mockGetOctokit
})) }))
// Run retried actions once, so a failing lookup does not wait out the backoff
jest.unstable_mockModule('../src/retry-helper.js', () => ({
execute: async (action: () => Promise<unknown>) => await action()
}))
// Dynamic imports after mocking // Dynamic imports after mocking
const githubApiHelper = await import('../src/github-api-helper.js') const githubApiHelper = await import('../src/github-api-helper.js')
@ -116,74 +110,3 @@ describe('github-api-helper object format', () => {
) )
}) })
}) })
describe('github-api-helper commit resolution', () => {
const sha1 = '0123456789abcdef0123456789abcdef01234567'
const sha256 =
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
let request: jest.Mock<any>
function mockCommitApi(data: unknown): void {
request = jest.fn(async () => ({data}))
mockGetOctokit.mockReturnValue({request} as any)
}
afterEach(() => {
jest.clearAllMocks()
})
it('resolves a ref to the SHA of its commit', async () => {
mockCommitApi(`${sha1}\n`)
await expect(
githubApiHelper.tryGetCommitSha(
'token',
'owner',
'repo',
'refs/heads/dev'
)
).resolves.toBe(sha1)
expect(request).toHaveBeenCalledWith(
'GET /repos/{owner}/{repo}/commits/{ref}',
{
owner: 'owner',
repo: 'repo',
ref: 'refs/heads/dev',
headers: {accept: 'application/vnd.github.sha'}
}
)
})
it('resolves a ref in a SHA-256 repository', async () => {
mockCommitApi(sha256)
await expect(
githubApiHelper.tryGetCommitSha('token', 'owner', 'repo', 'v1.0.0')
).resolves.toBe(sha256)
})
it('returns an empty string and warns when the response is not a SHA', async () => {
mockCommitApi('<html>not a sha</html>')
await expect(
githubApiHelper.tryGetCommitSha('token', 'owner', 'repo', 'main')
).resolves.toBe('')
expect(mockWarning).toHaveBeenCalledWith(
expect.stringContaining("Unable to resolve the commit for 'main'")
)
})
it('returns an empty string and warns when the lookup fails', async () => {
request = jest.fn(async () => {
throw new Error('Not Found')
})
mockGetOctokit.mockReturnValue({request} as any)
await expect(
githubApiHelper.tryGetCommitSha('token', 'owner', 'repo', 'gone')
).resolves.toBe('')
expect(mockWarning).toHaveBeenCalledWith(
"Unable to resolve the commit for 'gone': Not Found"
)
})
})

36
dist/index.js vendored
View File

@ -41324,11 +41324,6 @@ async function downloadRepository(authToken, owner, repo, ref, commit, repositor
info('Determining the default branch'); info('Determining the default branch');
ref = await getDefaultBranch(authToken, owner, repo, baseUrl); ref = await getDefaultBranch(authToken, owner, repo, baseUrl);
} }
// Without a commit, resolve the ref to one and download that commit, so the
// content and the returned SHA agree even if the ref moves in the meantime.
if (!commit) {
commit = await tryGetCommitSha(authToken, owner, repo, ref, baseUrl);
}
// Download the archive // Download the archive
let archiveData = await execute(async () => { let archiveData = await execute(async () => {
info('Downloading the archive'); info('Downloading the archive');
@ -41372,30 +41367,6 @@ async function downloadRepository(authToken, owner, repo, ref, commit, repositor
} }
} }
await rmRF(extractPath); await rmRF(extractPath);
return commit;
}
/**
* Resolves a ref to the SHA of the commit it points at. Returns an empty string
* when the ref cannot be resolved, so the download falls back to the ref itself.
*/
async function tryGetCommitSha(authToken, owner, repo, ref, baseUrl) {
try {
return await execute(async () => {
info(`Resolving the commit for '${ref}'`);
const octokit = getOctokit(authToken, {
baseUrl: getServerApiUrl(baseUrl)
});
const response = await octokit.request('GET /repos/{owner}/{repo}/commits/{ref}', { owner, repo, ref, headers: { accept: 'application/vnd.github.sha' } });
const sha = String(response.data).trim();
external_assert_.ok(/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(sha), `Unexpected commit SHA '${sha}'`);
info(`Resolved '${ref}' to ${sha}`);
return sha;
});
}
catch (err) {
warning(`Unable to resolve the commit for '${ref}': ${err?.message ?? err}`);
return '';
}
} }
/** /**
* Looks up the default branch name * Looks up the default branch name
@ -41783,11 +41754,10 @@ async function getSource(settings) {
else if (settings.sshKey) { else if (settings.sshKey) {
throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${MinimumGitVersion} or higher to the PATH.`); throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${MinimumGitVersion} or higher to the PATH.`);
} }
const downloadedCommit = await downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath, settings.githubServerUrl); await downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath, settings.githubServerUrl);
// Set the commit output. The REST API fallback does not create a local // Set the commit output. The REST API fallback does not create a local
// Git repository, so the SHA is the commit that was downloaded: the one // Git repository, so the SHA can only come from the resolved settings.
// requested, or the one the ref resolved to. setOutput('commit', settings.commit);
setOutput('commit', downloadedCommit);
return; return;
} }
// Save state for POST action // Save state for POST action

View File

@ -90,7 +90,7 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
) )
} }
const downloadedCommit = await githubApiHelper.downloadRepository( await githubApiHelper.downloadRepository(
settings.authToken, settings.authToken,
settings.repositoryOwner, settings.repositoryOwner,
settings.repositoryName, settings.repositoryName,
@ -101,9 +101,8 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
) )
// Set the commit output. The REST API fallback does not create a local // Set the commit output. The REST API fallback does not create a local
// Git repository, so the SHA is the commit that was downloaded: the one // Git repository, so the SHA can only come from the resolved settings.
// requested, or the one the ref resolved to. core.setOutput('commit', settings.commit)
core.setOutput('commit', downloadedCommit)
return return
} }

View File

@ -24,19 +24,13 @@ export async function downloadRepository(
commit: string, commit: string,
repositoryPath: string, repositoryPath: string,
baseUrl?: string baseUrl?: string
): Promise<string> { ): Promise<void> {
// Determine the default branch // Determine the default branch
if (!ref && !commit) { if (!ref && !commit) {
core.info('Determining the default branch') core.info('Determining the default branch')
ref = await getDefaultBranch(authToken, owner, repo, baseUrl) ref = await getDefaultBranch(authToken, owner, repo, baseUrl)
} }
// Without a commit, resolve the ref to one and download that commit, so the
// content and the returned SHA agree even if the ref moves in the meantime.
if (!commit) {
commit = await tryGetCommitSha(authToken, owner, repo, ref, baseUrl)
}
// Download the archive // Download the archive
let archiveData = await retryHelper.execute(async () => { let archiveData = await retryHelper.execute(async () => {
core.info('Downloading the archive') core.info('Downloading the archive')
@ -85,45 +79,6 @@ export async function downloadRepository(
} }
} }
await io.rmRF(extractPath) await io.rmRF(extractPath)
return commit
}
/**
* Resolves a ref to the SHA of the commit it points at. Returns an empty string
* when the ref cannot be resolved, so the download falls back to the ref itself.
*/
export async function tryGetCommitSha(
authToken: string,
owner: string,
repo: string,
ref: string,
baseUrl?: string
): Promise<string> {
try {
return await retryHelper.execute(async () => {
core.info(`Resolving the commit for '${ref}'`)
const octokit = github.getOctokit(authToken, {
baseUrl: getServerApiUrl(baseUrl)
})
const response = await octokit.request(
'GET /repos/{owner}/{repo}/commits/{ref}',
{owner, repo, ref, headers: {accept: 'application/vnd.github.sha'}}
)
const sha = String(response.data).trim()
assert.ok(
/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(sha),
`Unexpected commit SHA '${sha}'`
)
core.info(`Resolved '${ref}' to ${sha}`)
return sha
})
} catch (err) {
core.warning(
`Unable to resolve the commit for '${ref}': ${(err as any)?.message ?? err}`
)
return ''
}
} }
/** /**