diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 5c5c08cf2..e150ccd10 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -378,12 +378,7 @@ class GitHubDriver extends VcsDriver return $this->attemptCloneFallback(); } - $rateLimited = false; - foreach ($e->getHeaders() as $header) { - if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) { - $rateLimited = true; - } - } + $rateLimited = $githubUtil->isRateLimited($e->getHeaders()); if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { @@ -397,7 +392,7 @@ class GitHubDriver extends VcsDriver } if ($rateLimited) { - $rateLimit = $this->getRateLimit($e->getHeaders()); + $rateLimit = $githubUtil->getRateLimit($e->getHeaders()); $this->io->writeError(sprintf( 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', $rateLimit['limit'], @@ -413,39 +408,6 @@ class GitHubDriver extends VcsDriver } } - /** - * Extract ratelimit from response. - * - * @param array $headers Headers from Composer\Downloader\TransportException. - * - * @return array Associative array with the keys limit and reset. - */ - protected function getRateLimit(array $headers) - { - $rateLimit = array( - 'limit' => '?', - 'reset' => '?', - ); - - foreach ($headers as $header) { - $header = trim($header); - if (false === strpos($header, 'X-RateLimit-')) { - continue; - } - list($type, $value) = explode(':', $header, 2); - switch ($type) { - case 'X-RateLimit-Limit': - $rateLimit['limit'] = (int) trim($value); - break; - case 'X-RateLimit-Reset': - $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); - break; - } - } - - return $rateLimit; - } - /** * Fetch root identifier from GitHub * diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 8415c9a5c..2f5dbe5cd 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -126,4 +126,55 @@ class GitHub return true; } + + /** + * Extract ratelimit from response. + * + * @param array $headers Headers from Composer\Downloader\TransportException. + * + * @return array Associative array with the keys limit and reset. + */ + public function getRateLimit(array $headers) + { + $rateLimit = array( + 'limit' => '?', + 'reset' => '?', + ); + + foreach ($headers as $header) { + $header = trim($header); + if (false === strpos($header, 'X-RateLimit-')) { + continue; + } + list($type, $value) = explode(':', $header, 2); + switch ($type) { + case 'X-RateLimit-Limit': + $rateLimit['limit'] = (int) trim($value); + break; + case 'X-RateLimit-Reset': + $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); + break; + } + } + + return $rateLimit; + } + + /** + * Finds whether a request failed due to rate limiting + * + * @param array $headers Headers from Composer\Downloader\TransportException. + * + * @return bool + */ + public function isRateLimited(array $headers) + { + foreach ($headers as $header) { + if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) { + return true; + } + } + + return false; + } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index ee18ad62d..dc2b33089 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -327,7 +327,7 @@ class RemoteFilesystem $warning = $data['warning']; } } - $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning); + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning, $http_response_header); } } @@ -639,11 +639,35 @@ class RemoteFilesystem } } - protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null) + protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array()) { if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { - $message = "\n".'Could not fetch '.$this->fileUrl.', please create a GitHub OAuth token '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit'); $gitHubUtil = new GitHub($this->io, $this->config, null); + $message = "\n"; + + $rateLimited = $gitHubUtil->isRateLimited($headers); + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($headers); + if ($this->io->hasAuthentication($this->originUrl)) { + $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; + } else { + $message = 'Create a GitHub OAuth token to go over the API rate limit.'; + } + + $message = sprintf( + 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.', + $rateLimit['limit'], + $rateLimit['reset'] + )."\n"; + } else { + $message .= 'Could not fetch '.$this->fileUrl.', please '; + if ($this->io->hasAuthentication($this->originUrl)) { + $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; + } else { + $message .= 'create a GitHub OAuth token to access private repos'; + } + } + if (!$gitHubUtil->authorizeOAuth($this->originUrl) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) ) {