From 9905cd6970953e6e2af40db4088c1261ee20156e Mon Sep 17 00:00:00 2001 From: Emi Huang Date: Thu, 21 Aug 2025 14:27:45 -0700 Subject: [PATCH] Retry --- README.md | 8 ++++++++ action.yml | 6 ++++++ dist/index.js | 31 ++++++++++++++++++++++++++++++- src/git-auth-helper.ts | 8 ++++++++ src/git-command-manager.ts | 8 ++++++++ src/git-source-settings.ts | 10 ++++++++++ src/input-helper.ts | 18 ++++++++++++++++++ src/retry-helper.ts | 3 ++- 8 files changed, 90 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a32e9a..85ae18c 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,14 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ # Default: false fetch-tags: '' + # Timeout in seconds for individual git/network operations (0 disables). + # Default: 0 + timeout: '' + + # Maximum number of retry attempts for flaky operations (min 1). + # Default: 3 + retry: '' + # Whether to show progress status output when fetching. # Default: true show-progress: '' diff --git a/action.yml b/action.yml index 767c416..c26929c 100644 --- a/action.yml +++ b/action.yml @@ -77,6 +77,12 @@ inputs: fetch-tags: description: 'Whether to fetch tags, even if fetch-depth > 0.' default: false + timeout: + description: 'Timeout in seconds for individual git/network operations (0 disables).' + default: 0 + retry: + description: 'Maximum number of retry attempts for flaky operations (min 1).' + default: 3 show-progress: description: 'Whether to show progress status output when fetching.' default: true diff --git a/dist/index.js b/dist/index.js index f3ae6f3..04c3ac9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -339,6 +339,12 @@ class GitAuthHelper { // Configure GIT_SSH_COMMAND const sshPath = yield io.which('ssh', true); this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(this.sshKeyPath)}"`; + // Apply SSH ConnectTimeout if input timeout is set + const parsedTimeout = Math.floor(Number(process.env['INPUT_TIMEOUT'] || '0')); + if (!isNaN(parsedTimeout) && parsedTimeout > 0) { + // OpenSSH ConnectTimeout is in seconds + this.sshCommand += ` -o ConnectTimeout=${parsedTimeout}`; + } if (this.settings.sshStrict) { this.sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no'; } @@ -898,6 +904,12 @@ class GitCommandManager { ignoreReturnCode: allowAllExitCodes, listeners: mergedListeners }; + // Apply timeout from input (seconds -> ms). 0 disables. + const parsedTimeout = Math.floor(Number(process.env['INPUT_TIMEOUT'] || '0')); + if (!isNaN(parsedTimeout) && parsedTimeout > 0) { + ; + options.timeout = parsedTimeout * 1000; + } result.exitCode = yield exec.exec(`"${this.gitPath}"`, args, options); result.stdout = stdout.join(''); core.debug(result.exitCode.toString()); @@ -1831,6 +1843,22 @@ function getInputs() { // 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}`); + // Retry attempts (min 1) + const retryInput = core.getInput('retry') || '3'; + let retry = Math.floor(Number(retryInput)); + if (isNaN(retry) || retry < 1) { + retry = 1; + } + result.retry = retry; + core.debug(`retry = ${result.retry}`); + // Timeout seconds (0 disables) + const timeoutInput = core.getInput('timeout') || '0'; + let timeout = Math.floor(Number(timeoutInput)); + if (isNaN(timeout) || timeout < 0) { + timeout = 0; + } + result.timeout = timeout; + core.debug(`timeout = ${result.timeout}`); return result; }); } @@ -2263,7 +2291,8 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.RetryHelper = void 0; exports.execute = execute; const core = __importStar(__nccwpck_require__(2186)); -const defaultMaxAttempts = 3; +const parsedRetry = Math.floor(Number(process.env['INPUT_RETRY'] || '')); +const defaultMaxAttempts = !isNaN(parsedRetry) && parsedRetry > 0 ? parsedRetry : 3; const defaultMinSeconds = 10; const defaultMaxSeconds = 20; class RetryHelper { diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 126e8e5..863635b 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -257,6 +257,14 @@ class GitAuthHelper { this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename( this.sshKeyPath )}"` + // Apply SSH ConnectTimeout if input timeout is set + const parsedTimeout = Math.floor( + Number(process.env['INPUT_TIMEOUT'] || '0') + ) + if (!isNaN(parsedTimeout) && parsedTimeout > 0) { + // OpenSSH ConnectTimeout is in seconds + this.sshCommand += ` -o ConnectTimeout=${parsedTimeout}` + } if (this.settings.sshStrict) { this.sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no' } diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 8e42a38..3f1dd80 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -539,6 +539,14 @@ class GitCommandManager { listeners: mergedListeners } + // Apply timeout from input (seconds -> ms). 0 disables. + const parsedTimeout = Math.floor( + Number(process.env['INPUT_TIMEOUT'] || '0') + ) + if (!isNaN(parsedTimeout) && parsedTimeout > 0) { + ;(options as any).timeout = parsedTimeout * 1000 + } + result.exitCode = await exec.exec(`"${this.gitPath}"`, args, options) result.stdout = stdout.join('') diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts index 4e41ac3..2067bdf 100644 --- a/src/git-source-settings.ts +++ b/src/git-source-settings.ts @@ -118,4 +118,14 @@ export interface IGitSourceSettings { * User override on the GitHub Server/Host URL that hosts the repository to be cloned */ githubServerUrl: string | undefined + + /** + * Timeout in seconds for individual git/network operations (0 disables) + */ + timeout?: number + + /** + * Maximum number of retry attempts for flaky operations (min 1) + */ + retry?: number } diff --git a/src/input-helper.ts b/src/input-helper.ts index 059232f..1aded98 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -161,5 +161,23 @@ export async function getInputs(): Promise { result.githubServerUrl = core.getInput('github-server-url') core.debug(`GitHub Host URL = ${result.githubServerUrl}`) + // Retry attempts (min 1) + const retryInput = core.getInput('retry') || '3' + let retry = Math.floor(Number(retryInput)) + if (isNaN(retry) || retry < 1) { + retry = 1 + } + result.retry = retry + core.debug(`retry = ${result.retry}`) + + // Timeout seconds (0 disables) + const timeoutInput = core.getInput('timeout') || '0' + let timeout = Math.floor(Number(timeoutInput)) + if (isNaN(timeout) || timeout < 0) { + timeout = 0 + } + result.timeout = timeout + core.debug(`timeout = ${result.timeout}`) + return result } diff --git a/src/retry-helper.ts b/src/retry-helper.ts index 323e75d..0623422 100644 --- a/src/retry-helper.ts +++ b/src/retry-helper.ts @@ -1,6 +1,7 @@ import * as core from '@actions/core' -const defaultMaxAttempts = 3 +const parsedRetry = Math.floor(Number(process.env['INPUT_RETRY'] || '')) +const defaultMaxAttempts = !isNaN(parsedRetry) && parsedRetry > 0 ? parsedRetry : 3 const defaultMinSeconds = 10 const defaultMaxSeconds = 20