Add retry mechanism to Git clone and GitHub release fetching methods

This commit is contained in:
crazywhalecc
2026-05-09 14:13:47 +08:00
parent 2ed4b10260
commit 7b79767355
6 changed files with 35 additions and 21 deletions

View File

@@ -23,13 +23,13 @@ class go_win
$pkgroot = PKG_ROOT_PATH;
// get version
[$version] = explode("\n", default_shell()->executeCurl('https://go.dev/VERSION?m=text') ?: '');
[$version] = explode("\n", default_shell()->executeCurl('https://go.dev/VERSION?m=text', retries: $downloader->getRetry()) ?: '');
if ($version === '') {
throw new DownloaderException('Failed to get latest Go version from https://go.dev/VERSION?m=text');
}
// find SHA256 hash from download page
$page = default_shell()->executeCurl('https://go.dev/dl/');
$page = default_shell()->executeCurl('https://go.dev/dl/', retries: $downloader->getRetry());
if ($page === '' || $page === false) {
throw new DownloaderException('Failed to get Go download page from https://go.dev/dl/');
}

View File

@@ -39,11 +39,11 @@ class go_xcaddy
};
// get version and hash
[$version] = explode("\n", default_shell()->executeCurl('https://go.dev/VERSION?m=text') ?: '');
[$version] = explode("\n", default_shell()->executeCurl('https://go.dev/VERSION?m=text', retries: $downloader->getRetry()) ?: '');
if ($version === '') {
throw new DownloaderException('Failed to get latest Go version from https://go.dev/VERSION?m=text');
}
$page = default_shell()->executeCurl('https://go.dev/dl/');
$page = default_shell()->executeCurl('https://go.dev/dl/', retries: $downloader->getRetry());
if ($page === '' || $page === false) {
throw new DownloaderException('Failed to get Go download page from https://go.dev/dl/');
}

View File

