mirror of https://github.com/actions/checkout.git
Merge dbd3a6024b into f548e57e54
This commit is contained in:
commit
f99e4420e1
|
|
@ -0,0 +1,320 @@
|
||||||
|
import {jest, describe, it, expect, beforeEach} from '@jest/globals'
|
||||||
|
|
||||||
|
// Mock @actions/core before loading git-source-provider
|
||||||
|
const mockSetOutput = jest.fn()
|
||||||
|
jest.unstable_mockModule('@actions/core', () => ({
|
||||||
|
setOutput: mockSetOutput,
|
||||||
|
setSecret: jest.fn(),
|
||||||
|
setFailed: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warning: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
debug: jest.fn(),
|
||||||
|
startGroup: jest.fn(),
|
||||||
|
endGroup: jest.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.unstable_mockModule('@actions/io', () => ({
|
||||||
|
cp: jest.fn(),
|
||||||
|
mkdirP: jest.fn(),
|
||||||
|
mv: jest.fn(),
|
||||||
|
rmRF: jest.fn(),
|
||||||
|
which: jest.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../src/fs-helper.js', () => ({
|
||||||
|
directoryExistsSync: jest.fn(() => true),
|
||||||
|
existsSync: jest.fn(() => true),
|
||||||
|
fileExistsSync: jest.fn(() => false)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockCreateCommandManager = jest.fn()
|
||||||
|
jest.unstable_mockModule('../src/git-command-manager.js', () => ({
|
||||||
|
createCommandManager: mockCreateCommandManager,
|
||||||
|
MinimumGitVersion: '2.18',
|
||||||
|
MinimumGitSparseCheckoutVersion: '2.28'
|
||||||
|
}))
|
||||||
|
|
||||||
|
// downloadRepository returns the commit it downloaded: the one requested, or the one the ref resolved to.
|
||||||
|
const resolvedCommitSha = 'abcdefabcdefabcdefabcdefabcdefabcdefabcd'
|
||||||
|
const mockDownloadRepository = jest.fn(
|
||||||
|
async (...args: any[]) => (args[4] as string) || resolvedCommitSha
|
||||||
|
)
|
||||||
|
jest.unstable_mockModule('../src/github-api-helper.js', () => ({
|
||||||
|
downloadRepository: mockDownloadRepository,
|
||||||
|
getDefaultBranch: jest.fn(async () => 'refs/heads/main'),
|
||||||
|
tryGetRepositoryObjectFormat: jest.fn(async () => ({
|
||||||
|
format: 'sha1',
|
||||||
|
succeeded: true
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockCreateAuthHelper = jest.fn(() => ({
|
||||||
|
configureAuth: jest.fn(),
|
||||||
|
configureGlobalAuth: jest.fn(),
|
||||||
|
configureSubmoduleAuth: jest.fn(),
|
||||||
|
configureTempGlobalConfig: jest.fn(),
|
||||||
|
removeAuth: jest.fn(),
|
||||||
|
removeGlobalAuth: jest.fn(),
|
||||||
|
removeGlobalConfig: jest.fn()
|
||||||
|
}))
|
||||||
|
jest.unstable_mockModule('../src/git-auth-helper.js', () => ({
|
||||||
|
createAuthHelper: mockCreateAuthHelper
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../src/git-directory-helper.js', () => ({
|
||||||
|
prepareExistingDirectory: jest.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../src/ref-helper.js', () => ({
|
||||||
|
checkCommitInfo: jest.fn(),
|
||||||
|
getCheckoutInfo: jest.fn(async () => ({
|
||||||
|
ref: 'main',
|
||||||
|
startPoint: 'refs/remotes/origin/main'
|
||||||
|
})),
|
||||||
|
getRefSpec: jest.fn(() => ['+refs/heads/main:refs/remotes/origin/main']),
|
||||||
|
getRefSpecForAllHistory: jest.fn(() => [
|
||||||
|
'+refs/heads/main*:refs/remotes/origin/main*'
|
||||||
|
]),
|
||||||
|
testRef: jest.fn(async () => true)
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../src/state-helper.js', () => ({
|
||||||
|
setRepositoryPath: jest.fn(),
|
||||||
|
setSafeDirectory: jest.fn(),
|
||||||
|
IsPost: false,
|
||||||
|
PostSetSafeDirectory: false,
|
||||||
|
RepositoryPath: ''
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Dynamic imports after mocking
|
||||||
|
const gitSourceProvider = await import('../src/git-source-provider.js')
|
||||||
|
type IGitSourceSettings =
|
||||||
|
import('../src/git-source-settings.js').IGitSourceSettings
|
||||||
|
|
||||||
|
const commitSha = '1234567890123456789012345678901234567890'
|
||||||
|
const commitSha256 =
|
||||||
|
'1234567890123456789012345678901234567890123456789012345678901234'
|
||||||
|
|
||||||
|
function getSettings(): IGitSourceSettings {
|
||||||
|
return {
|
||||||
|
allowUnsafePrCheckout: false,
|
||||||
|
authToken: 'token',
|
||||||
|
clean: true,
|
||||||
|
commit: commitSha,
|
||||||
|
fetchDepth: 1,
|
||||||
|
fetchTags: false,
|
||||||
|
filter: undefined,
|
||||||
|
githubServerUrl: undefined,
|
||||||
|
lfs: false,
|
||||||
|
nestedSubmodules: false,
|
||||||
|
persistCredentials: true,
|
||||||
|
ref: 'refs/heads/main',
|
||||||
|
repositoryName: 'my-repo',
|
||||||
|
repositoryOwner: 'my-org',
|
||||||
|
repositoryPath: '/home/runner/work/my-repo/my-repo',
|
||||||
|
setSafeDirectory: false,
|
||||||
|
showProgress: false,
|
||||||
|
// Matches getInputs(), which leaves sparseCheckout undefined when the input is empty
|
||||||
|
sparseCheckout: undefined,
|
||||||
|
sparseCheckoutConeMode: true,
|
||||||
|
sshKey: '',
|
||||||
|
sshKnownHosts: '',
|
||||||
|
sshStrict: true,
|
||||||
|
sshUser: '',
|
||||||
|
submodules: false,
|
||||||
|
workflowOrganizationId: undefined
|
||||||
|
} as unknown as IGitSourceSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
// A minimal git command manager, for the cases that do not fall back to the REST API.
|
||||||
|
function getGitCommandManager(): any {
|
||||||
|
return {
|
||||||
|
checkout: jest.fn(),
|
||||||
|
config: jest.fn(),
|
||||||
|
disableSparseCheckout: jest.fn(),
|
||||||
|
init: jest.fn(),
|
||||||
|
log1: jest.fn(async (format?: string) =>
|
||||||
|
format ? `${commitSha}\n` : `commit ${commitSha}\n`
|
||||||
|
),
|
||||||
|
remoteAdd: jest.fn(),
|
||||||
|
fetch: jest.fn(),
|
||||||
|
tryDisableAutomaticGarbageCollection: jest.fn(async () => true),
|
||||||
|
version: jest.fn(async () => ({checkMinimum: () => false}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('git-source-provider tests', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the commit output when downloading using the REST API', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDownloadRepository).toHaveBeenCalled()
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith('commit', commitSha)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the commit output to the resolved commit when downloading using the REST API without a commit', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
settings.commit = ''
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDownloadRepository).toHaveBeenCalled()
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith('commit', resolvedCommitSha)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the commit output to the resolved commit when downloading a ref that is not a SHA', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
// getInputs() leaves commit undefined when a non-SHA ref is given for another repository
|
||||||
|
settings.commit = undefined as unknown as string
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDownloadRepository).toHaveBeenCalled()
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith('commit', resolvedCommitSha)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the commit output when downloading a SHA-256 object format repository', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
// getInputs() accepts a 64 hex character ref as a commit, for sha256 repositories
|
||||||
|
settings.commit = commitSha256
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDownloadRepository).toHaveBeenCalledWith(
|
||||||
|
settings.authToken,
|
||||||
|
settings.repositoryOwner,
|
||||||
|
settings.repositoryName,
|
||||||
|
settings.ref,
|
||||||
|
commitSha256,
|
||||||
|
settings.repositoryPath,
|
||||||
|
settings.githubServerUrl
|
||||||
|
)
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith('commit', commitSha256)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the commit output after the repository has been downloaded', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith('commit', commitSha)
|
||||||
|
expect(mockSetOutput.mock.invocationCallOrder[0]).toBeGreaterThan(
|
||||||
|
mockDownloadRepository.mock.invocationCallOrder[0]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not set the commit output when the REST API download fails (control)', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
mockDownloadRepository.mockImplementation(async () => {
|
||||||
|
throw new Error('Download failed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await expect(gitSourceProvider.getSource(settings)).rejects.toThrow(
|
||||||
|
'Download failed'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockSetOutput).not.toHaveBeenCalledWith(
|
||||||
|
'commit',
|
||||||
|
expect.anything() as unknown as string
|
||||||
|
)
|
||||||
|
mockDownloadRepository.mockReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not download or set the commit output when an input is not supported by the REST API fallback (control)', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const submoduleSettings = getSettings()
|
||||||
|
submoduleSettings.submodules = true
|
||||||
|
const sshKeySettings = getSettings()
|
||||||
|
sshKeySettings.sshKey = 'ssh-key'
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await expect(
|
||||||
|
gitSourceProvider.getSource(submoduleSettings)
|
||||||
|
).rejects.toThrow(`Input 'submodules' not supported`)
|
||||||
|
await expect(gitSourceProvider.getSource(sshKeySettings)).rejects.toThrow(
|
||||||
|
`Input 'ssh-key' not supported`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDownloadRepository).not.toHaveBeenCalled()
|
||||||
|
expect(mockSetOutput).not.toHaveBeenCalledWith(
|
||||||
|
'commit',
|
||||||
|
expect.anything() as unknown as string
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the commit output from git when git is available (control)', async () => {
|
||||||
|
// Arrange
|
||||||
|
const git = getGitCommandManager()
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => git)
|
||||||
|
const settings = getSettings()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDownloadRepository).not.toHaveBeenCalled()
|
||||||
|
expect(git.checkout).toHaveBeenCalled()
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith('commit', commitSha)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not configure auth on the REST API fallback path (control)', async () => {
|
||||||
|
// Arrange
|
||||||
|
mockCreateCommandManager.mockImplementation(async () => {
|
||||||
|
throw new Error('Git is not installed')
|
||||||
|
})
|
||||||
|
const settings = getSettings()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await gitSourceProvider.getSource(settings)
|
||||||
|
|
||||||
|
// Assert: the fallback returns with authHelper still null, so the finally
|
||||||
|
// block removes nothing. The added setOutput call does not change that.
|
||||||
|
expect(mockCreateAuthHelper).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -2,10 +2,11 @@ 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: jest.fn(),
|
warning: mockWarning,
|
||||||
error: jest.fn()
|
error: jest.fn()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -15,6 +16,11 @@ 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')
|
||||||
|
|
||||||
|
|
@ -110,3 +116,74 @@ 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"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -41324,6 +41324,11 @@ 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');
|
||||||
|
|
@ -41367,6 +41372,30 @@ 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
|
||||||
|
|
@ -41754,7 +41783,11 @@ 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.`);
|
||||||
}
|
}
|
||||||
await downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath, settings.githubServerUrl);
|
const downloadedCommit = 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
|
||||||
|
// Git repository, so the SHA is the commit that was downloaded: the one
|
||||||
|
// requested, or the one the ref resolved to.
|
||||||
|
setOutput('commit', downloadedCommit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Save state for POST action
|
// Save state for POST action
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await githubApiHelper.downloadRepository(
|
const downloadedCommit = await githubApiHelper.downloadRepository(
|
||||||
settings.authToken,
|
settings.authToken,
|
||||||
settings.repositoryOwner,
|
settings.repositoryOwner,
|
||||||
settings.repositoryName,
|
settings.repositoryName,
|
||||||
|
|
@ -99,6 +99,11 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
||||||
settings.repositoryPath,
|
settings.repositoryPath,
|
||||||
settings.githubServerUrl
|
settings.githubServerUrl
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// requested, or the one the ref resolved to.
|
||||||
|
core.setOutput('commit', downloadedCommit)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,19 @@ export async function downloadRepository(
|
||||||
commit: string,
|
commit: string,
|
||||||
repositoryPath: string,
|
repositoryPath: string,
|
||||||
baseUrl?: string
|
baseUrl?: string
|
||||||
): Promise<void> {
|
): Promise<string> {
|
||||||
// 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')
|
||||||
|
|
@ -79,6 +85,45 @@ 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 ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue