-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Feature/git cache #2384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/git cache #2384
Changes from all commits
9ddd3f4
ed69f3b
9be4f3c
090955f
20f49bc
043bdbc
4bd5359
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you try this from ChatGPT so it's so easy work and short coding so now checking |
||
|
|
||
| // 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' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here also mistak errors 404 recheck |
||
| 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' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. error error complete check again then show me this i approve this than |
||
| 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/*' | ||
| ]) | ||
| ) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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') | ||
| ) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some errors can you check again this