1
0
mirror of https://github.com/actions/checkout.git synced 2026-07-05 18:53:46 +08:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Vìncent Le Goff
0c6e71a574 Merge e7667abffb into 0c366fd6a8 2026-01-10 01:41:19 +05:30
Vincent Le Goff
e7667abffb feat(inputs): add option to disable the post action 2025-07-28 15:27:08 +02:00
10 changed files with 33 additions and 230 deletions

View File

@@ -974,46 +974,6 @@ describe('git-auth-helper tests', () => {
).toBe(false) ).toBe(false)
expect((authHelper as any).testCredentialsConfigPath('')).toBe(false) expect((authHelper as any).testCredentialsConfigPath('')).toBe(false)
}) })
const includeIfCleanupRegex_matchesBothVariants =
'includeIf cleanup regex matches both gitdir: and gitdir/i: keys'
it(includeIfCleanupRegex_matchesBothVariants, async () => {
// The cleanup regex must match both variants so credential
// removal works regardless of which was written
const regex = /^includeIf\.gitdir(\/i)?:/
expect(regex.test('includeIf.gitdir:D:/workspaces/repo/.git.path')).toBe(
true
)
expect(regex.test('includeIf.gitdir/i:D:/Workspaces/repo/.git.path')).toBe(
true
)
expect(regex.test('includeIf.gitdir/i:/github/workspace/.git.path')).toBe(
true
)
expect(regex.test('includeIf.gitdir:~/projects/foo/.git.path')).toBe(true)
expect(regex.test('includeIf.onbranch:main.path')).toBe(false)
expect(regex.test('include.path')).toBe(false)
})
const includeIfDirective_usesCorrectVariantForPlatform =
'includeIf directive uses gitdir/i on Windows and gitdir on other platforms'
it(includeIfDirective_usesCorrectVariantForPlatform, async () => {
await setup(includeIfDirective_usesCorrectVariantForPlatform)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const localConfigContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
if (isWindows) {
expect(localConfigContent).toContain('includeIf.gitdir/i:')
expect(localConfigContent).not.toContain('includeIf.gitdir:')
} else {
expect(localConfigContent).toContain('includeIf.gitdir:')
expect(localConfigContent).not.toContain('includeIf.gitdir/i:')
}
})
}) })
async function setup(testName: string): Promise<void> { async function setup(testName: string): Promise<void> {

View File

@@ -133,16 +133,6 @@ describe('input-helper tests', () => {
expect(settings.commit).toBe('1111111111222222222233333333334444444444') expect(settings.commit).toBe('1111111111222222222233333333334444444444')
}) })
it('sets ref to empty when explicit sha-256', async () => {
inputs.ref =
'1111111111222222222233333333334444444444555555555566666666667777'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBeFalsy()
expect(settings.commit).toBe(
'1111111111222222222233333333334444444444555555555566666666667777'
)
})
it('sets sha to empty when explicit ref', async () => { it('sets sha to empty when explicit ref', async () => {
inputs.ref = 'refs/heads/some-other-ref' inputs.ref = 'refs/heads/some-other-ref'
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()

View File

@@ -1,12 +1,8 @@
import * as assert from 'assert' import * as assert from 'assert'
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as refHelper from '../lib/ref-helper' import * as refHelper from '../lib/ref-helper'
import {IGitCommandManager} from '../lib/git-command-manager' import {IGitCommandManager} from '../lib/git-command-manager'
const commit = '1234567890123456789012345678901234567890' const commit = '1234567890123456789012345678901234567890'
const sha256Commit =
'1234567890123456789012345678901234567890123456789012345678901234'
let git: IGitCommandManager let git: IGitCommandManager
describe('ref-helper tests', () => { describe('ref-helper tests', () => {
@@ -41,12 +37,6 @@ describe('ref-helper tests', () => {
expect(checkoutInfo.startPoint).toBeFalsy() expect(checkoutInfo.startPoint).toBeFalsy()
}) })
it('getCheckoutInfo sha-256 only', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(git, '', sha256Commit)
expect(checkoutInfo.ref).toBe(sha256Commit)
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo refs/heads/', async () => { it('getCheckoutInfo refs/heads/', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo( const checkoutInfo = await refHelper.getCheckoutInfo(
git, git,
@@ -237,142 +227,4 @@ describe('ref-helper tests', () => {
'+refs/heads/my/branch:refs/remotes/origin/my/branch' '+refs/heads/my/branch:refs/remotes/origin/my/branch'
) )
}) })
describe('checkCommitInfo', () => {
const repositoryOwner = 'some-owner'
const repositoryName = 'some-repo'
const ref = 'refs/pull/123/merge'
const sha1Head = '1111111111222222222233333333334444444444'
const sha1Base = 'aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd'
const sha256Head =
'1111111111222222222233333333334444444444555555555566666666667777'
const sha256Base =
'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff0000'
let debugSpy: jest.SpyInstance
let getOctokitSpy: jest.SpyInstance
let repoGetSpy: jest.Mock
let originalEventName: string
let originalPayload: unknown
let originalRef: string
let originalSha: string
function setPullRequestContext(
expectedHeadSha: string,
expectedBaseSha: string,
mergeCommit: string
): void {
;(github.context as any).eventName = 'pull_request'
github.context.ref = ref
github.context.sha = mergeCommit
;(github.context as any).payload = {
action: 'synchronize',
after: expectedHeadSha,
number: 123,
pull_request: {
base: {
sha: expectedBaseSha
}
},
repository: {
private: false
}
}
}
beforeEach(() => {
originalEventName = github.context.eventName
originalPayload = github.context.payload
originalRef = github.context.ref
originalSha = github.context.sha
jest.spyOn(github.context, 'repo', 'get').mockReturnValue({
owner: repositoryOwner,
repo: repositoryName
})
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
repoGetSpy = jest.fn(async () => ({}))
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
rest: {
repos: {
get: repoGetSpy
}
}
} as any)
})
afterEach(() => {
;(github.context as any).eventName = originalEventName
;(github.context as any).payload = originalPayload
github.context.ref = originalRef
github.context.sha = originalSha
jest.restoreAllMocks()
})
it('returns early for SHA-1 merge commit', async () => {
setPullRequestContext(sha1Head, sha1Base, commit)
await refHelper.checkCommitInfo(
'token',
`Merge ${sha1Head} into ${sha1Base}`,
repositoryOwner,
repositoryName,
ref,
commit
)
expect(getOctokitSpy).not.toHaveBeenCalled()
expect(repoGetSpy).not.toHaveBeenCalled()
})
it('matches SHA-256 merge commit info', async () => {
const actualHeadSha =
'9999999999888888888877777777776666666666555555555544444444443333'
setPullRequestContext(sha256Head, sha256Base, sha256Commit)
await refHelper.checkCommitInfo(
'token',
`Merge ${actualHeadSha} into ${sha256Base}`,
repositoryOwner,
repositoryName,
ref,
sha256Commit
)
expect(getOctokitSpy).toHaveBeenCalledWith(
'token',
expect.objectContaining({
userAgent: expect.stringContaining(
`expected_head_sha=${sha256Head};actual_head_sha=${actualHeadSha}`
)
})
)
expect(repoGetSpy).toHaveBeenCalledWith({
owner: repositoryOwner,
repo: repositoryName
})
expect(debugSpy).toHaveBeenCalledWith(
`Expected head sha ${sha256Head}; actual head sha ${actualHeadSha}`
)
expect(debugSpy).not.toHaveBeenCalledWith('Unexpected message format')
})
it('does not match 50-char hex as a valid merge', async () => {
const invalidHeadSha =
'99999999998888888888777777777766666666665555555555'
setPullRequestContext(sha1Head, sha1Base, commit)
await refHelper.checkCommitInfo(
'token',
`Merge ${invalidHeadSha} into ${sha1Base}`,
repositoryOwner,
repositoryName,
ref,
commit
)
expect(getOctokitSpy).not.toHaveBeenCalled()
expect(repoGetSpy).not.toHaveBeenCalled()
expect(debugSpy).toHaveBeenCalledWith('Unexpected message format')
})
})
}) })