@@ -20,7 +20,7 @@ class Git implements DownloadTypeInterface, CheckUpdateInterface
// direct branch clone
if (isset($config['rev'])) {
default_shell()->executeGitClone($config['url'], $config['rev'], $path, $shallow, $config['submodules'] ?? null);
default_shell()->executeGitClone($config['url'], $config['rev'], $path, $shallow, $config['submodules'] ?? null, $downloader->getRetry());
$shell = PHP_OS_FAMILY === 'Windows' ? cmd(false) : shell(false);
$hash_result = $shell->execWithResult(SPC_GIT_EXEC . ' -C ' . escapeshellarg($path) . ' rev-parse --short HEAD');
$hash = ($hash_result[0] === 0 && !empty($hash_result[1])) ? trim($hash_result[1][0]) : '';
@@ -66,7 +66,7 @@ class Git implements DownloadTypeInterface, CheckUpdateInterface
$version = array_key_first($matched_version_branch);
$branch = $matched_version_branch[$version];
logger()->info("Matched version {$version} from branch {$branch} for {$name}");
default_shell()->executeGitClone($config['url'], $branch, $path, $shallow, $config['submodules'] ?? null);
default_shell()->executeGitClone($config['url'], $branch, $path, $shallow, $config['submodules'] ?? null, $downloader->getRetry());
return DownloadResult::git($name, $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
}
throw new DownloaderException("No matching branch found for regex {$config['regex']} (checked {$matched_count} branches).");

View File

@@ -21,13 +21,13 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface, CheckU
private ?string $version = null;
public function getGitHubReleases(string $name, string $repo, bool $prefer_stable = true, ?string $query = null): array
public function getGitHubReleases(string $name, string $repo, bool $prefer_stable = true, ?string $query = null, int $retries = 0): array
{
logger()->debug("Fetching {$name} GitHub releases from {$repo}");
$url = str_replace('{repo}', $repo, self::API_URL);
$url .= ($query ?? '');
$headers = $this->getGitHubTokenHeaders();
$data2 = default_shell()->executeCurl($url, headers: $headers);
$data2 = default_shell()->executeCurl($url, headers: $headers, retries: $retries);
$data = json_decode($data2 ?: '', true);
if (!is_array($data)) {
throw new DownloaderException("Failed to get GitHub release API info for {$repo} from {$url}");
@@ -46,13 +46,13 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface, CheckU
* Get the latest GitHub release assets for a given repository.
* match_asset is provided, only return the asset that matches the regex.
*/
public function getLatestGitHubRelease(string $name, string $repo, bool $prefer_stable, string $match_asset, ?string $query = null): array
public function getLatestGitHubRelease(string $name, string $repo, bool $prefer_stable, string $match_asset, ?string $query = null, int $retries = 0): array
{
logger()->debug("Fetching {$name} GitHub release from {$repo}");
$url = str_replace('{repo}', $repo, self::API_URL);
$url .= ($query ?? '');
$headers = $this->getGitHubTokenHeaders();
$data2 = default_shell()->executeCurl($url, headers: $headers);
$data2 = default_shell()->executeCurl($url, headers: $headers, retries: $retries);
$data = json_decode($data2 ?: '', true);
if (!is_array($data)) {
throw new DownloaderException("Failed to get GitHub release API info for {$repo} from {$url}");
@@ -84,7 +84,7 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface, CheckU
if (!isset($config['match'])) {
throw new DownloaderException("GitHubRelease downloader requires 'match' config for {$name}");
}
$rel = $this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match'], $config['query'] ?? null);
$rel = $this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match'], $config['query'] ?? null, $downloader->getRetry());
// download file using curl
$asset_url = str_replace(['{repo}', '{id}'], [$config['repo'], $rel['id']], self::ASSET_URL);
@@ -124,7 +124,7 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface, CheckU
if (!isset($config['match'])) {
throw new DownloaderException("GitHubRelease downloader requires 'match' config for {$name}");
}
$this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match'], $config['query'] ?? null);
$this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match'], $config['query'] ?? null, $downloader->getRetry());
$new_version = $this->version ?? $old_version ?? '';
return new CheckUpdateResult(
old: $old_version,

View File

@@ -22,11 +22,11 @@ class GitHubTarball implements DownloadTypeInterface, CheckUpdateInterface
* Get the GitHub tarball URL for a given repository and release type.
* If match_url is provided, only return the tarball that matches the regex.
*/
public function getGitHubTarballInfo(string $name, string $repo, string $rel_type, bool $prefer_stable = true, ?string $match_url = null, ?string $basename = null, ?string $query = null): array
public function getGitHubTarballInfo(string $name, string $repo, string $rel_type, bool $prefer_stable = true, ?string $match_url = null, ?string $basename = null, ?string $query = null, int $retries = 0): array
{
if ($rel_type === 'releases' && $match_url === null && $query === null && $prefer_stable) {
$api_url = str_replace(['{repo}', '{rel_type}'], [$repo, 'releases/latest'], self::API_URL);
$data = default_shell()->executeCurl($api_url, headers: $this->getGitHubTokenHeaders());
$data = default_shell()->executeCurl($api_url, headers: $this->getGitHubTokenHeaders(), retries: $retries);
$data = json_decode($data ?: '', true);
if (!is_array($data) || empty($data['tarball_url'])) {
throw new DownloaderException("Failed to get GitHub latest release for {$repo} from {$api_url}");
@@ -36,7 +36,7 @@ class GitHubTarball implements DownloadTypeInterface, CheckUpdateInterface
} else {
$api_url = str_replace(['{repo}', '{rel_type}'], [$repo, $rel_type], self::API_URL);
$api_url .= ($query ?? '');
$data = default_shell()->executeCurl($api_url, headers: $this->getGitHubTokenHeaders());
$data = default_shell()->executeCurl($api_url, headers: $this->getGitHubTokenHeaders(), retries: $retries);
$data = json_decode($data ?: '', true);
if (!is_array($data)) {
throw new DownloaderException("Failed to get GitHub tarball URL for {$repo} from {$api_url}");
@@ -65,7 +65,7 @@ class GitHubTarball implements DownloadTypeInterface, CheckUpdateInterface
}
$this->version = $version ?? null;
}
$head = default_shell()->executeCurl($rel_url, 'HEAD', headers: $this->getGitHubTokenHeaders()) ?: '';
$head = default_shell()->executeCurl($rel_url, 'HEAD', headers: $this->getGitHubTokenHeaders(), retries: $retries) ?: '';
preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?<filename>.+\.tar\.gz)\1/im', $head, $matches);
if ($matches) {
$filename = $matches['filename'];
@@ -84,9 +84,9 @@ class GitHubTarball implements DownloadTypeInterface, CheckUpdateInterface
'ghtagtar' => 'tags',
default => throw new DownloaderException("Invalid GitHubTarball type for {$name}"),
};
[$url, $filename] = $this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null);
[$url, $filename] = $this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null, $downloader->getRetry());
$path = DOWNLOAD_PATH . "/{$filename}";
default_shell()->executeCurlDownload($url, $path, headers: $this->getGitHubTokenHeaders());
default_shell()->executeCurlDownload($url, $path, headers: $this->getGitHubTokenHeaders(), retries: $downloader->getRetry());
return DownloadResult::archive($filename, $config, $config['extract'] ?? null, version: $this->version, downloader: static::class);
}
@@ -97,7 +97,7 @@ class GitHubTarball implements DownloadTypeInterface, CheckUpdateInterface
'ghtagtar' => 'tags',
default => throw new DownloaderException("Invalid GitHubTarball type for {$name}"),
};
$this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null);
$this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null, $downloader->getRetry());
$new_version = $this->version ?? $old_version ?? '';
return new CheckUpdateResult(
old: $old_version,

View File

@@ -84,7 +84,7 @@ class DefaultShell extends Shell
/**
* Execute a Git clone command to clone a repository.
*/
public function executeGitClone(string $url, string $branch, string $path, bool $shallow = true, ?array $submodules = null): void
public function executeGitClone(string $url, string $branch, string $path, bool $shallow = true, ?array $submodules = null, int $retries = 0): void
{
$path = FileSystem::convertPath($path);
if (file_exists($path)) {
@@ -99,7 +99,21 @@ class DefaultShell extends Shell
$cmd = clean_spaces("{$git} clone -c http.lowSpeedLimit=1 -c http.lowSpeedTime=3600 --config core.autocrlf=false --branch {$branch_arg} {$shallow_arg} {$submodules_arg} {$url_arg} {$path_arg}");
$this->logCommandInfo($cmd);
logger()->debug("[GIT CLONE] {$cmd}");
$this->passthru($cmd, $this->console_putput);
try {
$this->passthru($cmd, $this->console_putput);
} catch (InterruptException $e) {
throw $e;
} catch (\Throwable $e) {
if ($retries > 0) {
logger()->warning("Git clone failed, retrying... ({$retries} retries left)");
if (is_dir($path)) {
FileSystem::removeDir($path);
}
$this->executeGitClone($url, $branch, $path, $shallow, $submodules, $retries - 1);
return;
}
throw $e;
}
if ($submodules !== null) {
$depth_flag = $shallow ? '--depth 1' : '';
foreach ($submodules as $submodule) {