diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6200d2123f98..b5efd1683b79 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+### [1.7.2] 2018-08-16
+
+ * Fixed reporting of authentication/rate limiting issues for GitHub API access
+ * Fixed `create-project` not checking the checking the latest commit out when a cache was already present
+ * Fixed reporting of errors when `global` command can not switch the working directory
+ * Fixed PHP 5.3 JSON encoding issues with complex unicode character sequences
+ * Updated to latest ca-bundle and xdebug-handler projects, see related changelogs
+
### [1.7.1] 2018-08-07
* Fixed issue autoloading plugins in require-dev in some conditions
@@ -679,6 +687,7 @@
* Initial release
+[1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2
[1.7.1]: https://github.com/composer/composer/compare/1.7.0...1.7.1
[1.7.0]: https://github.com/composer/composer/compare/1.7.0-RC...1.7.0
[1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC
diff --git a/composer.lock b/composer.lock
index 873419108ff4..690aca23edd9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
"packages": [
{
"name": "composer/ca-bundle",
- "version": "1.1.1",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169"
+ "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169",
- "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0",
+ "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0",
"shasum": ""
},
"require": {
@@ -60,7 +60,7 @@
"ssl",
"tls"
],
- "time": "2018-03-29T19:57:20+00:00"
+ "time": "2018-08-08T08:57:40+00:00"
},
{
"name": "composer/semver",
@@ -187,16 +187,16 @@
},
{
"name": "composer/xdebug-handler",
- "version": "1.1.0",
+ "version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
- "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08"
+ "reference": "e1809da56ce1bd1b547a752936817341ac244d8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08",
- "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e1809da56ce1bd1b547a752936817341ac244d8e",
+ "reference": "e1809da56ce1bd1b547a752936817341ac244d8e",
"shasum": ""
},
"require": {
@@ -227,7 +227,7 @@
"Xdebug",
"performance"
],
- "time": "2018-04-11T15:42:36+00:00"
+ "time": "2018-08-16T10:54:23+00:00"
},
{
"name": "justinrainbow/json-schema",
diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php
index a7cc6650b4cb..cca5f18711c5 100644
--- a/src/Composer/Command/CreateProjectCommand.php
+++ b/src/Composer/Command/CreateProjectCommand.php
@@ -345,10 +345,6 @@ protected function installRootPackage(IOInterface $io, Config $config, $packageN
$package = $package->getAliasOf();
}
- if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) {
- $package->setSourceReference(substr($package->getPrettyVersion(), 4));
- }
-
$dm = $this->createDownloadManager($io, $config);
$dm->setPreferSource($preferSource)
->setPreferDist($preferDist)
diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php
index 9e87c7e175e7..e13b8aaf2e72 100644
--- a/src/Composer/Command/GlobalCommand.php
+++ b/src/Composer/Command/GlobalCommand.php
@@ -13,6 +13,7 @@
namespace Composer\Command;
use Composer\Factory;
+use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\StringInput;
@@ -75,8 +76,22 @@ public function run(InputInterface $input, OutputInterface $output)
// change to global dir
$config = Factory::createConfig();
- chdir($config->get('home'));
- $this->getIO()->writeError('Changed current directory to '.$config->get('home').'');
+ $home = $config->get('home');
+
+ if (!is_dir($home)) {
+ $fs = new Filesystem();
+ $fs->ensureDirectoryExists($home);
+ if (!is_dir($home)) {
+ throw new \RuntimeException('Could not create home directory');
+ }
+ }
+
+ try {
+ chdir($home);
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e);
+ }
+ $this->getIO()->writeError('Changed current directory to '.$home.'');
// create new input without "global" command prefix
$input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1));
diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php
index a3972f44fc85..b1a131a4f03a 100644
--- a/src/Composer/Composer.php
+++ b/src/Composer/Composer.php
@@ -29,9 +29,9 @@
*/
class Composer
{
- const VERSION = '@package_version@';
- const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
- const RELEASE_DATE = '@release_date@';
+ const VERSION = '1.7.2';
+ const BRANCH_ALIAS_VERSION = '';
+ const RELEASE_DATE = '2018-08-16 16:57:12';
/**
* @var Package\RootPackageInterface
diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php
index 680a57bafc96..44acaff5929b 100644
--- a/src/Composer/Json/JsonFormatter.php
+++ b/src/Composer/Json/JsonFormatter.php
@@ -69,6 +69,13 @@ public static function format($json, $unescapeUnicode, $unescapeSlashes)
$l = strlen($match[1]);
if ($l % 2) {
+ $code = hexdec($match[2]);
+ // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped
+ // see https://github.com/composer/composer/issues/7510
+ if (0xD800 <= $code && 0xDFFF >= $code) {
+ return $match[0];
+ }
+
return str_repeat('\\', $l - 1) . mb_convert_encoding(
pack('H*', $match[2]),
'UTF-8',
diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php
index 5c5c08cf2423..e150ccd10110 100644
--- a/src/Composer/Repository/Vcs/GitHubDriver.php
+++ b/src/Composer/Repository/Vcs/GitHubDriver.php
@@ -378,12 +378,7 @@ protected function getContents($url, $fetchingRepoData = false)
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 @@ protected function getContents($url, $fetchingRepoData = false)
}
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 @@ protected function getContents($url, $fetchingRepoData = false)
}
}
- /**
- * 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 8415c9a5c9a9..2f5dbe5cda28 100644
--- a/src/Composer/Util/GitHub.php
+++ b/src/Composer/Util/GitHub.php
@@ -126,4 +126,55 @@ public function authorizeOAuthInteractively($originUrl, $message = null)
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/Hg.php b/src/Composer/Util/Hg.php
index 48d0b3687f13..8cf6241a628a 100644
--- a/src/Composer/Util/Hg.php
+++ b/src/Composer/Util/Hg.php
@@ -60,7 +60,7 @@ public function runCommand($commandCallable, $url, $cwd)
$command = call_user_func($commandCallable, $authenticatedUrl);
- if (0 === $this->process->execute($command)) {
+ if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
return;
}
diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php
index ee18ad62d7b9..dc2b33089fc2 100644
--- a/src/Composer/Util/RemoteFilesystem.php
+++ b/src/Composer/Util/RemoteFilesystem.php
@@ -327,7 +327,7 @@ protected function get($originUrl, $fileUrl, $additionalOptions = array(), $file
$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 @@ protected function callbackGet($notificationCode, $severity, $message, $messageC
}
}
- 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))
) {
diff --git a/tests/Composer/Test/Json/JsonFormatterTest.php b/tests/Composer/Test/Json/JsonFormatterTest.php
index 7be8fafe2df0..417f267e310b 100644
--- a/tests/Composer/Test/Json/JsonFormatterTest.php
+++ b/tests/Composer/Test/Json/JsonFormatterTest.php
@@ -18,33 +18,31 @@
class JsonFormatterTest extends TestCase
{
/**
- * Test if \u0119 (196+153) will get correctly formatted
- * See ticket #2613
+ * Test if \u0119 will get correctly formatted (unescaped)
+ * https://github.com/composer/composer/issues/2613
*/
public function testUnicodeWithPrependedSlash()
{
if (!extension_loaded('mbstring')) {
$this->markTestSkipped('Test requires the mbstring extension');
}
-
- $data = '"' . chr(92) . chr(92) . chr(92) . 'u0119"';
- $encodedData = JsonFormatter::format($data, true, true);
- $expected = '34+92+92+196+153+34';
- $this->assertEquals($expected, $this->getCharacterCodes($encodedData));
+ $backslash = chr(92);
+ $data = '"' . $backslash . $backslash . $backslash . 'u0119"';
+ $expected = '"' . $backslash . $backslash . 'ę"';
+ $this->assertEquals($expected, JsonFormatter::format($data, true, true));
}
/**
- * Convert string to character codes split by a plus sign
- * @param string $string
- * @return string
+ * Surrogate pairs are intentionally skipped and not unescaped
+ * https://github.com/composer/composer/issues/7510
*/
- protected function getCharacterCodes($string)
+ public function testUtf16SurrogatePair()
{
- $codes = array();
- for ($i = 0; $i < strlen($string); $i++) {
- $codes[] = ord($string[$i]);
+ if (!extension_loaded('mbstring')) {
+ $this->markTestSkipped('Test requires the mbstring extension');
}
- return implode('+', $codes);
+ $escaped = '"\ud83d\ude00"';
+ $this->assertEquals($escaped, JsonFormatter::format($escaped, true, true));
}
}