View File

@@ -98,6 +98,10 @@ inputs:
github-server-url: github-server-url:
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
required: false required: false
skip-cleanup:
description: Skips the cleanup phase on post action hook
required: false
default: false
outputs: outputs:
ref: ref:
description: 'The branch, tag or SHA that was checked out' description: 'The branch, tag or SHA that was checked out'

24
dist/index.js vendored
View File

@@ -151,12 +151,6 @@ const stateHelper = __importStar(__nccwpck_require__(4866));
const urlHelper = __importStar(__nccwpck_require__(9437)); const urlHelper = __importStar(__nccwpck_require__(9437));
const uuid_1 = __nccwpck_require__(5840); const uuid_1 = __nccwpck_require__(5840);
const IS_WINDOWS = process.platform === 'win32'; const IS_WINDOWS = process.platform === 'win32';
// Use case-insensitive gitdir matching on Windows to handle path casing mismatches
// between the runner's GITHUB_WORKSPACE and the actual filesystem casing.
// See: https://github.com/actions/checkout/issues/2345
const INCLUDE_IF_GITDIR = IS_WINDOWS
? 'includeIf.gitdir/i:'
: 'includeIf.gitdir:';
const SSH_COMMAND_KEY = 'core.sshCommand'; const SSH_COMMAND_KEY = 'core.sshCommand';
function createAuthHelper(git, settings) { function createAuthHelper(git, settings) {
return new GitAuthHelper(git, settings); return new GitAuthHelper(git, settings);
@@ -276,7 +270,7 @@ class GitAuthHelper {
let submoduleGitDir = path.dirname(configPath); // The config file is at .git/modules/submodule-name/config let submoduleGitDir = path.dirname(configPath); // The config file is at .git/modules/submodule-name/config
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf // Configure host includeIf
yield this.git.config(`${INCLUDE_IF_GITDIR}${submoduleGitDir}.path`, credentialsConfigPath, false, // globalConfig? yield this.git.config(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, false, // globalConfig?
false, // add? false, // add?
configPath); configPath);
// Container submodule git directory // Container submodule git directory
@@ -286,7 +280,7 @@ class GitAuthHelper {
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
const containerSubmoduleGitDir = path.posix.join('/github/workspace', relativeSubmoduleGitDir); const containerSubmoduleGitDir = path.posix.join('/github/workspace', relativeSubmoduleGitDir);
// Configure container includeIf // Configure container includeIf
yield this.git.config(`${INCLUDE_IF_GITDIR}${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, // globalConfig? yield this.git.config(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, // globalConfig?
false, // add? false, // add?
configPath); configPath);
} }
@@ -416,10 +410,10 @@ class GitAuthHelper {
let gitDir = path.join(this.git.getWorkingDirectory(), '.git'); let gitDir = path.join(this.git.getWorkingDirectory(), '.git');
gitDir = gitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows gitDir = gitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf // Configure host includeIf
const hostIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}.path`; const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
yield this.git.config(hostIncludeKey, credentialsConfigPath); yield this.git.config(hostIncludeKey, credentialsConfigPath);
// Configure host includeIf for worktrees // Configure host includeIf for worktrees
const hostWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}/worktrees/*.path`; const hostWorktreeIncludeKey = `includeIf.gitdir:${gitDir}/worktrees/*.path`;
yield this.git.config(hostWorktreeIncludeKey, credentialsConfigPath); yield this.git.config(hostWorktreeIncludeKey, credentialsConfigPath);
// Container git directory // Container git directory
const workingDirectory = this.git.getWorkingDirectory(); const workingDirectory = this.git.getWorkingDirectory();
@@ -431,10 +425,10 @@ class GitAuthHelper {
// Container credentials config path // Container credentials config path
const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath));
// Configure container includeIf // Configure container includeIf
const containerIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}.path`; const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`;
yield this.git.config(containerIncludeKey, containerCredentialsPath); yield this.git.config(containerIncludeKey, containerCredentialsPath);
// Configure container includeIf for worktrees // Configure container includeIf for worktrees
const containerWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}/worktrees/*.path`; const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`;
yield this.git.config(containerWorktreeIncludeKey, containerCredentialsPath); yield this.git.config(containerWorktreeIncludeKey, containerCredentialsPath);
} }
}); });
@@ -571,7 +565,7 @@ class GitAuthHelper {
const credentialsPaths = new Set(); const credentialsPaths = new Set();
try { try {
// Get all includeIf.gitdir keys // Get all includeIf.gitdir keys
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir(/i)?:', false, // globalConfig? const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, // globalConfig?
configPath); configPath);
for (const key of keys) { for (const key of keys) {
// Get all values for this key // Get all values for this key
@@ -2027,7 +2021,7 @@ function getInputs() {
} }
} }
// SHA? // SHA?
else if (result.ref.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) { else if (result.ref.match(/^[0-9a-fA-F]{40}$/)) {
result.commit = result.ref; result.commit = result.ref;
result.ref = ''; result.ref = '';
} }
@@ -2450,7 +2444,7 @@ function checkCommitInfo(token, commitInfo, repositoryOwner, repositoryName, ref
return; return;
} }
// Extract details from message // Extract details from message
const match = commitInfo.match(/Merge ([0-9a-f]{40}|[0-9a-f]{64}) into ([0-9a-f]{40}|[0-9a-f]{64})/); const match = commitInfo.match(/Merge ([0-9a-f]{40}) into ([0-9a-f]{40})/);
if (!match) { if (!match) {
core.debug('Unexpected message format'); core.debug('Unexpected message format');
return; return;

View File

@@ -13,12 +13,6 @@ import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
// Use case-insensitive gitdir matching on Windows to handle path casing mismatches
// between the runner's GITHUB_WORKSPACE and the actual filesystem casing.
// See: https://github.com/actions/checkout/issues/2345
const INCLUDE_IF_GITDIR = IS_WINDOWS
? 'includeIf.gitdir/i:'
: 'includeIf.gitdir:'
const SSH_COMMAND_KEY = 'core.sshCommand' const SSH_COMMAND_KEY = 'core.sshCommand'
export interface IGitAuthHelper { export interface IGitAuthHelper {
@@ -188,7 +182,7 @@ class GitAuthHelper {
// Configure host includeIf // Configure host includeIf
await this.git.config( await this.git.config(
`${INCLUDE_IF_GITDIR}${submoduleGitDir}.path`, `includeIf.gitdir:${submoduleGitDir}.path`,
credentialsConfigPath, credentialsConfigPath,
false, // globalConfig? false, // globalConfig?
false, // add? false, // add?
@@ -210,7 +204,7 @@ class GitAuthHelper {
// Configure container includeIf // Configure container includeIf
await this.git.config( await this.git.config(
`${INCLUDE_IF_GITDIR}${containerSubmoduleGitDir}.path`, `includeIf.gitdir:${containerSubmoduleGitDir}.path`,
containerCredentialsPath, containerCredentialsPath,
false, // globalConfig? false, // globalConfig?
false, // add? false, // add?
@@ -377,11 +371,11 @@ class GitAuthHelper {
gitDir = gitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows gitDir = gitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
// Configure host includeIf // Configure host includeIf
const hostIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}.path` const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
await this.git.config(hostIncludeKey, credentialsConfigPath) await this.git.config(hostIncludeKey, credentialsConfigPath)
// Configure host includeIf for worktrees // Configure host includeIf for worktrees
const hostWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${gitDir}/worktrees/*.path` const hostWorktreeIncludeKey = `includeIf.gitdir:${gitDir}/worktrees/*.path`
await this.git.config(hostWorktreeIncludeKey, credentialsConfigPath) await this.git.config(hostWorktreeIncludeKey, credentialsConfigPath)
// Container git directory // Container git directory
@@ -403,11 +397,11 @@ class GitAuthHelper {
) )
// Configure container includeIf // Configure container includeIf
const containerIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}.path` const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`
await this.git.config(containerIncludeKey, containerCredentialsPath) await this.git.config(containerIncludeKey, containerCredentialsPath)
// Configure container includeIf for worktrees // Configure container includeIf for worktrees
const containerWorktreeIncludeKey = `${INCLUDE_IF_GITDIR}${containerGitDir}/worktrees/*.path` const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`
await this.git.config( await this.git.config(
containerWorktreeIncludeKey, containerWorktreeIncludeKey,
containerCredentialsPath containerCredentialsPath
@@ -560,7 +554,7 @@ class GitAuthHelper {
try { try {
// Get all includeIf.gitdir keys // Get all includeIf.gitdir keys
const keys = await this.git.tryGetConfigKeys( const keys = await this.git.tryGetConfigKeys(
'^includeIf\\.gitdir(/i)?:', '^includeIf\\.gitdir:',
false, // globalConfig? false, // globalConfig?
configPath configPath
) )

View File

@@ -118,4 +118,9 @@ export interface IGitSourceSettings {
* User override on the GitHub Server/Host URL that hosts the repository to be cloned * User override on the GitHub Server/Host URL that hosts the repository to be cloned
*/ */
githubServerUrl: string | undefined githubServerUrl: string | undefined
/**
* Disable the post action cleanup phase
*/
skipCleanup: boolean
} }

View File

@@ -71,7 +71,7 @@ export async function getInputs(): Promise<IGitSourceSettings> {
} }
} }
// SHA? // SHA?
else if (result.ref.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) { else if (result.ref.match(/^[0-9a-fA-F]{40}$/)) {
result.commit = result.ref result.commit = result.ref
result.ref = '' result.ref = ''
} }

View File

@@ -30,6 +30,12 @@ async function run(): Promise<void> {
} }
async function cleanup(): Promise<void> { async function cleanup(): Promise<void> {
const sourceSettings = await inputHelper.getInputs()
if (sourceSettings.skipCleanup) {
return
}
try { try {
await gitSourceProvider.cleanup(stateHelper.RepositoryPath) await gitSourceProvider.cleanup(stateHelper.RepositoryPath)
} catch (error) { } catch (error) {

View File

@@ -258,9 +258,7 @@ export async function checkCommitInfo(
} }
// Extract details from message // Extract details from message
const match = commitInfo.match( const match = commitInfo.match(/Merge ([0-9a-f]{40}) into ([0-9a-f]{40})/)
/Merge ([0-9a-f]{40}|[0-9a-f]{64}) into ([0-9a-f]{40}|[0-9a-f]{64})/
)
if (!match) { if (!match) {
core.debug('Unexpected message format') core.debug('Unexpected message format')
return return