1
0
Fork 0
mirror of https://github.com/actions/checkout.git synced 2026-05-06 18:07:35 +02:00
This commit is contained in:
b4u-mw 2026-05-05 08:41:56 +02:00 committed by GitHub
commit c9ff73d6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 3510 additions and 34 deletions

View file

@ -1047,8 +1047,10 @@ async function setup(testName: string): Promise<void> {
lfsFetch: jest.fn(),
lfsInstall: jest.fn(),
log1: jest.fn(),
referenceAdd: jest.fn(),
remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
execGit: jest.fn(),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn((name: string, value: string) => {
git.env[name] = value
@ -1157,6 +1159,7 @@ async function setup(testName: string): Promise<void> {
sparseCheckout: [],
sparseCheckoutConeMode: true,
fetchDepth: 1,
fetchDepthExplicit: false,
fetchTags: false,
showProgress: true,
lfs: false,
@ -1173,7 +1176,8 @@ async function setup(testName: string): Promise<void> {
sshUser: '',
workflowOrganizationId: 123456,
setSafeDirectory: true,
githubServerUrl: githubServerUrl
githubServerUrl: githubServerUrl,
referenceCache: ''
}
}

View file

@ -0,0 +1,109 @@
import * as path from 'path'
import * as fs from 'fs'
import * as io from '@actions/io'
import { GitCacheHelper } from '../src/git-cache-helper'
import { IGitCommandManager } from '../src/git-command-manager'
describe('GitCacheHelper', () => {
let cacheHelper: GitCacheHelper
let mockGit: jest.Mocked<IGitCommandManager>
const cacheDir = path.join(__dirname, 'test-cache')
beforeEach(async () => {
cacheHelper = new GitCacheHelper(cacheDir)
mockGit = {
execGit: jest.fn().mockImplementation(async (args) => {
// If git clone is called, simulate creating the destination dir
if (args && args.includes('clone')) {
const dest = args.find((a: string) => a.includes('.tmp.'));
if (dest) {
await io.mkdirP(dest);
} else {
console.log('No .tmp. found in args:', args);
}
}
return { exitCode: 0, stdout: '', stderr: '' };
}),
gitEnv: {}
} as any
await io.mkdirP(cacheDir)
})
afterEach(async () => {
await io.rmRF(cacheDir)
})
it('generates a consistent, short, and safe cache directory name', () => {
const url1 = 'https://github.com/mwyraz/forgejo-actions-checkout.git'
const name1 = (cacheHelper as any).generateCacheDirName(url1)
// Check structure: safe string + hash
expect(name1).toMatch(/^https___github_com_mwyraz_forgejo_actions_checkout_git_[0-9a-f]{8}\.git$/)
// Same URL should produce the same directory name
const url1_duplicate = 'https://github.com/mwyraz/forgejo-actions-checkout.git'
expect((cacheHelper as any).generateCacheDirName(url1_duplicate)).toBe(name1)
// Different URL should produce a different directory name
const url2 = 'https://github.com/mwyraz/forgejo-actions-checkout-other.git'
expect((cacheHelper as any).generateCacheDirName(url2)).not.toBe(name1)
// SSH URL
const url3 = 'git@github.com:auth/repo.git'
const name3 = (cacheHelper as any).generateCacheDirName(url3)
expect(name3).toMatch(/^git_github_com_auth_repo_git_[0-9a-f]{8}\.git$/)
// Unclean URLs
const url4 = 'https://github.com/foo/bar.git?v=1'
const name4 = (cacheHelper as any).generateCacheDirName(url4)
expect(name4).toMatch(/^https___github_com_foo_bar_git_v_1_[0-9a-f]{8}\.git$/)
})
it('sets up a cache directory if it does not exist', async () => {
const repositoryUrl = 'https://github.com/mwyraz/test-repo.git'
const resultPath = await cacheHelper.setupCache(mockGit, repositoryUrl)
const expectedName = (cacheHelper as any).generateCacheDirName(repositoryUrl)
expect(resultPath).toBe(path.join(cacheDir, expectedName))
// It should have executed git clone --bare
expect(mockGit.execGit).toHaveBeenCalledWith(
expect.arrayContaining([
'-C',
cacheDir,
'clone',
'--bare',
repositoryUrl,
expect.stringContaining(`${expectedName}.tmp`) // should use tmp dir
])
)
})
it('fetches updates if the cache directory already exists', async () => {
const repositoryUrl = 'https://github.com/mwyraz/existing-repo.git'
const expectedName = (cacheHelper as any).generateCacheDirName(repositoryUrl)
const fixedPath = path.join(cacheDir, expectedName)
// Fake existing directory
await io.mkdirP(path.join(fixedPath, 'objects'))
const resultPath = await cacheHelper.setupCache(mockGit, repositoryUrl)
expect(resultPath).toBe(fixedPath)
// It should have executed git fetch
expect(mockGit.execGit).toHaveBeenCalledWith(
expect.arrayContaining([
'-C',
fixedPath,
'fetch',
'--force',
'--prune',
'--tags',
'origin',
'+refs/heads/*:refs/heads/*'
])
)
})
})

View file

@ -478,8 +478,10 @@ async function setup(testName: string): Promise<void> {
lfsFetch: jest.fn(),
lfsInstall: jest.fn(),
log1: jest.fn(),
referenceAdd: jest.fn(),
remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn(),
execGit: jest.fn(),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn(),
shaExists: jest.fn(),

View file

@ -0,0 +1,182 @@
import * as path from 'path'
const mockStartGroup = jest.fn()
const mockEndGroup = jest.fn()
const mockInfo = jest.fn()
const mockWarning = jest.fn()
const mockSetOutput = jest.fn()
const mockSetSecret = jest.fn()
const mockCreateCommandManager = jest.fn()
const mockCreateAuthHelper = jest.fn()
const mockPrepareExistingDirectory = jest.fn()
const mockGetFetchUrl = jest.fn()
const mockGetRefSpec = jest.fn()
const mockTestRef = jest.fn()
const mockGetCheckoutInfo = jest.fn()
const mockCheckCommitInfo = jest.fn()
const mockSetRepositoryPath = jest.fn()
const mockSetupCache = jest.fn()
const mockDirectoryExistsSync = jest.fn()
const mockFileExistsSync = jest.fn()
jest.mock('@actions/core', () => ({
startGroup: mockStartGroup,
endGroup: mockEndGroup,
info: mockInfo,
warning: mockWarning,
setOutput: mockSetOutput,
setSecret: mockSetSecret
}))
jest.mock('@actions/io', () => ({
rmRF: jest.fn(),
mkdirP: jest.fn()
}))
jest.mock('../src/fs-helper', () => ({
directoryExistsSync: mockDirectoryExistsSync,
fileExistsSync: mockFileExistsSync
}))
jest.mock('../src/git-command-manager', () => ({
MinimumGitSparseCheckoutVersion: {},
createCommandManager: mockCreateCommandManager
}))
jest.mock('../src/git-auth-helper', () => ({
createAuthHelper: mockCreateAuthHelper
}))
jest.mock('../src/git-directory-helper', () => ({
prepareExistingDirectory: mockPrepareExistingDirectory
}))
jest.mock('../src/github-api-helper', () => ({
downloadRepository: jest.fn(),
getDefaultBranch: jest.fn()
}))
jest.mock('../src/ref-helper', () => ({
getRefSpec: mockGetRefSpec,
getCheckoutInfo: mockGetCheckoutInfo,
testRef: mockTestRef,
checkCommitInfo: mockCheckCommitInfo
}))
jest.mock('../src/state-helper', () => ({
setRepositoryPath: mockSetRepositoryPath
}))
jest.mock('../src/url-helper', () => ({
getFetchUrl: mockGetFetchUrl
}))
jest.mock('../src/git-cache-helper', () => ({
GitCacheHelper: jest.fn().mockImplementation(() => ({
setupCache: mockSetupCache
}))
}))
import {getSource} from '../src/git-source-provider'
describe('getSource reference cache regression', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('updates the reference cache and reconfigures alternates for existing repositories', async () => {
const repositoryPath = '/tmp/work/repo'
const repositoryUrl = 'https://github.com/actions/checkout'
const cachePath = '/tmp/reference-cache/actions-checkout.git'
const mockGit = {
init: jest.fn(),
remoteAdd: jest.fn(),
referenceAdd: jest.fn().mockResolvedValue(undefined),
tryDisableAutomaticGarbageCollection: jest.fn().mockResolvedValue(true),
fetch: jest.fn().mockResolvedValue(undefined),
version: jest.fn().mockResolvedValue({
checkMinimum: jest.fn().mockReturnValue(true)
}),
disableSparseCheckout: jest.fn().mockResolvedValue(undefined),
checkout: jest.fn().mockResolvedValue(undefined),
log1: jest
.fn()
.mockResolvedValueOnce('commit info')
.mockResolvedValueOnce('0123456789abcdef'),
lfsInstall: jest.fn(),
submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(),
submoduleForeach: jest.fn(),
config: jest.fn()
}
const mockAuthHelper = {
configureAuth: jest.fn().mockResolvedValue(undefined),
configureGlobalAuth: jest.fn().mockResolvedValue(undefined),
configureSubmoduleAuth: jest.fn().mockResolvedValue(undefined),
configureTempGlobalConfig: jest.fn().mockResolvedValue('/tmp/gitconfig'),
removeAuth: jest.fn().mockResolvedValue(undefined),
removeGlobalAuth: jest.fn().mockResolvedValue(undefined),
removeGlobalConfig: jest.fn().mockResolvedValue(undefined)
}
mockCreateCommandManager.mockResolvedValue(mockGit)
mockCreateAuthHelper.mockReturnValue(mockAuthHelper)
mockPrepareExistingDirectory.mockResolvedValue(undefined)
mockGetFetchUrl.mockReturnValue(repositoryUrl)
mockGetRefSpec.mockReturnValue(['+refs/heads/main:refs/remotes/origin/main'])
mockTestRef.mockResolvedValue(true)
mockGetCheckoutInfo.mockResolvedValue({
ref: 'refs/heads/main',
startPoint: 'refs/remotes/origin/main'
})
mockCheckCommitInfo.mockResolvedValue(undefined)
mockSetupCache.mockResolvedValue(cachePath)
mockFileExistsSync.mockReturnValue(false)
mockDirectoryExistsSync.mockImplementation((targetPath: string) => {
return (
targetPath === repositoryPath ||
targetPath === path.join(repositoryPath, '.git') ||
targetPath === path.join(cachePath, 'objects')
)
})
await getSource({
repositoryPath,
repositoryOwner: 'actions',
repositoryName: 'checkout',
ref: 'refs/heads/main',
commit: '0123456789abcdef',
clean: false,
filter: undefined,
sparseCheckout: undefined as any,
sparseCheckoutConeMode: false,
fetchDepth: 1,
fetchDepthExplicit: true,
fetchTags: false,
showProgress: false,
referenceCache: '/tmp/reference-cache',
lfs: false,
submodules: false,
nestedSubmodules: false,
authToken: 'token',
sshKey: '',
sshKnownHosts: '',
sshStrict: true,
sshUser: 'git',
persistCredentials: false,
workflowOrganizationId: undefined,
githubServerUrl: 'https://github.com',
setSafeDirectory: false
} as any)
expect(mockGit.init).not.toHaveBeenCalled()
expect(mockGit.remoteAdd).not.toHaveBeenCalled()
expect(mockSetupCache).toHaveBeenCalledWith(mockGit, repositoryUrl)
expect(mockGit.referenceAdd).toHaveBeenCalledWith(
path.join(cachePath, 'objects')
)
})
})

View file

@ -0,0 +1,163 @@
import * as core from '@actions/core'
import * as fsHelper from '../src/fs-helper'
import {GitCacheHelper} from '../src/git-cache-helper'
import {
adjustFetchDepthForCache,
setupReferenceCache
} from '../src/git-source-provider'
// Mock @actions/core
jest.mock('@actions/core')
describe('adjustFetchDepthForCache', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('does nothing when referenceCache is not set', () => {
const settings = {
referenceCache: '',
fetchDepth: 1,
fetchDepthExplicit: false
}
adjustFetchDepthForCache(settings)
expect(settings.fetchDepth).toBe(1)
expect(core.warning).not.toHaveBeenCalled()
expect(core.info).not.toHaveBeenCalled()
})
it('overrides fetchDepth to 0 when referenceCache is set and fetchDepth is default', () => {
const settings = {
referenceCache: '/cache/git-reference-cache',
fetchDepth: 1,
fetchDepthExplicit: false
}
adjustFetchDepthForCache(settings)
expect(settings.fetchDepth).toBe(0)
expect(core.info).toHaveBeenCalledWith(
expect.stringContaining('Overriding fetch-depth from 1 to 0')
)
expect(core.warning).not.toHaveBeenCalled()
})
it('warns but keeps fetchDepth when referenceCache is set and fetchDepth is explicit', () => {
const settings = {
referenceCache: '/cache/git-reference-cache',
fetchDepth: 1,
fetchDepthExplicit: true
}
adjustFetchDepthForCache(settings)
expect(settings.fetchDepth).toBe(1)
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("'fetch-depth: 1' is set with reference-cache enabled")
)
expect(core.info).not.toHaveBeenCalled()
})
it('does nothing when referenceCache is set and fetchDepth is already 0 (explicit)', () => {
const settings = {
referenceCache: '/cache/git-reference-cache',
fetchDepth: 0,
fetchDepthExplicit: true
}
adjustFetchDepthForCache(settings)
expect(settings.fetchDepth).toBe(0)
expect(core.warning).not.toHaveBeenCalled()
expect(core.info).not.toHaveBeenCalled()
})
it('does nothing when referenceCache is set and fetchDepth is already 0 (default)', () => {
const settings = {
referenceCache: '/cache/git-reference-cache',
fetchDepth: 0,
fetchDepthExplicit: false
}
adjustFetchDepthForCache(settings)
expect(settings.fetchDepth).toBe(0)
expect(core.warning).not.toHaveBeenCalled()
expect(core.info).not.toHaveBeenCalled()
})
it('warns with correct depth value when explicit fetchDepth is > 1', () => {
const settings = {
referenceCache: '/cache/git-reference-cache',
fetchDepth: 42,
fetchDepthExplicit: true
}
adjustFetchDepthForCache(settings)
expect(settings.fetchDepth).toBe(42)
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("'fetch-depth: 42' is set with reference-cache enabled")
)
})
})
describe('setupReferenceCache', () => {
beforeEach(() => {
jest.clearAllMocks()
})
afterEach(() => {
jest.restoreAllMocks()
})
it('does nothing when referenceCache is not set', async () => {
const git = {
referenceAdd: jest.fn()
} as any
await setupReferenceCache(git, '', 'https://github.com/actions/checkout.git')
expect(git.referenceAdd).not.toHaveBeenCalled()
expect(core.startGroup).not.toHaveBeenCalled()
})
it('updates the cache and configures alternates when cache objects exist', async () => {
const git = {
referenceAdd: jest.fn().mockResolvedValue(undefined)
} as any
const setupCacheSpy = jest
.spyOn(GitCacheHelper.prototype, 'setupCache')
.mockResolvedValue('/tmp/reference-cache/repo.git')
jest
.spyOn(fsHelper, 'directoryExistsSync')
.mockReturnValue(true)
await setupReferenceCache(
git,
'/tmp/reference-cache',
'https://github.com/actions/checkout.git'
)
expect(setupCacheSpy).toHaveBeenCalledWith(
git,
'https://github.com/actions/checkout.git'
)
expect(git.referenceAdd).toHaveBeenCalledWith(
'/tmp/reference-cache/repo.git/objects'
)
})
it('warns when the cache objects directory is missing', async () => {
const git = {
referenceAdd: jest.fn().mockResolvedValue(undefined)
} as any
jest
.spyOn(GitCacheHelper.prototype, 'setupCache')
.mockResolvedValue('/tmp/reference-cache/repo.git')
jest
.spyOn(fsHelper, 'directoryExistsSync')
.mockReturnValue(false)
await setupReferenceCache(
git,
'/tmp/reference-cache',
'https://github.com/actions/checkout.git'
)
expect(git.referenceAdd).not.toHaveBeenCalled()
expect(core.warning).toHaveBeenCalledWith(
'Reference repository cache objects directory /tmp/reference-cache/repo.git/objects does not exist'
)
})
})

View file

@ -91,6 +91,7 @@ describe('input-helper tests', () => {
expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace)
expect(settings.setSafeDirectory).toBe(true)
expect(settings.referenceCache || '').toBe('')
})
it('qualifies ref', async () => {