diff --git a/__test__/github-api-helper.test.ts b/__test__/github-api-helper.test.ts new file mode 100644 index 0000000..6319e20 --- /dev/null +++ b/__test__/github-api-helper.test.ts @@ -0,0 +1,98 @@ +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 request: jest.Mock + + function mockHashAlgorithmApi(hashAlgorithm: string): void { + request = jest.fn(async () => ({ + data: { + hash_algorithm: hashAlgorithm + } + })) + getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({ + request + } as any) + } + + beforeEach(() => { + debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn()) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('detects SHA-256 from the repository hash algorithm endpoint', async () => { + mockHashAlgorithmApi('sha256') + + await expect( + githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo') + ).resolves.toEqual({format: 'sha256', succeeded: true}) + + expect(getOctokitSpy).toHaveBeenCalledWith( + 'token', + expect.objectContaining({baseUrl: 'https://api.github.com'}) + ) + expect(request).toHaveBeenCalledWith( + 'GET /repos/{owner}/{repo}/hash-algorithm', + {owner: 'owner', repo: 'repo'} + ) + }) + + it('detects SHA-1 from the repository hash algorithm endpoint', async () => { + mockHashAlgorithmApi('sha1') + + await expect( + githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo') + ).resolves.toEqual({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, + commitSha + ) + ).resolves.toEqual({format: 'sha256', succeeded: true}) + + expect(getOctokitSpy).not.toHaveBeenCalled() + }) + + it('returns unsuccessful when the hash algorithm endpoint value is not recognized', async () => { + mockHashAlgorithmApi('unknown') + + await expect( + githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo') + ).resolves.toEqual({format: '', succeeded: false}) + expect(debugSpy).toHaveBeenCalledWith( + 'Unable to determine repository object format from hash-algorithm endpoint' + ) + }) + + it('returns unsuccessful when the hash algorithm API lookup fails', async () => { + request = jest.fn(async () => { + throw new Error('not found') + }) + jest.spyOn(github, 'getOctokit').mockReturnValue({ + request + } as any) + + await expect( + githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo') + ).resolves.toEqual({format: '', succeeded: false}) + expect(debugSpy).toHaveBeenCalledWith( + 'Unable to determine repository object format from hash-algorithm endpoint: not found' + ) + }) +}) diff --git a/dist/index.js b/dist/index.js index 57729b2..906b59a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -896,9 +896,14 @@ class GitCommandManager { getWorkingDirectory() { return this.workingDirectory; } - init() { + init(objectFormat) { return __awaiter(this, void 0, void 0, function* () { - yield this.execGit(['init', this.workingDirectory]); + const args = ['init']; + if (objectFormat === 'sha256') { + args.push('--object-format=sha256'); + } + args.push(this.workingDirectory); + yield this.execGit(args); }); } isDetached() { @@ -1486,8 +1491,17 @@ function getSource(settings) { stateHelper.setRepositoryPath(settings.repositoryPath); // Initialize the repository if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) { + core.startGroup('Determining repository object format'); + const objectFormatResult = yield githubApiHelper.tryGetRepositoryObjectFormat(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.githubServerUrl, settings.commit); + const objectFormat = objectFormatResult.succeeded + ? objectFormatResult.format + : ''; + if (objectFormat === 'sha256') { + core.info('Detected SHA-256 repository object format'); + } + core.endGroup(); core.startGroup('Initializing the repository'); - yield git.init(); + yield git.init(objectFormat); yield git.remoteAdd('origin', repositoryUrl); core.endGroup(); } @@ -1810,6 +1824,7 @@ 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)); @@ -1911,6 +1926,40 @@ function getDefaultBranch(authToken, owner, repo, baseUrl) { })); }); } +function tryGetRepositoryObjectFormat(authToken, owner, repo, baseUrl, commit) { + return __awaiter(this, void 0, void 0, function* () { + var _a; + const commitFormat = getObjectFormat(commit); + if (commitFormat) { + return { format: commitFormat, succeeded: true }; + } + try { + const octokit = github.getOctokit(authToken, { + baseUrl: (0, url_helper_1.getServerApiUrl)(baseUrl) + }); + const response = yield octokit.request('GET /repos/{owner}/{repo}/hash-algorithm', { owner, repo }); + const hashAlgorithm = response.data.hash_algorithm; + if (hashAlgorithm === 'sha256' || hashAlgorithm === 'sha1') { + return { format: hashAlgorithm, succeeded: true }; + } + core.debug('Unable to determine repository object format from hash-algorithm endpoint'); + return { format: '', succeeded: false }; + } + catch (err) { + core.debug(`Unable to determine repository object format from hash-algorithm endpoint: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`); + return { format: '', succeeded: false }; + } + }); +} +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, { diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index f5ba40e..f1349ce 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -43,7 +43,7 @@ export interface IGitCommandManager { getDefaultBranch(repositoryUrl: string): Promise getSubmoduleConfigPaths(recursive: boolean): Promise getWorkingDirectory(): string - init(): Promise + init(objectFormat?: string): Promise isDetached(): Promise lfsFetch(ref: string): Promise lfsInstall(): Promise @@ -364,8 +364,14 @@ class GitCommandManager { return this.workingDirectory } - async init(): Promise { - await this.execGit(['init', this.workingDirectory]) + async init(objectFormat?: string): Promise { + const args = ['init'] + if (objectFormat === 'sha256') { + args.push('--object-format=sha256') + } + args.push(this.workingDirectory) + + await this.execGit(args) } async isDetached(): Promise { diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index ec87178..452d44e 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -109,8 +109,25 @@ export async function getSource(settings: IGitSourceSettings): Promise { if ( !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) ) { + core.startGroup('Determining repository object format') + const objectFormatResult = + await githubApiHelper.tryGetRepositoryObjectFormat( + settings.authToken, + settings.repositoryOwner, + settings.repositoryName, + settings.githubServerUrl, + settings.commit + ) + const objectFormat = objectFormatResult.succeeded + ? objectFormatResult.format + : '' + if (objectFormat === 'sha256') { + core.info('Detected SHA-256 repository object format') + } + core.endGroup() + core.startGroup('Initializing the repository') - await git.init() + await git.init(objectFormat) await git.remoteAdd('origin', repositoryUrl) core.endGroup() } diff --git a/src/github-api-helper.ts b/src/github-api-helper.ts index 1ff27c2..36a3d8a 100644 --- a/src/github-api-helper.ts +++ b/src/github-api-helper.ts @@ -11,6 +11,12 @@ 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, @@ -122,6 +128,53 @@ export async function getDefaultBranch( }) } +export async function tryGetRepositoryObjectFormat( + authToken: string, + owner: string, + repo: string, + baseUrl?: string, + commit?: string +): Promise { + const commitFormat = getObjectFormat(commit) + if (commitFormat) { + return {format: commitFormat, succeeded: true} + } + + try { + const octokit = github.getOctokit(authToken, { + baseUrl: getServerApiUrl(baseUrl) + }) + const response = await octokit.request( + 'GET /repos/{owner}/{repo}/hash-algorithm', + {owner, repo} + ) + const hashAlgorithm = response.data.hash_algorithm + if (hashAlgorithm === 'sha256' || hashAlgorithm === 'sha1') { + return {format: hashAlgorithm, succeeded: true} + } + + core.debug( + 'Unable to determine repository object format from hash-algorithm endpoint' + ) + return {format: '', succeeded: false} + } catch (err) { + core.debug( + `Unable to determine repository object format from hash-algorithm endpoint: ${(err as any)?.message ?? err}` + ) + return {format: '', succeeded: false} + } +} + +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,