Skip to content

Commit f279b42

Browse files
committed
Fix checkout init for SHA-256 repositories
1 parent 900f221 commit f279b42

8 files changed

Lines changed: 489 additions & 7 deletions

__test__/git-auth-helper.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ async function setup(testName: string): Promise<void> {
11031103
),
11041104
tryDisableAutomaticGarbageCollection: jest.fn(),
11051105
tryGetFetchUrl: jest.fn(),
1106+
tryGetObjectFormat: jest.fn(async () => ({format: '', succeeded: true})),
11061107
tryGetConfigValues: jest.fn(
11071108
async (
11081109
key: string,

__test__/git-command-manager.test.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,169 @@ describe('Test fetchDepth and fetchTags options', () => {
378378
})
379379
})
380380

381+
describe('repository object format', () => {
382+
beforeEach(async () => {
383+
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
384+
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
385+
})
386+
387+
afterEach(() => {
388+
jest.restoreAllMocks()
389+
})
390+
391+
it('detects SHA-256 from a 64-character HEAD oid', async () => {
392+
mockExec.mockImplementation((path, args, options) => {
393+
if (args.includes('version')) {
394+
options.listeners.stdout(Buffer.from('git version 2.50.1'))
395+
}
396+
397+
if (args.includes('ls-remote')) {
398+
options.listeners.stdout(
399+
Buffer.from(
400+
'ref: refs/heads/main\tHEAD\n' +
401+
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92\tHEAD\n'
402+
)
403+
)
404+
}
405+
406+
return 0
407+
})
408+
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
409+
410+
git = await commandManager.createCommandManager('test', false, false)
411+
412+
const objectFormat = await git.tryGetObjectFormat(
413+
'https://github.com/example/repo'
414+
)
415+
416+
expect(objectFormat).toEqual({format: 'sha256', succeeded: true})
417+
expect(mockExec).toHaveBeenCalledWith(
418+
expect.any(String),
419+
[
420+
'-c',
421+
'protocol.version=2',
422+
'ls-remote',
423+
'--quiet',
424+
'--exit-code',
425+
'--symref',
426+
'https://github.com/example/repo',
427+
'HEAD'
428+
],
429+
expect.objectContaining({
430+
ignoreReturnCode: true,
431+
silent: true
432+
})
433+
)
434+
})
435+
436+
it('detects SHA-1 from a 40-character HEAD oid', async () => {
437+
mockExec.mockImplementation((path, args, options) => {
438+
if (args.includes('version')) {
439+
options.listeners.stdout(Buffer.from('git version 2.50.1'))
440+
}
441+
442+
if (args.includes('ls-remote')) {
443+
options.listeners.stdout(
444+
Buffer.from(
445+
'ref: refs/heads/main\tHEAD\n' +
446+
'c988866043f035e6a46509872215f91d879044c9\tHEAD\n'
447+
)
448+
)
449+
}
450+
451+
return 0
452+
})
453+
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
454+
455+
git = await commandManager.createCommandManager('test', false, false)
456+
457+
await expect(
458+
git.tryGetObjectFormat('https://github.com/example/repo')
459+
).resolves.toEqual({format: 'sha1', succeeded: true})
460+
})
461+
462+
it('returns unsuccessful when HEAD does not resolve to a recognized object id', async () => {
463+
mockExec.mockImplementation((path, args, options) => {
464+
if (args.includes('version')) {
465+
options.listeners.stdout(Buffer.from('git version 2.50.1'))
466+
}
467+
468+
if (args.includes('ls-remote')) {
469+
options.listeners.stdout(Buffer.from('ref: refs/heads/main\tHEAD\n'))
470+
}
471+
472+
return 0
473+
})
474+
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
475+
476+
git = await commandManager.createCommandManager('test', false, false)
477+
478+
await expect(
479+
git.tryGetObjectFormat('https://github.com/example/repo')
480+
).resolves.toEqual({format: '', succeeded: false})
481+
})
482+
483+
it('returns unsuccessful when object format detection cannot reach the remote', async () => {
484+
mockExec.mockImplementation((path, args, options) => {
485+
if (args.includes('version')) {
486+
options.listeners.stdout(Buffer.from('git version 2.50.1'))
487+
return 0
488+
}
489+
490+
return 128
491+
})
492+
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
493+
494+
git = await commandManager.createCommandManager('test', false, false)
495+
496+
await expect(
497+
git.tryGetObjectFormat('https://github.com/example/repo')
498+
).resolves.toEqual({format: '', succeeded: false})
499+
})
500+
501+
it('initializes SHA-256 repositories with the matching object format', async () => {
502+
mockExec.mockImplementation((path, args, options) => {
503+
if (args.includes('version')) {
504+
options.listeners.stdout(Buffer.from('git version 2.50.1'))
505+
}
506+
507+
return 0
508+
})
509+
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
510+
511+
git = await commandManager.createCommandManager('test', false, false)
512+
513+
await git.init('sha256')
514+
515+
expect(mockExec).toHaveBeenCalledWith(
516+
expect.any(String),
517+
['init', '--object-format=sha256', 'test'],
518+
expect.any(Object)
519+
)
520+
})
521+
522+
it('initializes SHA-1 repositories with existing default arguments', async () => {
523+
mockExec.mockImplementation((path, args, options) => {
524+
if (args.includes('version')) {
525+
options.listeners.stdout(Buffer.from('git version 2.50.1'))
526+
}
527+
528+
return 0
529+
})
530+
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
531+
532+
git = await commandManager.createCommandManager('test', false, false)
533+
534+
await git.init('sha1')
535+
536+
expect(mockExec).toHaveBeenCalledWith(
537+
expect.any(String),
538+
['init', 'test'],
539+
expect.any(Object)
540+
)
541+
})
542+
})
543+
381544
describe('git user-agent with orchestration ID', () => {
382545
beforeEach(async () => {
383546
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())

__test__/git-directory-helper.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ async function setup(testName: string): Promise<void> {
501501
await fs.promises.stat(path.join(repositoryPath, '.git'))
502502
return repositoryUrl
503503
}),
504+
tryGetObjectFormat: jest.fn(async () => ({format: '', succeeded: true})),
504505
tryGetConfigValues: jest.fn(),
505506
tryGetConfigKeys: jest.fn(),
506507
tryReset: jest.fn(async () => {

__test__/github-api-helper.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as core from '@actions/core'
2+
import * as github from '@actions/github'
3+
import * as githubApiHelper from '../lib/github-api-helper'
4+
5+
describe('github-api-helper object format', () => {
6+
let getOctokitSpy: jest.SpyInstance
7+
let debugSpy: jest.SpyInstance
8+
let repoGet: jest.Mock
9+
let branchGet: jest.Mock
10+
11+
function mockObjectFormatApi(defaultBranch: string, commitSha: string): void {
12+
repoGet = jest.fn(async () => ({
13+
data: {
14+
default_branch: defaultBranch
15+
}
16+
}))
17+
branchGet = jest.fn(async () => ({
18+
data: {
19+
commit: {
20+
sha: commitSha
21+
}
22+
}
23+
}))
24+
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
25+
rest: {
26+
repos: {
27+
get: repoGet,
28+
getBranch: branchGet
29+
}
30+
}
31+
} as any)
32+
}
33+
34+
beforeEach(() => {
35+
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
36+
})
37+
38+
afterEach(() => {
39+
jest.restoreAllMocks()
40+
})
41+
42+
it('detects SHA-256 from the default branch commit SHA', async () => {
43+
const commitSha =
44+
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92'
45+
mockObjectFormatApi('main', commitSha)
46+
47+
await expect(
48+
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
49+
).resolves.toEqual({format: 'sha256', succeeded: true})
50+
51+
expect(getOctokitSpy).toHaveBeenCalledWith(
52+
'token',
53+
expect.objectContaining({baseUrl: 'https://api.github.com'})
54+
)
55+
expect(repoGet).toHaveBeenCalledWith({owner: 'owner', repo: 'repo'})
56+
expect(branchGet).toHaveBeenCalledWith({
57+
owner: 'owner',
58+
repo: 'repo',
59+
branch: 'main'
60+
})
61+
})
62+
63+
it('detects SHA-1 from the default branch commit SHA', async () => {
64+
mockObjectFormatApi('main', 'c988866043f035e6a46509872215f91d879044c9')
65+
66+
await expect(
67+
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
68+
).resolves.toEqual({format: 'sha1', succeeded: true})
69+
})
70+
71+
it('returns unsuccessful when the default branch commit SHA is not recognized', async () => {
72+
mockObjectFormatApi('main', 'not-a-sha')
73+
74+
await expect(
75+
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
76+
).resolves.toEqual({format: '', succeeded: false})
77+
expect(debugSpy).toHaveBeenCalledWith(
78+
'Unable to determine repository object format from default branch'
79+
)
80+
})
81+
82+
it('returns unsuccessful when the repository API lookup fails', async () => {
83+
repoGet = jest.fn(async () => {
84+
throw new Error('not found')
85+
})
86+
branchGet = jest.fn()
87+
jest.spyOn(github, 'getOctokit').mockReturnValue({
88+
rest: {
89+
repos: {
90+
get: repoGet,
91+
getBranch: branchGet
92+
}
93+
}
94+
} as any)
95+
96+
await expect(
97+
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
98+
).resolves.toEqual({format: '', succeeded: false})
99+
expect(branchGet).not.toHaveBeenCalled()
100+
expect(debugSpy).toHaveBeenCalledWith(
101+
'Unable to determine repository object format: not found'
102+
)
103+
})
104+
})

0 commit comments

Comments
 (0)