mirror of
https://github.com/actions/checkout.git
synced 2026-03-19 10:30:22 +08:00
Merge 3ff67abc5a into 0c366fd6a8
This commit is contained in:
commit
047af58489
12 changed files with 815 additions and 119 deletions
22
README.md
22
README.md
|
|
@ -155,6 +155,28 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
|
|||
# Default: true
|
||||
set-safe-directory: ''
|
||||
|
||||
# Timeout in seconds for each git network operation attempt (e.g. fetch,
|
||||
# lfs-fetch, ls-remote). If a single attempt exceeds this, the process is
|
||||
# terminated. If retries are configured (see retry-max-attempts), the operation
|
||||
# will be retried. Set to 0 to disable. Default is 300 (5 minutes).
|
||||
# Default: 300
|
||||
timeout: ''
|
||||
|
||||
# Total number of attempts for each git network operation (including the initial
|
||||
# attempt). For example, 3 means one initial attempt plus up to 2 retries.
|
||||
# Default: 3
|
||||
retry-max-attempts: ''
|
||||
|
||||
# Minimum backoff time in seconds between retry attempts. The actual backoff is
|
||||
# randomly chosen between min and max.
|
||||
# Default: 10
|
||||
retry-min-backoff: ''
|
||||
|
||||
# Maximum backoff time in seconds between retry attempts. The actual backoff is
|
||||
# randomly chosen between min and max.
|
||||
# Default: 20
|
||||
retry-max-backoff: ''
|
||||
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -1146,7 +1146,9 @@ async function setup(testName: string): Promise<void> {
|
|||
}
|
||||
),
|
||||
tryReset: jest.fn(),
|
||||
version: jest.fn()
|
||||
version: jest.fn(),
|
||||
setTimeout: jest.fn(),
|
||||
setRetryConfig: jest.fn()
|
||||
}
|
||||
|
||||
settings = {
|
||||
|
|
@ -1173,7 +1175,11 @@ async function setup(testName: string): Promise<void> {
|
|||
sshUser: '',
|
||||
workflowOrganizationId: 123456,
|
||||
setSafeDirectory: true,
|
||||
githubServerUrl: githubServerUrl
|
||||
githubServerUrl: githubServerUrl,
|
||||
timeout: 300,
|
||||
retryMaxAttempts: 3,
|
||||
retryMinBackoff: 10,
|
||||
retryMaxBackoff: 20
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,17 @@ import * as commandManager from '../lib/git-command-manager'
|
|||
let git: commandManager.IGitCommandManager
|
||||
let mockExec = jest.fn()
|
||||
|
||||
function createMockGit(): Promise<commandManager.IGitCommandManager> {
|
||||
mockExec.mockImplementation((path, args, options) => {
|
||||
if (args.includes('version')) {
|
||||
options.listeners.stdout(Buffer.from('2.18'))
|
||||
}
|
||||
return 0
|
||||
})
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
return commandManager.createCommandManager('test', false, false)
|
||||
}
|
||||
|
||||
describe('git-auth-helper tests', () => {
|
||||
beforeAll(async () => {})
|
||||
|
||||
|
|
@ -494,3 +505,73 @@ describe('git user-agent with orchestration ID', () => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('timeout and retry configuration', () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
|
||||
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('setTimeout accepts valid values', async () => {
|
||||
git = await createMockGit()
|
||||
git.setTimeout(30)
|
||||
git.setTimeout(0)
|
||||
})
|
||||
|
||||
it('setTimeout rejects negative values', async () => {
|
||||
git = await createMockGit()
|
||||
expect(() => git.setTimeout(-1)).toThrow(/non-negative/)
|
||||
})
|
||||
|
||||
it('setRetryConfig accepts valid parameters', async () => {
|
||||
git = await createMockGit()
|
||||
git.setRetryConfig(5, 2, 15)
|
||||
})
|
||||
|
||||
it('setRetryConfig rejects min > max backoff', async () => {
|
||||
git = await createMockGit()
|
||||
expect(() => git.setRetryConfig(3, 20, 5)).toThrow(
|
||||
/min seconds should be less than or equal to max seconds/
|
||||
)
|
||||
})
|
||||
|
||||
it('fetch without timeout uses exec', async () => {
|
||||
git = await createMockGit()
|
||||
// timeout defaults to 0 (disabled)
|
||||
|
||||
mockExec.mockClear()
|
||||
await git.fetch(['refs/heads/main'], {})
|
||||
|
||||
// exec.exec is used (via retryHelper) when no timeout
|
||||
const fetchCalls = mockExec.mock.calls.filter(
|
||||
(call: any[]) => (call[1] as string[]).includes('fetch')
|
||||
)
|
||||
expect(fetchCalls).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('fetch with timeout does not use exec', async () => {
|
||||
git = await createMockGit()
|
||||
// Short timeout and single attempt so the test completes quickly
|
||||
git.setTimeout(1)
|
||||
git.setRetryConfig(1, 0, 0)
|
||||
|
||||
mockExec.mockClear()
|
||||
|
||||
// fetch will use spawn path (which will fail/timeout since there's
|
||||
// no real git repo), but we verify exec.exec was NOT called for fetch
|
||||
try {
|
||||
await git.fetch(['refs/heads/main'], {})
|
||||
} catch {
|
||||
// Expected: spawn will fail/timeout in test environment
|
||||
}
|
||||
|
||||
const fetchCalls = mockExec.mock.calls.filter(
|
||||
(call: any[]) => (call[1] as string[]).includes('fetch')
|
||||
)
|
||||
expect(fetchCalls).toHaveLength(0)
|
||||
}, 10000)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -506,6 +506,8 @@ async function setup(testName: string): Promise<void> {
|
|||
tryReset: jest.fn(async () => {
|
||||
return true
|
||||
}),
|
||||
version: jest.fn()
|
||||
version: jest.fn(),
|
||||
setTimeout: jest.fn(),
|
||||
setRetryConfig: jest.fn()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,4 +144,58 @@ describe('input-helper tests', () => {
|
|||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.workflowOrganizationId).toBe(123456)
|
||||
})
|
||||
|
||||
it('sets timeout and retry defaults', async () => {
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.timeout).toBe(300)
|
||||
expect(settings.retryMaxAttempts).toBe(3)
|
||||
expect(settings.retryMinBackoff).toBe(10)
|
||||
expect(settings.retryMaxBackoff).toBe(20)
|
||||
})
|
||||
|
||||
it('allows timeout 0 to disable', async () => {
|
||||
inputs.timeout = '0'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.timeout).toBe(0)
|
||||
})
|
||||
|
||||
it('parses custom timeout and retry values', async () => {
|
||||
inputs.timeout = '30'
|
||||
inputs['retry-max-attempts'] = '5'
|
||||
inputs['retry-min-backoff'] = '2'
|
||||
inputs['retry-max-backoff'] = '15'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.timeout).toBe(30)
|
||||
expect(settings.retryMaxAttempts).toBe(5)
|
||||
expect(settings.retryMinBackoff).toBe(2)
|
||||
expect(settings.retryMaxBackoff).toBe(15)
|
||||
})
|
||||
|
||||
it('clamps retry-max-backoff to min when less than min and warns', async () => {
|
||||
inputs['retry-min-backoff'] = '20'
|
||||
inputs['retry-max-backoff'] = '5'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.retryMaxBackoff).toBe(20)
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining("'retry-max-backoff' (5) is less than 'retry-min-backoff' (20)")
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults invalid timeout to 300 and warns', async () => {
|
||||
inputs.timeout = 'garbage'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.timeout).toBe(300)
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Invalid value 'garbage' for 'timeout'")
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults negative retry-max-attempts to 3 and warns', async () => {
|
||||
inputs['retry-max-attempts'] = '-1'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.retryMaxAttempts).toBe(3)
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Invalid value '-1' for 'retry-max-attempts'")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
22
action.yml
22
action.yml
|
|
@ -95,6 +95,28 @@ inputs:
|
|||
set-safe-directory:
|
||||
description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory <path>`
|
||||
default: true
|
||||
timeout:
|
||||
description: >
|
||||
Timeout in seconds for each git network operation attempt (e.g. fetch, lfs-fetch, ls-remote).
|
||||
If a single attempt exceeds this, the process is terminated.
|
||||
If retries are configured (see retry-max-attempts), the operation will be retried.
|
||||
Set to 0 to disable. Default is 300 (5 minutes).
|
||||
default: 300
|
||||
retry-max-attempts:
|
||||
description: >
|
||||
Total number of attempts for each git network operation (including the initial attempt).
|
||||
For example, 3 means one initial attempt plus up to 2 retries.
|
||||
default: 3
|
||||
retry-min-backoff:
|
||||
description: >
|
||||
Minimum backoff time in seconds between retry attempts.
|
||||
The actual backoff is randomly chosen between min and max.
|
||||
default: 10
|
||||
retry-max-backoff:
|
||||
description: >
|
||||
Maximum backoff time in seconds between retry attempts.
|
||||
The actual backoff is randomly chosen between min and max.
|
||||
default: 20
|
||||
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
|
||||
required: false
|
||||
|
|
|
|||
253
dist/index.js
vendored
253
dist/index.js
vendored
|
|
@ -655,6 +655,7 @@ const io = __importStar(__nccwpck_require__(7436));
|
|||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const regexpHelper = __importStar(__nccwpck_require__(3120));
|
||||
const retryHelper = __importStar(__nccwpck_require__(2155));
|
||||
const child_process_1 = __nccwpck_require__(2081);
|
||||
const git_version_1 = __nccwpck_require__(3142);
|
||||
// Auth header not supported before 2.9
|
||||
// Wire protocol v2 not supported before 2.18
|
||||
|
|
@ -678,6 +679,8 @@ class GitCommandManager {
|
|||
this.doSparseCheckout = false;
|
||||
this.workingDirectory = '';
|
||||
this.gitVersion = new git_version_1.GitVersion();
|
||||
this.timeoutMs = 0;
|
||||
this.networkRetryHelper = new retryHelper.RetryHelper();
|
||||
}
|
||||
branchDelete(remote, branch) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
|
|
@ -735,7 +738,7 @@ class GitCommandManager {
|
|||
}
|
||||
};
|
||||
// Suppress the output in order to avoid flooding annotations with innocuous errors.
|
||||
yield this.execGit(args, false, true, listeners);
|
||||
yield this.execGit(args, { silent: true, customListeners: listeners });
|
||||
core.debug(`stderr callback is: ${stderr}`);
|
||||
core.debug(`errline callback is: ${errline}`);
|
||||
core.debug(`stdout callback is: ${stdout}`);
|
||||
|
|
@ -823,7 +826,7 @@ class GitCommandManager {
|
|||
'--name-only',
|
||||
'--get-regexp',
|
||||
pattern
|
||||
], true);
|
||||
], { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -851,15 +854,15 @@ class GitCommandManager {
|
|||
args.push(arg);
|
||||
}
|
||||
const that = this;
|
||||
yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield that.execGit(args);
|
||||
yield this.networkRetryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield that.execGit(args, { timeoutMs: that.timeoutMs });
|
||||
}));
|
||||
});
|
||||
}
|
||||
getDefaultBranch(repositoryUrl) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let output;
|
||||
yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.networkRetryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
output = yield this.execGit([
|
||||
'ls-remote',
|
||||
'--quiet',
|
||||
|
|
@ -867,7 +870,7 @@ class GitCommandManager {
|
|||
'--symref',
|
||||
repositoryUrl,
|
||||
'HEAD'
|
||||
]);
|
||||
], { timeoutMs: this.timeoutMs });
|
||||
}));
|
||||
if (output) {
|
||||
// Satisfy compiler, will always be set
|
||||
|
|
@ -904,7 +907,7 @@ class GitCommandManager {
|
|||
isDetached() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Note, "branch --show-current" would be simpler but isn't available until Git 2.22
|
||||
const output = yield this.execGit(['rev-parse', '--symbolic-full-name', '--verify', '--quiet', 'HEAD'], true);
|
||||
const output = yield this.execGit(['rev-parse', '--symbolic-full-name', '--verify', '--quiet', 'HEAD'], { allowAllExitCodes: true });
|
||||
return !output.stdout.trim().startsWith('refs/heads/');
|
||||
});
|
||||
}
|
||||
|
|
@ -912,8 +915,8 @@ class GitCommandManager {
|
|||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const args = ['lfs', 'fetch', 'origin', ref];
|
||||
const that = this;
|
||||
yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield that.execGit(args);
|
||||
yield this.networkRetryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield that.execGit(args, { timeoutMs: that.timeoutMs });
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
@ -925,8 +928,8 @@ class GitCommandManager {
|
|||
log1(format) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const args = format ? ['log', '-1', format] : ['log', '-1'];
|
||||
const silent = format ? false : true;
|
||||
const output = yield this.execGit(args, false, silent);
|
||||
const silent = !format;
|
||||
const output = yield this.execGit(args, { silent });
|
||||
return output.stdout;
|
||||
});
|
||||
}
|
||||
|
|
@ -956,7 +959,7 @@ class GitCommandManager {
|
|||
shaExists(sha) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const args = ['rev-parse', '--verify', '--quiet', `${sha}^{object}`];
|
||||
const output = yield this.execGit(args, true);
|
||||
const output = yield this.execGit(args, { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -977,7 +980,10 @@ class GitCommandManager {
|
|||
if (recursive) {
|
||||
args.push('--recursive');
|
||||
}
|
||||
yield this.execGit(args);
|
||||
const that = this;
|
||||
yield this.networkRetryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield that.execGit(args, { timeoutMs: that.timeoutMs });
|
||||
}));
|
||||
});
|
||||
}
|
||||
submoduleUpdate(fetchDepth, recursive) {
|
||||
|
|
@ -990,12 +996,15 @@ class GitCommandManager {
|
|||
if (recursive) {
|
||||
args.push('--recursive');
|
||||
}
|
||||
yield this.execGit(args);
|
||||
const that = this;
|
||||
yield this.networkRetryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield that.execGit(args, { timeoutMs: that.timeoutMs });
|
||||
}));
|
||||
});
|
||||
}
|
||||
submoduleStatus() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.execGit(['submodule', 'status'], true);
|
||||
const output = yield this.execGit(['submodule', 'status'], { allowAllExitCodes: true });
|
||||
core.debug(output.stdout);
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
|
|
@ -1008,7 +1017,7 @@ class GitCommandManager {
|
|||
}
|
||||
tryClean() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.execGit(['clean', '-ffdx'], true);
|
||||
const output = yield this.execGit(['clean', '-ffdx'], { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -1019,7 +1028,7 @@ class GitCommandManager {
|
|||
globalConfig ? '--global' : '--local',
|
||||
'--unset-all',
|
||||
configKey
|
||||
], true);
|
||||
], { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -1033,19 +1042,19 @@ class GitCommandManager {
|
|||
args.push(globalConfig ? '--global' : '--local');
|
||||
}
|
||||
args.push('--unset', configKey, configValue);
|
||||
const output = yield this.execGit(args, true);
|
||||
const output = yield this.execGit(args, { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
tryDisableAutomaticGarbageCollection() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true);
|
||||
const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
tryGetFetchUrl() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.execGit(['config', '--local', '--get', 'remote.origin.url'], true);
|
||||
const output = yield this.execGit(['config', '--local', '--get', 'remote.origin.url'], { allowAllExitCodes: true });
|
||||
if (output.exitCode !== 0) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -1066,7 +1075,7 @@ class GitCommandManager {
|
|||
args.push(globalConfig ? '--global' : '--local');
|
||||
}
|
||||
args.push('--get-all', configKey);
|
||||
const output = yield this.execGit(args, true);
|
||||
const output = yield this.execGit(args, { allowAllExitCodes: true });
|
||||
if (output.exitCode !== 0) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -1086,7 +1095,7 @@ class GitCommandManager {
|
|||
args.push(globalConfig ? '--global' : '--local');
|
||||
}
|
||||
args.push('--name-only', '--get-regexp', pattern);
|
||||
const output = yield this.execGit(args, true);
|
||||
const output = yield this.execGit(args, { allowAllExitCodes: true });
|
||||
if (output.exitCode !== 0) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -1098,7 +1107,7 @@ class GitCommandManager {
|
|||
}
|
||||
tryReset() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.execGit(['reset', '--hard', 'HEAD'], true);
|
||||
const output = yield this.execGit(['reset', '--hard', 'HEAD'], { allowAllExitCodes: true });
|
||||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -1107,6 +1116,25 @@ class GitCommandManager {
|
|||
return this.gitVersion;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sets the timeout for network git operations.
|
||||
* @param timeoutSeconds Timeout in seconds. 0 disables the timeout.
|
||||
*/
|
||||
setTimeout(timeoutSeconds) {
|
||||
if (timeoutSeconds < 0) {
|
||||
throw new Error(`Timeout must be non-negative, got ${timeoutSeconds}`);
|
||||
}
|
||||
this.timeoutMs = timeoutSeconds * 1000;
|
||||
}
|
||||
/**
|
||||
* Configures retry behavior for network git operations.
|
||||
* @param maxAttempts Total attempts including the initial one. Must be >= 1.
|
||||
* @param minBackoffSeconds Minimum backoff between retries. Must be <= maxBackoffSeconds.
|
||||
* @param maxBackoffSeconds Maximum backoff between retries.
|
||||
*/
|
||||
setRetryConfig(maxAttempts, minBackoffSeconds, maxBackoffSeconds) {
|
||||
this.networkRetryHelper = new retryHelper.RetryHelper(maxAttempts, minBackoffSeconds, maxBackoffSeconds);
|
||||
}
|
||||
static createCommandManager(workingDirectory, lfs, doSparseCheckout) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const result = new GitCommandManager();
|
||||
|
|
@ -1115,8 +1143,19 @@ class GitCommandManager {
|
|||
});
|
||||
}
|
||||
execGit(args_1) {
|
||||
return __awaiter(this, arguments, void 0, function* (args, allowAllExitCodes = false, silent = false, customListeners = {}) {
|
||||
return __awaiter(this, arguments, void 0, function* (args, options = {}) {
|
||||
const { allowAllExitCodes = false, silent = false, customListeners = {}, timeoutMs = 0 } = options;
|
||||
fshelper.directoryExistsSync(this.workingDirectory, true);
|
||||
// Use child_process.spawn directly when timeout is set,
|
||||
// so we can kill the process on timeout and avoid orphaned git processes.
|
||||
// Note: customListeners are not supported in the timeout path.
|
||||
if (timeoutMs > 0) {
|
||||
if (customListeners &&
|
||||
Object.keys(customListeners).length > 0) {
|
||||
core.debug('customListeners are not supported with timeoutMs and will be ignored');
|
||||
}
|
||||
return yield this.execGitWithTimeout(args, timeoutMs, silent, allowAllExitCodes);
|
||||
}
|
||||
const result = new GitOutput();
|
||||
const env = {};
|
||||
for (const key of Object.keys(process.env)) {
|
||||
|
|
@ -1132,20 +1171,123 @@ class GitCommandManager {
|
|||
};
|
||||
const mergedListeners = Object.assign(Object.assign({}, defaultListener), customListeners);
|
||||
const stdout = [];
|
||||
const options = {
|
||||
const execOptions = {
|
||||
cwd: this.workingDirectory,
|
||||
env,
|
||||
silent,
|
||||
ignoreReturnCode: allowAllExitCodes,
|
||||
listeners: mergedListeners
|
||||
};
|
||||
result.exitCode = yield exec.exec(`"${this.gitPath}"`, args, options);
|
||||
result.exitCode = yield exec.exec(`"${this.gitPath}"`, args, execOptions);
|
||||
result.stdout = stdout.join('');
|
||||
core.debug(result.exitCode.toString());
|
||||
core.debug(result.stdout);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Executes a git command with a timeout. Uses child_process.spawn directly
|
||||
* (instead of @actions/exec) so we can kill the process on timeout and
|
||||
* terminate it cleanly. Does not support customListeners.
|
||||
*/
|
||||
execGitWithTimeout(args, timeoutMs, silent, allowAllExitCodes) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const result = new GitOutput();
|
||||
const env = {};
|
||||
for (const key of Object.keys(process.env)) {
|
||||
env[key] = process.env[key];
|
||||
}
|
||||
for (const key of Object.keys(this.gitEnv)) {
|
||||
env[key] = this.gitEnv[key];
|
||||
}
|
||||
const stdout = [];
|
||||
const stderr = [];
|
||||
return new Promise((resolve, reject) => {
|
||||
var _a;
|
||||
const child = (0, child_process_1.spawn)(this.gitPath, args, {
|
||||
cwd: this.workingDirectory,
|
||||
env,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
||||
stdout.push(data.toString());
|
||||
});
|
||||
if (child.stderr) {
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr.push(data.toString());
|
||||
if (!silent) {
|
||||
process.stderr.write(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
let settled = false;
|
||||
let timedOut = false;
|
||||
let forceKillTimer;
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer);
|
||||
if (forceKillTimer) {
|
||||
clearTimeout(forceKillTimer);
|
||||
}
|
||||
};
|
||||
const timer = global.setTimeout(() => {
|
||||
timedOut = true;
|
||||
// SIGTERM first, then force SIGKILL after 5 seconds.
|
||||
// On Windows, SIGTERM is equivalent to a forced kill, so
|
||||
// the SIGKILL fallback is effectively a no-op there.
|
||||
child.kill('SIGTERM');
|
||||
forceKillTimer = global.setTimeout(() => {
|
||||
try {
|
||||
child.kill('SIGKILL');
|
||||
}
|
||||
catch (killErr) {
|
||||
core.debug(`Failed to SIGKILL git process: ${killErr}`);
|
||||
}
|
||||
}, 5000);
|
||||
if (forceKillTimer.unref) {
|
||||
forceKillTimer.unref();
|
||||
}
|
||||
}, timeoutMs);
|
||||
if (timer.unref) {
|
||||
timer.unref();
|
||||
}
|
||||
child.on('close', (code) => {
|
||||
if (settled)
|
||||
return;
|
||||
settled = true;
|
||||
cleanup();
|
||||
if (timedOut) {
|
||||
reject(new Error(`Git operation timed out after ${timeoutMs / 1000} seconds: git ${args.slice(0, 5).join(' ')}...`));
|
||||
return;
|
||||
}
|
||||
// null code means killed by signal (e.g. OOM killer, external SIGTERM)
|
||||
if (code === null) {
|
||||
const stderrText = stderr.join('').trim();
|
||||
reject(new Error(`The process 'git' was killed by a signal` +
|
||||
(stderrText ? `\n${stderrText}` : '')));
|
||||
return;
|
||||
}
|
||||
if (code !== 0 && !allowAllExitCodes) {
|
||||
const stderrText = stderr.join('').trim();
|
||||
reject(new Error(`The process 'git' failed with exit code ${code}` +
|
||||
(stderrText ? `\n${stderrText}` : '')));
|
||||
return;
|
||||
}
|
||||
result.exitCode = code;
|
||||
result.stdout = stdout.join('');
|
||||
core.debug(result.exitCode.toString());
|
||||
core.debug(result.stdout);
|
||||
resolve(result);
|
||||
});
|
||||
child.on('error', (err) => {
|
||||
if (settled)
|
||||
return;
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
initializeCommandManager(workingDirectory, lfs, doSparseCheckout) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.workingDirectory = workingDirectory;
|
||||
|
|
@ -1448,6 +1590,10 @@ function getSource(settings) {
|
|||
core.startGroup('Getting Git version info');
|
||||
const git = yield getGitCommandManager(settings);
|
||||
core.endGroup();
|
||||
if (git) {
|
||||
git.setTimeout(settings.timeout);
|
||||
git.setRetryConfig(settings.retryMaxAttempts, settings.retryMinBackoff, settings.retryMaxBackoff);
|
||||
}
|
||||
let authHelper = null;
|
||||
try {
|
||||
if (git) {
|
||||
|
|
@ -2095,6 +2241,41 @@ 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}`);
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
|
@ -5260,6 +5441,7 @@ class Context {
|
|||
this.action = process.env.GITHUB_ACTION;
|
||||
this.actor = process.env.GITHUB_ACTOR;
|
||||
this.job = process.env.GITHUB_JOB;
|
||||
this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT, 10);
|
||||
this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER, 10);
|
||||
this.runId = parseInt(process.env.GITHUB_RUN_ID, 10);
|
||||
this.apiUrl = (_a = process.env.GITHUB_API_URL) !== null && _a !== void 0 ? _a : `https://api.github.com`;
|
||||
|
|
@ -6136,7 +6318,7 @@ class HttpClient {
|
|||
}
|
||||
const usingSsl = parsedUrl.protocol === 'https:';
|
||||
proxyAgent = new undici_1.ProxyAgent(Object.assign({ uri: proxyUrl.href, pipelining: !this._keepAlive ? 0 : 1 }, ((proxyUrl.username || proxyUrl.password) && {
|
||||
token: `${proxyUrl.username}:${proxyUrl.password}`
|
||||
token: `Basic ${Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString('base64')}`
|
||||
})));
|
||||
this._proxyAgentDispatcher = proxyAgent;
|
||||
if (usingSsl && this._ignoreSslError) {
|
||||
|
|
@ -6250,11 +6432,11 @@ function getProxyUrl(reqUrl) {
|
|||
})();
|
||||
if (proxyVar) {
|
||||
try {
|
||||
return new URL(proxyVar);
|
||||
return new DecodedURL(proxyVar);
|
||||
}
|
||||
catch (_a) {
|
||||
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
|
||||
return new URL(`http://${proxyVar}`);
|
||||
return new DecodedURL(`http://${proxyVar}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -6313,6 +6495,19 @@ function isLoopbackAddress(host) {
|
|||
hostLower.startsWith('[::1]') ||
|
||||
hostLower.startsWith('[0:0:0:0:0:0:0:1]'));
|
||||
}
|
||||
class DecodedURL extends URL {
|
||||
constructor(url, base) {
|
||||
super(url, base);
|
||||
this._decodedUsername = decodeURIComponent(super.username);
|
||||
this._decodedPassword = decodeURIComponent(super.password);
|
||||
}
|
||||
get username() {
|
||||
return this._decodedUsername;
|
||||
}
|
||||
get password() {
|
||||
return this._decodedPassword;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=proxy.js.map
|
||||
|
||||
/***/ }),
|
||||
|
|
|
|||
123
package-lock.json
generated
123
package-lock.json
generated
|
|
@ -69,20 +69,25 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
|
||||
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.1.tgz",
|
||||
"integrity": "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.2.0",
|
||||
"@octokit/core": "^5.0.1",
|
||||
"@octokit/plugin-paginate-rest": "^9.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
|
||||
"@octokit/plugin-paginate-rest": "^9.2.2",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.4.0",
|
||||
"@octokit/request": "^8.4.1",
|
||||
"@octokit/request-error": "^5.1.1",
|
||||
"undici": "^5.28.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz",
|
||||
"integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
||||
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.25.4"
|
||||
|
|
@ -681,10 +686,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -741,10 +747,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -810,10 +817,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
|
|
@ -1784,10 +1792,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
|
|
@ -3136,10 +3145,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -3214,10 +3224,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -3313,10 +3324,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -3536,10 +3548,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
|
||||
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
|
|
@ -3590,10 +3603,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
|
||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||
"dev": true
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
|
|
@ -3779,10 +3793,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -4579,10 +4594,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jake/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
@ -5186,10 +5202,11 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
|
|
@ -5486,12 +5503,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
|
||||
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
|
|
@ -6564,10 +6582,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import * as path from 'path'
|
|||
import * as refHelper from './ref-helper'
|
||||
import * as regexpHelper from './regexp-helper'
|
||||
import * as retryHelper from './retry-helper'
|
||||
import {spawn} from 'child_process'
|
||||
import {GitVersion} from './git-version'
|
||||
|
||||
// Auth header not supported before 2.9
|
||||
|
|
@ -80,6 +81,12 @@ export interface IGitCommandManager {
|
|||
): Promise<string[]>
|
||||
tryReset(): Promise<boolean>
|
||||
version(): Promise<GitVersion>
|
||||
setTimeout(timeoutSeconds: number): void
|
||||
setRetryConfig(
|
||||
maxAttempts: number,
|
||||
minBackoffSeconds: number,
|
||||
maxBackoffSeconds: number
|
||||
): void
|
||||
}
|
||||
|
||||
export async function createCommandManager(
|
||||
|
|
@ -104,6 +111,8 @@ class GitCommandManager {
|
|||
private doSparseCheckout = false
|
||||
private workingDirectory = ''
|
||||
private gitVersion: GitVersion = new GitVersion()
|
||||
private timeoutMs = 0
|
||||
private networkRetryHelper = new retryHelper.RetryHelper()
|
||||
|
||||
// Private constructor; use createCommandManager()
|
||||
private constructor() {}
|
||||
|
|
@ -168,7 +177,7 @@ class GitCommandManager {
|
|||
}
|
||||
|
||||
// Suppress the output in order to avoid flooding annotations with innocuous errors.
|
||||
await this.execGit(args, false, true, listeners)
|
||||
await this.execGit(args, {silent: true, customListeners: listeners})
|
||||
|
||||
core.debug(`stderr callback is: ${stderr}`)
|
||||
core.debug(`errline callback is: ${errline}`)
|
||||
|
|
@ -269,7 +278,7 @@ class GitCommandManager {
|
|||
'--get-regexp',
|
||||
pattern
|
||||
],
|
||||
true
|
||||
{allowAllExitCodes: true}
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
|
@ -312,22 +321,25 @@ class GitCommandManager {
|
|||
}
|
||||
|
||||
const that = this
|
||||
await retryHelper.execute(async () => {
|
||||
await that.execGit(args)
|
||||
await this.networkRetryHelper.execute(async () => {
|
||||
await that.execGit(args, {timeoutMs: that.timeoutMs})
|
||||
})
|
||||
}
|
||||
|
||||
async getDefaultBranch(repositoryUrl: string): Promise<string> {
|
||||
let output: GitOutput | undefined
|
||||
await retryHelper.execute(async () => {
|
||||
output = await this.execGit([
|
||||
'ls-remote',
|
||||
'--quiet',
|
||||
'--exit-code',
|
||||
'--symref',
|
||||
repositoryUrl,
|
||||
'HEAD'
|
||||
])
|
||||
await this.networkRetryHelper.execute(async () => {
|
||||
output = await this.execGit(
|
||||
[
|
||||
'ls-remote',
|
||||
'--quiet',
|
||||
'--exit-code',
|
||||
'--symref',
|
||||
repositoryUrl,
|
||||
'HEAD'
|
||||
],
|
||||
{timeoutMs: this.timeoutMs}
|
||||
)
|
||||
})
|
||||
|
||||
if (output) {
|
||||
|
|
@ -372,7 +384,7 @@ class GitCommandManager {
|
|||
// Note, "branch --show-current" would be simpler but isn't available until Git 2.22
|
||||
const output = await this.execGit(
|
||||
['rev-parse', '--symbolic-full-name', '--verify', '--quiet', 'HEAD'],
|
||||
true
|
||||
{allowAllExitCodes: true}
|
||||
)
|
||||
return !output.stdout.trim().startsWith('refs/heads/')
|
||||
}
|
||||
|
|
@ -381,8 +393,8 @@ class GitCommandManager {
|
|||
const args = ['lfs', 'fetch', 'origin', ref]
|
||||
|
||||
const that = this
|
||||
await retryHelper.execute(async () => {
|
||||
await that.execGit(args)
|
||||
await this.networkRetryHelper.execute(async () => {
|
||||
await that.execGit(args, {timeoutMs: that.timeoutMs})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -392,8 +404,8 @@ class GitCommandManager {
|
|||
|
||||
async log1(format?: string): Promise<string> {
|
||||
const args = format ? ['log', '-1', format] : ['log', '-1']
|
||||
const silent = format ? false : true
|
||||
const output = await this.execGit(args, false, silent)
|
||||
const silent = !format
|
||||
const output = await this.execGit(args, {silent})
|
||||
return output.stdout
|
||||
}
|
||||
|
||||
|
|
@ -422,7 +434,7 @@ class GitCommandManager {
|
|||
|
||||
async shaExists(sha: string): Promise<boolean> {
|
||||
const args = ['rev-parse', '--verify', '--quiet', `${sha}^{object}`]
|
||||
const output = await this.execGit(args, true)
|
||||
const output = await this.execGit(args, {allowAllExitCodes: true})
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
|
|
@ -443,7 +455,10 @@ class GitCommandManager {
|
|||
args.push('--recursive')
|
||||
}
|
||||
|
||||
await this.execGit(args)
|
||||
const that = this
|
||||
await this.networkRetryHelper.execute(async () => {
|
||||
await that.execGit(args, {timeoutMs: that.timeoutMs})
|
||||
})
|
||||
}
|
||||
|
||||
async submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> {
|
||||
|
|
@ -457,11 +472,14 @@ class GitCommandManager {
|
|||
args.push('--recursive')
|
||||
}
|
||||
|
||||
await this.execGit(args)
|
||||
const that = this
|
||||
await this.networkRetryHelper.execute(async () => {
|
||||
await that.execGit(args, {timeoutMs: that.timeoutMs})
|
||||
})
|
||||
}
|
||||
|
||||
async submoduleStatus(): Promise<boolean> {
|
||||
const output = await this.execGit(['submodule', 'status'], true)
|
||||
const output = await this.execGit(['submodule', 'status'], {allowAllExitCodes: true})
|
||||
core.debug(output.stdout)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
|
@ -472,7 +490,7 @@ class GitCommandManager {
|
|||
}
|
||||
|
||||
async tryClean(): Promise<boolean> {
|
||||
const output = await this.execGit(['clean', '-ffdx'], true)
|
||||
const output = await this.execGit(['clean', '-ffdx'], {allowAllExitCodes: true})
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
|
|
@ -487,7 +505,7 @@ class GitCommandManager {
|
|||
'--unset-all',
|
||||
configKey
|
||||
],
|
||||
true
|
||||
{allowAllExitCodes: true}
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
|
@ -506,14 +524,14 @@ class GitCommandManager {
|
|||
}
|
||||
args.push('--unset', configKey, configValue)
|
||||
|
||||
const output = await this.execGit(args, true)
|
||||
const output = await this.execGit(args, {allowAllExitCodes: true})
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async tryDisableAutomaticGarbageCollection(): Promise<boolean> {
|
||||
const output = await this.execGit(
|
||||
['config', '--local', 'gc.auto', '0'],
|
||||
true
|
||||
{allowAllExitCodes: true}
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
|
@ -521,7 +539,7 @@ class GitCommandManager {
|
|||
async tryGetFetchUrl(): Promise<string> {
|
||||
const output = await this.execGit(
|
||||
['config', '--local', '--get', 'remote.origin.url'],
|
||||
true
|
||||
{allowAllExitCodes: true}
|
||||
)
|
||||
|
||||
if (output.exitCode !== 0) {
|
||||
|
|
@ -549,7 +567,7 @@ class GitCommandManager {
|
|||
}
|
||||
args.push('--get-all', configKey)
|
||||
|
||||
const output = await this.execGit(args, true)
|
||||
const output = await this.execGit(args, {allowAllExitCodes: true})
|
||||
|
||||
if (output.exitCode !== 0) {
|
||||
return []
|
||||
|
|
@ -574,7 +592,7 @@ class GitCommandManager {
|
|||
}
|
||||
args.push('--name-only', '--get-regexp', pattern)
|
||||
|
||||
const output = await this.execGit(args, true)
|
||||
const output = await this.execGit(args, {allowAllExitCodes: true})
|
||||
|
||||
if (output.exitCode !== 0) {
|
||||
return []
|
||||
|
|
@ -587,7 +605,7 @@ class GitCommandManager {
|
|||
}
|
||||
|
||||
async tryReset(): Promise<boolean> {
|
||||
const output = await this.execGit(['reset', '--hard', 'HEAD'], true)
|
||||
const output = await this.execGit(['reset', '--hard', 'HEAD'], {allowAllExitCodes: true})
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
|
|
@ -595,6 +613,35 @@ class GitCommandManager {
|
|||
return this.gitVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for network git operations.
|
||||
* @param timeoutSeconds Timeout in seconds. 0 disables the timeout.
|
||||
*/
|
||||
setTimeout(timeoutSeconds: number): void {
|
||||
if (timeoutSeconds < 0) {
|
||||
throw new Error(`Timeout must be non-negative, got ${timeoutSeconds}`)
|
||||
}
|
||||
this.timeoutMs = timeoutSeconds * 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures retry behavior for network git operations.
|
||||
* @param maxAttempts Total attempts including the initial one. Must be >= 1.
|
||||
* @param minBackoffSeconds Minimum backoff between retries. Must be <= maxBackoffSeconds.
|
||||
* @param maxBackoffSeconds Maximum backoff between retries.
|
||||
*/
|
||||
setRetryConfig(
|
||||
maxAttempts: number,
|
||||
minBackoffSeconds: number,
|
||||
maxBackoffSeconds: number
|
||||
): void {
|
||||
this.networkRetryHelper = new retryHelper.RetryHelper(
|
||||
maxAttempts,
|
||||
minBackoffSeconds,
|
||||
maxBackoffSeconds
|
||||
)
|
||||
}
|
||||
|
||||
static async createCommandManager(
|
||||
workingDirectory: string,
|
||||
lfs: boolean,
|
||||
|
|
@ -611,12 +658,42 @@ class GitCommandManager {
|
|||
|
||||
private async execGit(
|
||||
args: string[],
|
||||
allowAllExitCodes = false,
|
||||
silent = false,
|
||||
customListeners = {}
|
||||
options: {
|
||||
allowAllExitCodes?: boolean
|
||||
silent?: boolean
|
||||
customListeners?: {}
|
||||
timeoutMs?: number
|
||||
} = {}
|
||||
): Promise<GitOutput> {
|
||||
const {
|
||||
allowAllExitCodes = false,
|
||||
silent = false,
|
||||
customListeners = {},
|
||||
timeoutMs = 0
|
||||
} = options
|
||||
|
||||
fshelper.directoryExistsSync(this.workingDirectory, true)
|
||||
|
||||
// Use child_process.spawn directly when timeout is set,
|
||||
// so we can kill the process on timeout and avoid orphaned git processes.
|
||||
// Note: customListeners are not supported in the timeout path.
|
||||
if (timeoutMs > 0) {
|
||||
if (
|
||||
customListeners &&
|
||||
Object.keys(customListeners).length > 0
|
||||
) {
|
||||
core.debug(
|
||||
'customListeners are not supported with timeoutMs and will be ignored'
|
||||
)
|
||||
}
|
||||
return await this.execGitWithTimeout(
|
||||
args,
|
||||
timeoutMs,
|
||||
silent,
|
||||
allowAllExitCodes
|
||||
)
|
||||
}
|
||||
|
||||
const result = new GitOutput()
|
||||
|
||||
const env = {}
|
||||
|
|
@ -636,7 +713,7 @@ class GitCommandManager {
|
|||
const mergedListeners = {...defaultListener, ...customListeners}
|
||||
|
||||
const stdout: string[] = []
|
||||
const options = {
|
||||
const execOptions = {
|
||||
cwd: this.workingDirectory,
|
||||
env,
|
||||
silent,
|
||||
|
|
@ -644,7 +721,8 @@ class GitCommandManager {
|
|||
listeners: mergedListeners
|
||||
}
|
||||
|
||||
result.exitCode = await exec.exec(`"${this.gitPath}"`, args, options)
|
||||
result.exitCode = await exec.exec(`"${this.gitPath}"`, args, execOptions)
|
||||
|
||||
result.stdout = stdout.join('')
|
||||
|
||||
core.debug(result.exitCode.toString())
|
||||
|
|
@ -653,6 +731,137 @@ class GitCommandManager {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a git command with a timeout. Uses child_process.spawn directly
|
||||
* (instead of @actions/exec) so we can kill the process on timeout and
|
||||
* terminate it cleanly. Does not support customListeners.
|
||||
*/
|
||||
private async execGitWithTimeout(
|
||||
args: string[],
|
||||
timeoutMs: number,
|
||||
silent: boolean,
|
||||
allowAllExitCodes: boolean
|
||||
): Promise<GitOutput> {
|
||||
const result = new GitOutput()
|
||||
|
||||
const env: {[key: string]: string} = {}
|
||||
for (const key of Object.keys(process.env)) {
|
||||
env[key] = process.env[key] as string
|
||||
}
|
||||
for (const key of Object.keys(this.gitEnv)) {
|
||||
env[key] = this.gitEnv[key]
|
||||
}
|
||||
|
||||
const stdout: string[] = []
|
||||
const stderr: string[] = []
|
||||
|
||||
return new Promise<GitOutput>((resolve, reject) => {
|
||||
const child = spawn(this.gitPath, args, {
|
||||
cwd: this.workingDirectory,
|
||||
env,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
})
|
||||
|
||||
child.stdout?.on('data', (data: Buffer) => {
|
||||
stdout.push(data.toString())
|
||||
})
|
||||
|
||||
if (child.stderr) {
|
||||
child.stderr.on('data', (data: Buffer) => {
|
||||
stderr.push(data.toString())
|
||||
if (!silent) {
|
||||
process.stderr.write(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let settled = false
|
||||
let timedOut = false
|
||||
let forceKillTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const cleanup = (): void => {
|
||||
clearTimeout(timer)
|
||||
if (forceKillTimer) {
|
||||
clearTimeout(forceKillTimer)
|
||||
}
|
||||
}
|
||||
|
||||
const timer = global.setTimeout(() => {
|
||||
timedOut = true
|
||||
// SIGTERM first, then force SIGKILL after 5 seconds.
|
||||
// On Windows, SIGTERM is equivalent to a forced kill, so
|
||||
// the SIGKILL fallback is effectively a no-op there.
|
||||
child.kill('SIGTERM')
|
||||
forceKillTimer = global.setTimeout(() => {
|
||||
try {
|
||||
child.kill('SIGKILL')
|
||||
} catch (killErr) {
|
||||
core.debug(
|
||||
`Failed to SIGKILL git process: ${killErr}`
|
||||
)
|
||||
}
|
||||
}, 5000)
|
||||
if (forceKillTimer.unref) {
|
||||
forceKillTimer.unref()
|
||||
}
|
||||
}, timeoutMs)
|
||||
if (timer.unref) {
|
||||
timer.unref()
|
||||
}
|
||||
|
||||
child.on('close', (code: number | null) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
cleanup()
|
||||
|
||||
if (timedOut) {
|
||||
reject(
|
||||
new Error(
|
||||
`Git operation timed out after ${timeoutMs / 1000} seconds: git ${args.slice(0, 5).join(' ')}...`
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// null code means killed by signal (e.g. OOM killer, external SIGTERM)
|
||||
if (code === null) {
|
||||
const stderrText = stderr.join('').trim()
|
||||
reject(
|
||||
new Error(
|
||||
`The process 'git' was killed by a signal` +
|
||||
(stderrText ? `\n${stderrText}` : '')
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (code !== 0 && !allowAllExitCodes) {
|
||||
const stderrText = stderr.join('').trim()
|
||||
reject(
|
||||
new Error(
|
||||
`The process 'git' failed with exit code ${code}` +
|
||||
(stderrText ? `\n${stderrText}` : '')
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
result.exitCode = code
|
||||
result.stdout = stdout.join('')
|
||||
core.debug(result.exitCode.toString())
|
||||
core.debug(result.stdout)
|
||||
resolve(result)
|
||||
})
|
||||
|
||||
child.on('error', (err: Error) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
cleanup()
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private async initializeCommandManager(
|
||||
workingDirectory: string,
|
||||
lfs: boolean,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,15 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
|||
const git = await getGitCommandManager(settings)
|
||||
core.endGroup()
|
||||
|
||||
if (git) {
|
||||
git.setTimeout(settings.timeout)
|
||||
git.setRetryConfig(
|
||||
settings.retryMaxAttempts,
|
||||
settings.retryMinBackoff,
|
||||
settings.retryMaxBackoff
|
||||
)
|
||||
}
|
||||
|
||||
let authHelper: gitAuthHelper.IGitAuthHelper | null = null
|
||||
try {
|
||||
if (git) {
|
||||
|
|
|
|||
|
|
@ -118,4 +118,26 @@ 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 each network git operation attempt (e.g. fetch, lfs-fetch, ls-remote).
|
||||
* 0 means no timeout.
|
||||
*/
|
||||
timeout: number
|
||||
|
||||
/**
|
||||
* Total number of attempts for each network git operation (including the initial attempt).
|
||||
* For example, 3 means one initial attempt plus up to 2 retries.
|
||||
*/
|
||||
retryMaxAttempts: number
|
||||
|
||||
/**
|
||||
* Minimum backoff time in seconds between retry attempts.
|
||||
*/
|
||||
retryMinBackoff: number
|
||||
|
||||
/**
|
||||
* Maximum backoff time in seconds between retry attempts.
|
||||
*/
|
||||
retryMaxBackoff: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,5 +161,60 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue