Sfoglia il codice sorgente

Fix checkout init for SHA-256 repositories (#2439)

* Fix checkout init for SHA-256 repositories

* Remove unused object format result field
Yashwanth Anantharaju 3 settimane fa
parent
commit
1cce3390c2

+ 53 - 0
__test__/git-command-manager.test.ts

@@ -378,6 +378,59 @@ describe('Test fetchDepth and fetchTags options', () => {
   })
 })
 
+describe('repository initialization object format', () => {
+  beforeEach(async () => {
+    jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
+    jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
+  })
+
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })
+
+  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())

+ 98 - 0
__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'
+    )
+  })
+})

+ 52 - 3
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, {

+ 9 - 3
src/git-command-manager.ts

@@ -43,7 +43,7 @@ export interface IGitCommandManager {
   getDefaultBranch(repositoryUrl: string): Promise<string>
   getSubmoduleConfigPaths(recursive: boolean): Promise<string[]>
   getWorkingDirectory(): string
-  init(): Promise<void>
+  init(objectFormat?: string): Promise<void>
   isDetached(): Promise<boolean>
   lfsFetch(ref: string): Promise<void>
   lfsInstall(): Promise<void>
@@ -364,8 +364,14 @@ class GitCommandManager {
     return this.workingDirectory
   }
 
-  async init(): Promise<void> {
-    await this.execGit(['init', 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 isDetached(): Promise<boolean> {

+ 18 - 1
src/git-source-provider.ts

@@ -109,8 +109,25 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
     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()
     }

+ 52 - 0
src/github-api-helper.ts

@@ -11,6 +11,11 @@ import {getServerApiUrl} from './url-helper'
 
 const IS_WINDOWS = process.platform === 'win32'
 
+export interface RepositoryObjectFormatResult {
+  format: string
+  succeeded: boolean
+}
+
 export async function downloadRepository(
   authToken: string,
   owner: string,
@@ -122,6 +127,53 @@ export async function getDefaultBranch(
   })
 }
 
+export async function tryGetRepositoryObjectFormat(
+  authToken: string,
+  owner: string,
+  repo: string,
+  baseUrl?: string,
+  commit?: string
+): Promise<RepositoryObjectFormatResult> {
+  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,