mirror of
https://github.com/actions/checkout.git
synced 2026-03-19 18:40:23 +08:00
- Kill git process on timeout: use child_process.spawn directly for
timeout-eligible operations so we have a ChildProcess handle to send
SIGTERM (then SIGKILL after 5s). On Windows, SIGTERM is a forced kill
so the SIGKILL fallback is effectively a no-op there.
- Fix timeout:0 not working: replace falsy || coalescion with explicit
empty-string check so that '0' is not replaced by the default '300'.
- Refactor execGit to use an options object instead of 5 positional
parameters, eliminating error-prone filler args (false, false, {}).
- Pass allowAllExitCodes through to execGitWithTimeout so both code
paths have consistent behavior for non-zero exit codes.
- Add settled guard to prevent double-reject when both close and error
events fire on the spawned process.
- Handle null exit code (process killed by signal) as an error rather
than silently treating it as success.
- Capture stderr in error messages for the timeout path, matching the
information level of the non-timeout exec path.
- Log SIGKILL failures at debug level instead of empty catch block.
- Warn on customListeners being ignored in the timeout path.
- Emit core.warning() when invalid input values are silently replaced
with defaults, so users know their configuration was rejected.
- Add input validation in setTimeout (reject negative values).
- Clarify retry-max-attempts semantics: total attempts including the
initial attempt (3 = 1 initial + 2 retries).
- Remove Kubernetes probe references from descriptions.
- Use non-exhaustive list (e.g.) for network operations in docs to
avoid staleness if new operations are added.
- Add tests for timeout/retry input parsing (defaults, timeout:0,
custom values, invalid input with warnings, backoff clamping) and
command manager configuration (setTimeout, setRetryConfig, fetch).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
220 lines
7.4 KiB
TypeScript
220 lines
7.4 KiB
TypeScript
import * as core from '@actions/core'
|
|
import * as fsHelper from './fs-helper'
|
|
import * as github from '@actions/github'
|
|
import * as path from 'path'
|
|
import * as workflowContextHelper from './workflow-context-helper'
|
|
import {IGitSourceSettings} from './git-source-settings'
|
|
|
|
export async function getInputs(): Promise<IGitSourceSettings> {
|
|
const result = {} as unknown as IGitSourceSettings
|
|
|
|
// GitHub workspace
|
|
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
|
|
if (!githubWorkspacePath) {
|
|
throw new Error('GITHUB_WORKSPACE not defined')
|
|
}
|
|
githubWorkspacePath = path.resolve(githubWorkspacePath)
|
|
core.debug(`GITHUB_WORKSPACE = '${githubWorkspacePath}'`)
|
|
fsHelper.directoryExistsSync(githubWorkspacePath, true)
|
|
|
|
// Qualified repository
|
|
const qualifiedRepository =
|
|
core.getInput('repository') ||
|
|
`${github.context.repo.owner}/${github.context.repo.repo}`
|
|
core.debug(`qualified repository = '${qualifiedRepository}'`)
|
|
const splitRepository = qualifiedRepository.split('/')
|
|
if (
|
|
splitRepository.length !== 2 ||
|
|
!splitRepository[0] ||
|
|
!splitRepository[1]
|
|
) {
|
|
throw new Error(
|
|
`Invalid repository '${qualifiedRepository}'. Expected format {owner}/{repo}.`
|
|
)
|
|
}
|
|
result.repositoryOwner = splitRepository[0]
|
|
result.repositoryName = splitRepository[1]
|
|
|
|
// Repository path
|
|
result.repositoryPath = core.getInput('path') || '.'
|
|
result.repositoryPath = path.resolve(
|
|
githubWorkspacePath,
|
|
result.repositoryPath
|
|
)
|
|
if (
|
|
!(result.repositoryPath + path.sep).startsWith(
|
|
githubWorkspacePath + path.sep
|
|
)
|
|
) {
|
|
throw new Error(
|
|
`Repository path '${result.repositoryPath}' is not under '${githubWorkspacePath}'`
|
|
)
|
|
}
|
|
|
|
// Workflow repository?
|
|
const isWorkflowRepository =
|
|
qualifiedRepository.toUpperCase() ===
|
|
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase()
|
|
|
|
// Source branch, source version
|
|
result.ref = core.getInput('ref')
|
|
if (!result.ref) {
|
|
if (isWorkflowRepository) {
|
|
result.ref = github.context.ref
|
|
result.commit = github.context.sha
|
|
|
|
// Some events have an unqualifed ref. For example when a PR is merged (pull_request closed event),
|
|
// the ref is unqualifed like "main" instead of "refs/heads/main".
|
|
if (result.commit && result.ref && !result.ref.startsWith('refs/')) {
|
|
result.ref = `refs/heads/${result.ref}`
|
|
}
|
|
}
|
|
}
|
|
// SHA?
|
|
else if (result.ref.match(/^[0-9a-fA-F]{40}$/)) {
|
|
result.commit = result.ref
|
|
result.ref = ''
|
|
}
|
|
core.debug(`ref = '${result.ref}'`)
|
|
core.debug(`commit = '${result.commit}'`)
|
|
|
|
// Clean
|
|
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
|
|
core.debug(`clean = ${result.clean}`)
|
|
|
|
// Filter
|
|
const filter = core.getInput('filter')
|
|
if (filter) {
|
|
result.filter = filter
|
|
}
|
|
|
|
core.debug(`filter = ${result.filter}`)
|
|
|
|
// Sparse checkout
|
|
const sparseCheckout = core.getMultilineInput('sparse-checkout')
|
|
if (sparseCheckout.length) {
|
|
result.sparseCheckout = sparseCheckout
|
|
core.debug(`sparse checkout = ${result.sparseCheckout}`)
|
|
}
|
|
|
|
result.sparseCheckoutConeMode =
|
|
(core.getInput('sparse-checkout-cone-mode') || 'true').toUpperCase() ===
|
|
'TRUE'
|
|
|
|
// Fetch depth
|
|
result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1'))
|
|
if (isNaN(result.fetchDepth) || result.fetchDepth < 0) {
|
|
result.fetchDepth = 0
|
|
}
|
|
core.debug(`fetch depth = ${result.fetchDepth}`)
|
|
|
|
// Fetch tags
|
|
result.fetchTags =
|
|
(core.getInput('fetch-tags') || 'false').toUpperCase() === 'TRUE'
|
|
core.debug(`fetch tags = ${result.fetchTags}`)
|
|
|
|
// Show fetch progress
|
|
result.showProgress =
|
|
(core.getInput('show-progress') || 'true').toUpperCase() === 'TRUE'
|
|
core.debug(`show progress = ${result.showProgress}`)
|
|
|
|
// LFS
|
|
result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'
|
|
core.debug(`lfs = ${result.lfs}`)
|
|
|
|
// Submodules
|
|
result.submodules = false
|
|
result.nestedSubmodules = false
|
|
const submodulesString = (core.getInput('submodules') || '').toUpperCase()
|
|
if (submodulesString == 'RECURSIVE') {
|
|
result.submodules = true
|
|
result.nestedSubmodules = true
|
|
} else if (submodulesString == 'TRUE') {
|
|
result.submodules = true
|
|
}
|
|
core.debug(`submodules = ${result.submodules}`)
|
|
core.debug(`recursive submodules = ${result.nestedSubmodules}`)
|
|
|
|
// Auth token
|
|
result.authToken = core.getInput('token', {required: true})
|
|
|
|
// SSH
|
|
result.sshKey = core.getInput('ssh-key')
|
|
result.sshKnownHosts = core.getInput('ssh-known-hosts')
|
|
result.sshStrict =
|
|
(core.getInput('ssh-strict') || 'true').toUpperCase() === 'TRUE'
|
|
result.sshUser = core.getInput('ssh-user')
|
|
|
|
// Persist credentials
|
|
result.persistCredentials =
|
|
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
|
|
|
|
// Workflow organization ID
|
|
result.workflowOrganizationId =
|
|
await workflowContextHelper.getOrganizationId()
|
|
|
|
// Set safe.directory in git global config.
|
|
result.setSafeDirectory =
|
|
(core.getInput('set-safe-directory') || 'true').toUpperCase() === 'TRUE'
|
|
|
|
// Determine the GitHub URL that the repository is being hosted from
|
|
result.githubServerUrl = core.getInput('github-server-url')
|
|
core.debug(`GitHub Host URL = ${result.githubServerUrl}`)
|
|
|
|
// Timeout per network operation attempt
|
|
const timeoutInput = core.getInput('timeout')
|
|
result.timeout = Math.floor(Number(timeoutInput !== '' ? timeoutInput : '300'))
|
|
if (isNaN(result.timeout) || result.timeout < 0) {
|
|
core.warning(
|
|
`Invalid value '${timeoutInput}' for 'timeout' input. Using default: 300 seconds.`
|
|
)
|
|
result.timeout = 300
|
|
}
|
|
core.debug(`timeout = ${result.timeout}`)
|
|
|
|
// Retry max attempts (total attempts including initial)
|
|
const retryMaxAttemptsInput = core.getInput('retry-max-attempts')
|
|
result.retryMaxAttempts = Math.floor(
|
|
Number(retryMaxAttemptsInput !== '' ? retryMaxAttemptsInput : '3')
|
|
)
|
|
if (isNaN(result.retryMaxAttempts) || result.retryMaxAttempts < 1) {
|
|
core.warning(
|
|
`Invalid value '${retryMaxAttemptsInput}' for 'retry-max-attempts' input. Using default: 3.`
|
|
)
|
|
result.retryMaxAttempts = 3
|
|
}
|
|
core.debug(`retry max attempts = ${result.retryMaxAttempts}`)
|
|
|
|
// Retry backoff range
|
|
const retryMinBackoffInput = core.getInput('retry-min-backoff')
|
|
result.retryMinBackoff = Math.floor(
|
|
Number(retryMinBackoffInput !== '' ? retryMinBackoffInput : '10')
|
|
)
|
|
if (isNaN(result.retryMinBackoff) || result.retryMinBackoff < 0) {
|
|
core.warning(
|
|
`Invalid value '${retryMinBackoffInput}' for 'retry-min-backoff' input. Using default: 10 seconds.`
|
|
)
|
|
result.retryMinBackoff = 10
|
|
}
|
|
core.debug(`retry min backoff = ${result.retryMinBackoff}`)
|
|
|
|
const retryMaxBackoffInput = core.getInput('retry-max-backoff')
|
|
result.retryMaxBackoff = Math.floor(
|
|
Number(retryMaxBackoffInput !== '' ? retryMaxBackoffInput : '20')
|
|
)
|
|
if (isNaN(result.retryMaxBackoff) || result.retryMaxBackoff < 0) {
|
|
core.warning(
|
|
`Invalid value '${retryMaxBackoffInput}' for 'retry-max-backoff' input. Using default: 20 seconds.`
|
|
)
|
|
result.retryMaxBackoff = 20
|
|
}
|
|
if (result.retryMaxBackoff < result.retryMinBackoff) {
|
|
core.warning(
|
|
`'retry-max-backoff' (${result.retryMaxBackoff}) is less than 'retry-min-backoff' (${result.retryMinBackoff}). Using retry-min-backoff value for both.`
|
|
)
|
|
result.retryMaxBackoff = result.retryMinBackoff
|
|
}
|
|
core.debug(`retry max backoff = ${result.retryMaxBackoff}`)
|
|
|
|
return result
|
|
}
|