diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e6075148..ade8d99cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+### [1.9.1] 2019-11-01
+
+ * Fixed various credential handling issues with gitlab and github
+ * Fixed credentials being present in git remotes in Composer cache and vendor directory when not using SSH keys
+ * Fixed `composer why` not listing replacers as a reason something is present
+ * Fixed various PHP 7.4 compatibility issues
+ * Fixed root warnings always present in Docker containers, setting COMPOSER_ALLOW_SUPERUSER is not necessary anymore
+ * Fixed GitHub access tokens leaking into debug-verbosity output
+ * Fixed several edge case issues detecting GitHub, Bitbucket and GitLab repository types
+ * Fixed Composer asking if you want to use a composer.json in a parent directory when ran in non-interactive mode
+ * Fixed classmap autoloading issue finding classes located within a few non-PHP context blocks (?>... `mkdir -p /usr/local/bin`.
> **Note:** For information on changing your PATH, please read the
-> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google.
+> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use
+> your search engine of choice.
Now run `composer` in order to run Composer instead of `php composer.phar`.
@@ -139,7 +140,7 @@ C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
Add the directory to your PATH environment variable if it isn't already.
For information on changing your PATH variable, please see
[this article](https://www.computerhope.com/issues/ch000549.htm) and/or
-use Google.
+use your search engine of choice.
Close your current terminal. Test usage with a new terminal:
diff --git a/doc/06-config.md b/doc/06-config.md
index f3afc4eb1..6ffbea411 100644
--- a/doc/06-config.md
+++ b/doc/06-config.md
@@ -296,4 +296,9 @@ Example:
Defaults to `true`. If set to `false`, Composer will not create `.htaccess` files
in the composer home, cache, and data directories.
+## lock
+
+Defaults to `true`. If set to `false`, Composer will not create a `composer.lock`
+file.
+
← [Repositories](05-repositories.md) | [Community](07-community.md) →
diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md
index fbc5d0417..1185bd689 100644
--- a/doc/articles/scripts.md
+++ b/doc/articles/scripts.md
@@ -339,6 +339,9 @@ One limitation of this is that you can not call multiple commands in
a row like `@php install && @php foo`. You must split them up in a
JSON array of commands.
+You can also call a shell/bash script, which will have the path to
+the PHP executable available in it as a `PHP_BINARY` env var.
+
## Custom descriptions.
You can set custom script descriptions with the following in your `composer.json`:
diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md
index d0a64506f..7a5725ec4 100644
--- a/doc/articles/troubleshooting.md
+++ b/doc/articles/troubleshooting.md
@@ -211,6 +211,19 @@ To enable the swap you can use for example:
```
You can make a permanent swap file following this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04).
+## proc_open(): failed to open stream errors (Windows)
+
+If composer shows proc_open(NUL) errors on Windows:
+
+`proc_open(NUL): failed to open stream: No such file or directory`
+
+This could be happening because you are working in a _OneDrive_ directory and
+using a version of PHP that does not support the file system semantics of this
+service. The issue was fixed in PHP 7.2.23 and 7.3.10.
+
+Alternatively it could be because the Windows Null Service is not enabled. For
+more information, see this [issue](https://github.com/composer/composer/issues/7186#issuecomment-373134916).
+
## Degraded Mode
Due to some intermittent issues on Travis and other systems, we introduced a
diff --git a/res/composer-schema.json b/res/composer-schema.json
index cb3594f7b..bd04c5d5f 100644
--- a/res/composer-schema.json
+++ b/res/composer-schema.json
@@ -11,7 +11,8 @@
},
"type": {
"description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
- "type": "string"
+ "type": "string",
+ "pattern": "^[a-z0-9-]+$"
},
"target-dir": {
"description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
@@ -39,7 +40,8 @@
},
"version": {
"type": "string",
- "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes."
+ "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.",
+ "pattern": "^v?\\d+(((\\.\\d+)?\\.\\d+)?\\.\\d+)?"
},
"time": {
"type": "string",
@@ -290,6 +292,10 @@
"sort-packages": {
"type": "boolean",
"description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
+ },
+ "lock": {
+ "type": "boolean",
+ "description": "Defaults to true. If set to false, Composer will not create a composer.lock file."
}
}
},
diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php
index d970ca5b1..5e068ccff 100644
--- a/src/Composer/Autoload/AutoloadGenerator.php
+++ b/src/Composer/Autoload/AutoloadGenerator.php
@@ -256,15 +256,14 @@ EOF;
continue;
}
- $namespaceFilter = $namespace === '' ? null : $namespace;
- $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
+ $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap);
}
}
}
}
foreach ($autoloads['classmap'] as $dir) {
- $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap);
+ $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
}
ksort($classMap);
@@ -317,9 +316,9 @@ EOF;
return count($classMap);
}
- private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array())
+ private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap = array())
{
- foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) {
+ foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) {
$pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
if (!isset($classMap[$class])) {
$classMap[$class] = $pathCode;
@@ -334,9 +333,9 @@ EOF;
return $classMap;
}
- private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true)
+ private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, $showAmbiguousWarning = true)
{
- return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter);
+ return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType);
}
public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@@ -447,7 +446,7 @@ EOF;
foreach ($autoloads['classmap'] as $dir) {
try {
- $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false));
+ $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false));
} catch (\RuntimeException $e) {
$this->io->writeError(''.$e->getMessage().'');
}
@@ -592,6 +591,9 @@ class ComposerAutoloaderInit$suffix
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::\$loader) {
diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php
index 1ecf96bfe..ef12809d5 100644
--- a/src/Composer/Autoload/ClassMapGenerator.php
+++ b/src/Composer/Autoload/ClassMapGenerator.php
@@ -50,17 +50,19 @@ class ClassMapGenerator
/**
* Iterate over all files in the given directory searching for classes
*
- * @param \Iterator|string $path The path to search in or an iterator
- * @param string $blacklist Regex that matches against the file path that exclude from the classmap.
- * @param IOInterface $io IO object
- * @param string $namespace Optional namespace prefix to filter by
+ * @param \Iterator|string $path The path to search in or an iterator
+ * @param string $blacklist Regex that matches against the file path that exclude from the classmap.
+ * @param IOInterface $io IO object
+ * @param string $namespace Optional namespace prefix to filter by
+ * @param string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
*
* @throws \RuntimeException When the path is neither an existing file nor directory
* @return array A class map array
*/
- public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null)
+ public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null)
{
if (is_string($path)) {
+ $basePath = $path;
if (is_file($path)) {
$path = array(new \SplFileInfo($path));
} elseif (is_dir($path)) {
@@ -71,6 +73,8 @@ class ClassMapGenerator
'" which does not appear to be a file nor a folder'
);
}
+ } elseif (null !== $autoloadType) {
+ throw new \RuntimeException('Path must be a string when specifying an autoload type');
}
$map = array();
@@ -100,10 +104,14 @@ class ClassMapGenerator
}
$classes = self::findClasses($filePath);
+ if (null !== $autoloadType) {
+ $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
+ }
foreach ($classes as $class) {
// skip classes not within the given namespace prefix
- if (null !== $namespace && 0 !== strpos($class, $namespace)) {
+ // TODO enable in Composer v1.11 or 2.0 whichever comes first
+ if (/* null === $autoloadType && */ null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) {
continue;
}
@@ -121,6 +129,72 @@ class ClassMapGenerator
return $map;
}
+ /**
+ * Remove classes which could not have been loaded by namespace autoloaders
+ *
+ * @param array $classes found classes in given file
+ * @param string $filePath current file
+ * @param string $baseNamespace prefix of given autoload mapping
+ * @param string $namespaceType psr-0|psr-4
+ * @param string $basePath root directory of given autoload mapping
+ * @param IOInterface $io IO object
+ * @return array valid classes
+ */
+ private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io)
+ {
+ $validClasses = array();
+ $rejectedClasses = array();
+
+ $realSubPath = substr($filePath, strlen($basePath) + 1);
+ $realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.'));
+
+ foreach ($classes as $class) {
+ // silently skip if ns doesn't have common root
+ if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
+ continue;
+ }
+ // transform class name to file path and validate
+ if ('psr-0' === $namespaceType) {
+ $namespaceLength = strrpos($class, '\\');
+ if (false !== $namespaceLength) {
+ $namespace = substr($class, 0, $namespaceLength + 1);
+ $className = substr($class, $namespaceLength + 1);
+ $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
+ . str_replace('_', DIRECTORY_SEPARATOR, $className);
+ }
+ else {
+ $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
+ }
+ } elseif ('psr-4' === $namespaceType) {
+ $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
+ $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
+ } else {
+ throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
+ }
+ if ($subPath === $realSubPath) {
+ $validClasses[] = $class;
+ } else {
+ $rejectedClasses[] = $class;
+ }
+ }
+ // warn only if no valid classes, else silently skip invalid
+ if (empty($validClasses)) {
+ foreach ($rejectedClasses as $class) {
+ trigger_error(
+ "Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. It will not autoload anymore in Composer v1.11+.",
+ E_USER_DEPRECATED
+ );
+ }
+
+ // TODO enable in Composer v1.11 or 2.0 whichever comes first
+ //return array();
+ }
+
+ // TODO enable in Composer v1.11 or 2.0 whichever comes first & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses
+ //return $validClasses;
+ return $classes;
+ }
+
/**
* Extract the classes in the given file
*
@@ -173,7 +247,7 @@ class ClassMapGenerator
}
}
// strip non-php blocks in the file
- $contents = preg_replace('{\?>.+<\?}s', '?>', $contents);
+ $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?>', $contents);
// strip trailing non-php code if needed
$pos = strrpos($contents, '?>');
if (false !== $pos && false === strpos(substr($contents, $pos), '')) {
diff --git a/src/Composer/Command/AboutCommand.php b/src/Composer/Command/AboutCommand.php
index d1472ba17..bf1fa0f9c 100644
--- a/src/Composer/Command/AboutCommand.php
+++ b/src/Composer/Command/AboutCommand.php
@@ -42,5 +42,7 @@ EOT
See https://getcomposer.org/ for more information.
EOT
);
+
+ return 0;
}
}
diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php
index b2b617c42..00fed9240 100644
--- a/src/Composer/Command/BaseDependencyCommand.php
+++ b/src/Composer/Command/BaseDependencyCommand.php
@@ -62,7 +62,7 @@ class BaseDependencyCommand extends BaseCommand
* @param InputInterface $input
* @param OutputInterface $output
* @param bool $inverted Whether to invert matching process (why-not vs why behaviour)
- * @return int|null Exit code of the operation.
+ * @return int Exit code of the operation.
*/
protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
{
diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php
index ec51c56d3..08ec04701 100644
--- a/src/Composer/Command/ClearCacheCommand.php
+++ b/src/Composer/Command/ClearCacheCommand.php
@@ -70,5 +70,7 @@ EOT
}
$io->writeError('All caches cleared.');
+
+ return 0;
}
}
diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php
index 38b103bb6..329230e2e 100644
--- a/src/Composer/Command/ConfigCommand.php
+++ b/src/Composer/Command/ConfigCommand.php
@@ -412,6 +412,7 @@ EOT
),
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
+ 'lock' => array($booleanValidator, $booleanNormalizer),
);
$multiConfigValues = array(
'github-protocols' => array(
@@ -463,13 +464,19 @@ EOT
$this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.');
}
- return $this->configSource->removeConfigSetting($settingKey);
+ $this->configSource->removeConfigSetting($settingKey);
+
+ return 0;
}
if (isset($uniqueConfigValues[$settingKey])) {
- return $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
+ $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
+
+ return 0;
}
if (isset($multiConfigValues[$settingKey])) {
- return $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
+ $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
+
+ return 0;
}
// handle properties
@@ -530,38 +537,51 @@ EOT
throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
}
if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
- return $this->configSource->removeProperty($settingKey);
+ $this->configSource->removeProperty($settingKey);
+
+ return 0;
}
if (isset($uniqueProps[$settingKey])) {
- return $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
+ $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
+
+ return 0;
}
if (isset($multiProps[$settingKey])) {
- return $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
+ $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
+
+ return 0;
}
// handle repositories
if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) {
- return $this->configSource->removeRepository($matches[1]);
+ $this->configSource->removeRepository($matches[1]);
+
+ return 0;
}
if (2 === count($values)) {
- return $this->configSource->addRepository($matches[1], array(
+ $this->configSource->addRepository($matches[1], array(
'type' => $values[0],
'url' => $values[1],
));
+
+ return 0;
}
if (1 === count($values)) {
$value = strtolower($values[0]);
if (true === $booleanValidator($value)) {
if (false === $booleanNormalizer($value)) {
- return $this->configSource->addRepository($matches[1], false);
+ $this->configSource->addRepository($matches[1], false);
+
+ return 0;
}
} else {
$value = JsonFile::parseJson($values[0]);
+ $this->configSource->addRepository($matches[1], $value);
- return $this->configSource->addRepository($matches[1], $value);
+ return 0;
}
}
@@ -571,22 +591,32 @@ EOT
// handle extra
if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) {
- return $this->configSource->removeProperty($settingKey);
+ $this->configSource->removeProperty($settingKey);
+
+ return 0;
}
- return $this->configSource->addProperty($settingKey, $values[0]);
+ $this->configSource->addProperty($settingKey, $values[0]);
+
+ return 0;
}
// handle platform
if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) {
- return $this->configSource->removeConfigSetting($settingKey);
+ $this->configSource->removeConfigSetting($settingKey);
+
+ return 0;
}
- return $this->configSource->addConfigSetting($settingKey, $values[0]);
+ $this->configSource->addConfigSetting($settingKey, $values[0]);
+
+ return 0;
}
if ($settingKey === 'platform' && $input->getOption('unset')) {
- return $this->configSource->removeConfigSetting($settingKey);
+ $this->configSource->removeConfigSetting($settingKey);
+
+ return 0;
}
// handle auth
@@ -595,7 +625,7 @@ EOT
$this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
$this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
- return;
+ return 0;
}
if ($matches[1] === 'bitbucket-oauth') {
@@ -618,16 +648,20 @@ EOT
$this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
}
- return;
+ return 0;
}
// handle script
if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) {
- return $this->configSource->removeProperty($settingKey);
+ $this->configSource->removeProperty($settingKey);
+
+ return 0;
}
- return $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);
+ $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);
+
+ return 0;
}
throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php
index d6adec083..c350fde9b 100644
--- a/src/Composer/Command/DependsCommand.php
+++ b/src/Composer/Command/DependsCommand.php
@@ -48,7 +48,7 @@ EOT
*
* @param InputInterface $input
* @param OutputInterface $output
- * @return int|null
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php
index 3add15166..f1e91f15c 100644
--- a/src/Composer/Command/DumpAutoloadCommand.php
+++ b/src/Composer/Command/DumpAutoloadCommand.php
@@ -63,11 +63,11 @@ EOT
$apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');
if ($authoritative) {
- $this->getIO()->writeError('Generating optimized autoload files (authoritative)', false);
+ $this->getIO()->write('Generating optimized autoload files (authoritative)');
} elseif ($optimize) {
- $this->getIO()->writeError('Generating optimized autoload files', false);
+ $this->getIO()->write('Generating optimized autoload files');
} else {
- $this->getIO()->writeError('Generating autoload files', false);
+ $this->getIO()->write('Generating autoload files');
}
$generator = $composer->getAutoloadGenerator();
@@ -78,11 +78,13 @@ EOT
$numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
if ($authoritative) {
- $this->getIO()->overwriteError('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes');
+ $this->getIO()->write('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes');
} elseif ($optimize) {
- $this->getIO()->overwriteError('Generated optimized autoload files containing '. $numberOfClasses .' classes');
+ $this->getIO()->write('Generated optimized autoload files containing '. $numberOfClasses .' classes');
} else {
- $this->getIO()->overwriteError('Generated autoload files containing '. $numberOfClasses .' classes');
+ $this->getIO()->write('Generated autoload files containing '. $numberOfClasses .' classes');
}
+
+ return 0;
}
}
diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php
index 19ff99e9d..1c7704e83 100644
--- a/src/Composer/Command/InitCommand.php
+++ b/src/Composer/Command/InitCommand.php
@@ -152,6 +152,8 @@ EOT
if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
$this->installDependencies($output);
}
+
+ return 0;
}
/**
@@ -400,7 +402,7 @@ EOT
return $this->repos;
}
- protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true)
+ final protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true, $fixed = false)
{
if ($requires) {
$requires = $this->normalizeRequirements($requires);
@@ -410,7 +412,7 @@ EOT
foreach ($requires as $requirement) {
if (!isset($requirement['version'])) {
// determine the best version automatically
- list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability);
+ list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, null, null, $fixed);
$requirement['version'] = $version;
// replace package name from packagist.org
@@ -423,7 +425,7 @@ EOT
));
} else {
// check that the specified version/constraint exists before we proceed
- list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev');
+ list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev', $fixed);
// replace package name from packagist.org
$requirement['name'] = $name;
@@ -700,10 +702,11 @@ EOT
* @param string $preferredStability
* @param string|null $requiredVersion
* @param string $minimumStability
+ * @param bool $fixed
* @throws \InvalidArgumentException
* @return array name version
*/
- private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null)
+ private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = null)
{
// find the latest version allowed in this repo set
$versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
@@ -777,7 +780,7 @@ EOT
return array(
$package->getPrettyName(),
- $versionSelector->findRecommendedRequireVersion($package),
+ $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
);
}
diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php
index b3c30d63b..7537945e9 100644
--- a/src/Composer/Command/LicensesCommand.php
+++ b/src/Composer/Command/LicensesCommand.php
@@ -110,6 +110,8 @@ EOT
default:
throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format));
}
+
+ return 0;
}
/**
diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php
index ae26a7487..599087246 100644
--- a/src/Composer/Command/OutdatedCommand.php
+++ b/src/Composer/Command/OutdatedCommand.php
@@ -59,7 +59,7 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
{
$args = array(
- 'show',
+ 'command' => 'show',
'--latest' => true,
);
if (!$input->getOption('all')) {
diff --git a/src/Composer/Command/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php
index 9e5575c74..1e18e5e23 100644
--- a/src/Composer/Command/ProhibitsCommand.php
+++ b/src/Composer/Command/ProhibitsCommand.php
@@ -48,7 +48,7 @@ EOT
*
* @param InputInterface $input
* @param OutputInterface $output
- * @return int|null
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php
index 876371635..af16a3898 100644
--- a/src/Composer/Command/RequireCommand.php
+++ b/src/Composer/Command/RequireCommand.php
@@ -49,6 +49,7 @@ class RequireCommand extends InitCommand
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
+ new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
@@ -99,7 +100,9 @@ EOT
return 1;
}
- if (!is_readable($this->file)) {
+ // check for readability by reading the file as is_readable can not be trusted on network-mounts
+ // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
+ if (!is_readable($this->file) && false === Silencer::call('file_get_contents', $this->file)) {
$io->writeError(''.$this->file.' is not readable.');
return 1;
@@ -120,6 +123,25 @@ EOT
return 1;
}
+ if ($input->getOption('fixed') === true) {
+ $config = $this->json->read();
+
+ $packageType = empty($config['type']) ? 'library' : $config['type'];
+
+ /**
+ * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
+ */
+ if ($packageType !== 'project') {
+ $io->writeError('"--fixed" option is allowed for "project" package types only to prevent possible misuses.');
+
+ if (empty($config['type'])) {
+ $io->writeError('If your package is not library, you should explicitly specify "type" parameter in composer.json.');
+ }
+
+ return 1;
+ }
+ }
+
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
$repos = $composer->getRepositoryManager()->getRepositories();
@@ -137,7 +159,15 @@ EOT
}
$phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
- $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'));
+ try {
+ $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'), $input->getOption('fixed'));
+ } catch (\Exception $e) {
+ if ($this->newlyCreated) {
+ throw new \RuntimeException('No composer.json present in the current directory, this may be the cause of the following exception.', 0, $e);
+ }
+
+ throw $e;
+ }
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php
index 54aa4dcea..0e8aa60e4 100644
--- a/src/Composer/Command/SearchCommand.php
+++ b/src/Composer/Command/SearchCommand.php
@@ -79,5 +79,7 @@ EOT
foreach ($results as $result) {
$io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
}
+
+ return 0;
}
}
diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php
index 0e85f0ce3..9beb0b8c4 100644
--- a/src/Composer/Command/SelfUpdateCommand.php
+++ b/src/Composer/Command/SelfUpdateCommand.php
@@ -254,6 +254,8 @@ TAGSPUBKEY
} else {
$io->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible');
}
+
+ return 0;
}
protected function fetchKeys(IOInterface $io, Config $config)
diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php
index dde0f0062..ffc0776dd 100644
--- a/src/Composer/Command/ShowCommand.php
+++ b/src/Composer/Command/ShowCommand.php
@@ -129,6 +129,12 @@ EOT
return 1;
}
+ if ($input->getOption('tree') && $input->getOption('path')) {
+ $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)');
+
+ return 1;
+ }
+
$format = $input->getOption('format');
if (!in_array($format, array('text', 'json'))) {
$io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
@@ -586,6 +592,7 @@ EOT
}
$io->write('type : ' . $package->getType());
$this->printLicenses($package);
+ $io->write('homepage : ' . $package->getHomepage());
$io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
$io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
if ($installedRepo->hasPackage($package)) {
diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php
index 06fb2ab05..b9708c3db 100644
--- a/src/Composer/Command/StatusCommand.php
+++ b/src/Composer/Command/StatusCommand.php
@@ -61,7 +61,7 @@ EOT
/**
* @param InputInterface $input
* @param OutputInterface $output
- * @return int|null
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php
index 411feb202..6c2619751 100644
--- a/src/Composer/Command/SuggestsCommand.php
+++ b/src/Composer/Command/SuggestsCommand.php
@@ -93,7 +93,7 @@ EOT
continue;
}
foreach ($package['suggest'] as $suggestion => $reason) {
- if (false === strpos('/', $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
+ if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
continue;
}
if (!isset($installed[$suggestion])) {
@@ -121,7 +121,7 @@ EOT
$io->write(sprintf('%s', $suggestion));
}
- return null;
+ return 0;
}
// Grouped by package
@@ -151,5 +151,7 @@ EOT
$io->write('');
}
}
+
+ return 0;
}
}
diff --git a/src/Composer/Config.php b/src/Composer/Config.php
index 7abca7dfa..1050096b1 100644
--- a/src/Composer/Config.php
+++ b/src/Composer/Config.php
@@ -63,6 +63,7 @@ class Config
'archive-dir' => '.',
'htaccess-protect' => true,
'use-github-api' => true,
+ 'lock' => true,
// valid keys without defaults (auth config stuff):
// bitbucket-oauth
// github-oauth
@@ -329,6 +330,8 @@ class Config
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'use-github-api':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
+ case 'lock':
+ return $this->config[$key] !== 'false' && (bool) $this->config[$key];
default:
if (!isset($this->config[$key])) {
return null;
diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php
index a9fb2e117..c7ec2dad6 100644
--- a/src/Composer/Console/Application.php
+++ b/src/Composer/Console/Application.php
@@ -113,6 +113,10 @@ class Application extends BaseApplication
{
$this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
+ if (getenv('COMPOSER_NO_INTERACTION')) {
+ $input->setInteractive(false);
+ }
+
$io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
new QuestionHelper(),
)));
@@ -208,11 +212,7 @@ class Application extends BaseApplication
$io->writeError(sprintf('Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF']));
}
- if (getenv('COMPOSER_NO_INTERACTION')) {
- $input->setInteractive(false);
- }
-
- if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER')) {
+ if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER') && !file_exists('/.dockerenv')) {
if (function_exists('posix_getuid') && posix_getuid() === 0) {
if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
$io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details');
diff --git a/src/Composer/DependencyResolver/Decisions.php b/src/Composer/DependencyResolver/Decisions.php
index 86b62c3d3..e2773501f 100644
--- a/src/Composer/DependencyResolver/Decisions.php
+++ b/src/Composer/DependencyResolver/Decisions.php
@@ -183,7 +183,7 @@ class Decisions implements \Iterator, \Countable
$previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
if ($previousDecision != 0) {
- $literalString = $this->pool->literalToString($literal);
+ $literalString = $this->pool->literalToPrettyString($literal, array());
$package = $this->pool->literalToPackage($literal);
throw new SolverBugException(
"Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."
diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php
index 3d3d947f4..ba026d2e2 100644
--- a/src/Composer/Downloader/GitDownloader.php
+++ b/src/Composer/Downloader/GitDownloader.php
@@ -74,10 +74,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$command =
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
. '&& cd '.$flag.'%path% '
- . '&& git remote set-url origin %url% && git remote add composer %url%';
+ . '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
} else {
$msg = "Cloning ".$this->getShortHash($ref);
- $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer';
+ $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer && git remote set-url origin %sanitizedUrl% && git remote set-url composer %sanitizedUrl%';
if (getenv('COMPOSER_DISABLE_NETWORK')) {
throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
}
@@ -87,11 +87,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$commandCallable = function ($url) use ($path, $command, $cachePath) {
return str_replace(
- array('%url%', '%path%', '%cachePath%'),
+ array('%url%', '%path%', '%cachePath%', '%sanitizedUrl%'),
array(
ProcessExecutor::escape($url),
ProcessExecutor::escape($path),
ProcessExecutor::escape($cachePath),
+ ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
),
$command
);
@@ -129,10 +130,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
if (!empty($this->cachedPackages[$target->getId()][$ref])) {
$msg = "Checking out ".$this->getShortHash($ref).' from cache';
- $command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %url%';
+ $command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
} else {
$msg = "Checking out ".$this->getShortHash($ref);
- $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)';
+ $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
if (getenv('COMPOSER_DISABLE_NETWORK')) {
throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
}
@@ -142,11 +143,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$commandCallable = function ($url) use ($ref, $command, $cachePath) {
return str_replace(
- array('%url%', '%ref%', '%cachePath%'),
+ array('%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'),
array(
ProcessExecutor::escape($url),
ProcessExecutor::escape($ref.'^{commit}'),
ProcessExecutor::escape($cachePath),
+ ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
),
$command
);
diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php
index 9fa0b1573..8b5c85057 100644
--- a/src/Composer/Installer.php
+++ b/src/Composer/Installer.php
@@ -130,7 +130,7 @@ class Installer
protected $preferStable = false;
protected $preferLowest = false;
protected $skipSuggest = false;
- protected $writeLock = true;
+ protected $writeLock;
protected $executeOperations = true;
/**
@@ -177,6 +177,8 @@ class Installer
$this->installationManager = $installationManager;
$this->eventDispatcher = $eventDispatcher;
$this->autoloadGenerator = $autoloadGenerator;
+
+ $this->writeLock = $config->get('lock');
}
/**
diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php
index fb10fb678..ddf8b6e73 100644
--- a/src/Composer/Repository/BaseRepository.php
+++ b/src/Composer/Repository/BaseRepository.php
@@ -98,6 +98,27 @@ abstract class BaseRepository implements RepositoryInterface
// Replacements are considered valid reasons for a package to be installed during forward resolution
if (!$invert) {
$links += $package->getReplaces();
+
+ // On forward search, check if any replaced package was required and add the replaced
+ // packages to the list of needles. Contrary to the cross-reference link check below,
+ // replaced packages are the target of links.
+ foreach ($package->getReplaces() as $link) {
+ foreach ($needles as $needle) {
+ if ($link->getSource() === $needle) {
+ if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
+ // already displayed this node's dependencies, cutting short
+ if (in_array($link->getTarget(), $packagesInTree)) {
+ $results[] = array($package, $link, false);
+ continue;
+ }
+ $packagesInTree[] = $link->getTarget();
+ $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : array();
+ $results[] = array($package, $link, $dependents);
+ $needles[] = $link->getTarget();
+ }
+ }
+ }
+ }
}
// Require-dev is only relevant for the root package
@@ -112,12 +133,12 @@ abstract class BaseRepository implements RepositoryInterface
if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
// already displayed this node's dependencies, cutting short
if (in_array($link->getSource(), $packagesInTree)) {
- $results[$link->getSource()] = array($package, $link, false);
+ $results[] = array($package, $link, false);
continue;
}
$packagesInTree[] = $link->getSource();
$dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
- $results[$link->getSource()] = array($package, $link, $dependents);
+ $results[] = array($package, $link, $dependents);
}
}
}
diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php
index 61ebc8d8c..2f26af2f7 100644
--- a/src/Composer/Repository/PathRepository.php
+++ b/src/Composer/Repository/PathRepository.php
@@ -125,7 +125,13 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
{
parent::initialize();
- foreach ($this->getUrlMatches() as $url) {
+ $urlMatches = $this->getUrlMatches();
+
+ if (empty($urlMatches)) {
+ throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist');
+ }
+
+ foreach ($urlMatches as $url) {
$path = realpath($url) . DIRECTORY_SEPARATOR;
$composerFilePath = $path.'composer.json';
@@ -155,7 +161,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
if (!isset($package['version'])) {
$versionData = $this->versionGuesser->guessVersion($package, $path);
- $package['version'] = $versionData['pretty_version'] ?: 'dev-master';
+ if (is_array($versionData) && $versionData['pretty_version']) {
+ $package['version'] = $versionData['pretty_version'];
+ } else {
+ $package['version'] = 'dev-master';
+ }
}
$output = '';
diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php
index 5b0f26639..22180b55d 100644
--- a/src/Composer/Repository/Vcs/BitbucketDriver.php
+++ b/src/Composer/Repository/Vcs/BitbucketDriver.php
@@ -47,7 +47,7 @@ abstract class BitbucketDriver extends VcsDriver
*/
public function initialize()
{
- preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#', $this->url, $match);
+ preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#i', $this->url, $match);
$this->owner = $match[1];
$this->repository = $match[2];
$this->originUrl = 'bitbucket.org';
diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php
index 5770a8326..08dbe9233 100644
--- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php
+++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php
@@ -53,7 +53,7 @@ class GitBitbucketDriver extends BitbucketDriver
*/
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
- if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
+ if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#i', $url)) {
return false;
}
diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php
index 55940e212..c9ed229b8 100644
--- a/src/Composer/Repository/Vcs/GitHubDriver.php
+++ b/src/Composer/Repository/Vcs/GitHubDriver.php
@@ -48,10 +48,10 @@ class GitHubDriver extends VcsDriver
*/
public function initialize()
{
- preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
+ preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
$this->owner = $match[3];
$this->repository = $match[4];
- $this->originUrl = !empty($match[1]) ? $match[1] : $match[2];
+ $this->originUrl = strtolower(!empty($match[1]) ? $match[1] : $match[2]);
if ($this->originUrl === 'www.github.com') {
$this->originUrl = 'github.com';
}
@@ -270,12 +270,12 @@ class GitHubDriver extends VcsDriver
*/
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
- if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
+ if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
return false;
}
$originUrl = !empty($matches[2]) ? $matches[2] : $matches[3];
- if (!in_array(preg_replace('{^www\.}i', '', $originUrl), $config->get('github-domains'))) {
+ if (!in_array(strtolower(preg_replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) {
return false;
}
diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php
index 2037a8c63..a1f810bfa 100644
--- a/src/Composer/Repository/Vcs/GitLabDriver.php
+++ b/src/Composer/Repository/Vcs/GitLabDriver.php
@@ -497,6 +497,8 @@ class GitLabDriver extends VcsDriver
*/
private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber)
{
+ $guessedDomain = strtolower($guessedDomain);
+
if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
if ($portNumber) {
return $guessedDomain.':'.$portNumber;
diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php
index a919e7860..2d0596467 100644
--- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php
+++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php
@@ -53,7 +53,7 @@ class HgBitbucketDriver extends BitbucketDriver
*/
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
- if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
+ if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#i', $url)) {
return false;
}
diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php
index 3812e7a66..04a363442 100644
--- a/src/Composer/Repository/Vcs/HgDriver.php
+++ b/src/Composer/Repository/Vcs/HgDriver.php
@@ -71,7 +71,7 @@ class HgDriver extends VcsDriver
return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
};
- $hgUtils->runCommand($command, $this->url, $this->repoDir);
+ $hgUtils->runCommand($command, $this->url, null);
}
}
diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php
index 7085f2561..7b8d33d68 100644
--- a/src/Composer/Util/AuthHelper.php
+++ b/src/Composer/Util/AuthHelper.php
@@ -23,6 +23,7 @@ class AuthHelper
{
protected $io;
protected $config;
+ private $displayedOriginAuthentications = array();
public function __construct(IOInterface $io, Config $config)
{
@@ -116,7 +117,7 @@ class AuthHelper
$message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
$gitLabUtil = new GitLab($this->io, $this->config, null);
- if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && $auth['password'] === 'private-token') {
+ if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token'), true)) {
throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
}
@@ -172,7 +173,7 @@ class AuthHelper
throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
}
- $this->io->writeError(' Authentication required ('.parse_url($url, PHP_URL_HOST).'):');
+ $this->io->writeError(' Authentication required ('.$origin.'):');
$username = $this->io->ask(' Username: ');
$password = $this->io->askAndHideAnswer(' Password: ');
$this->io->setAuthentication($origin, $username, $password);
@@ -193,14 +194,18 @@ class AuthHelper
public function addAuthenticationHeader(array $headers, $origin, $url)
{
if ($this->io->hasAuthentication($origin)) {
+ $authenticationDisplayMessage = null;
$auth = $this->io->getAuthentication($origin);
if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
$headers[] = 'Authorization: token '.$auth['username'];
+ $authenticationDisplayMessage = 'Using GitHub token authentication';
} elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
if ($auth['password'] === 'oauth2') {
$headers[] = 'Authorization: Bearer '.$auth['username'];
- } elseif ($auth['password'] === 'private-token') {
+ $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
+ } elseif ($auth['password'] === 'private-token' || $auth['password'] === 'gitlab-ci-token') {
$headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
+ $authenticationDisplayMessage = 'Using GitLab private token authentication';
}
} elseif (
'bitbucket.org' === $origin
@@ -209,10 +214,17 @@ class AuthHelper
) {
if (!$this->isPublicBitBucketDownload($url)) {
$headers[] = 'Authorization: Bearer ' . $auth['password'];
+ $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
}
} else {
$authStr = base64_encode($auth['username'] . ':' . $auth['password']);
$headers[] = 'Authorization: Basic '.$authStr;
+ $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"';
+ }
+
+ if ($authenticationDisplayMessage && !in_array($origin, $this->displayedOriginAuthentications, true)) {
+ $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG);
+ $this->displayedOriginAuthentications[] = $origin;
}
}
@@ -243,4 +255,15 @@ class AuthHelper
return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
}
+
+ /**
+ * @param string $url
+ * @return string
+ */
+ public function stripCredentialsFromUrl($url)
+ {
+ // GitHub repository rename result in redirect locations containing the access_token as GET parameter
+ // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
+ return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
+ }
}
diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php
index 6118df938..b1a106709 100644
--- a/src/Composer/Util/Filesystem.php
+++ b/src/Composer/Util/Filesystem.php
@@ -687,12 +687,14 @@ class Filesystem
if (!Platform::isWindows()) {
return false;
}
+
+ // Important to clear all caches first
+ clearstatcache(true, $junction);
+
if (!is_dir($junction) || is_link($junction)) {
return false;
}
- // Important to clear all caches first
- clearstatcache(true, $junction);
$stat = lstat($junction);
// S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php
index 48e91ba84..39a66f898 100644
--- a/src/Composer/Util/Git.php
+++ b/src/Composer/Util/Git.php
@@ -54,9 +54,9 @@ class Git
}
if (!$initialClone) {
- // capture username/password from URL if there is one
+ // capture username/password from URL if there is one and we have no auth configured yet
$this->process->execute('git remote -v', $output, $cwd);
- if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) {
+ if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
$this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
}
}
@@ -95,8 +95,10 @@ class Git
$auth = null;
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
- // private github repository without git access, try https with auth
- if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)) {
+ // private github repository without ssh key access, try https with auth
+ if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
+ || preg_match('{^(https?)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)
+ ) {
if (!$this->io->hasAuthentication($match[1])) {
$gitHubUtil = new GitHub($this->io, $this->config, $this->process);
$message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
@@ -153,7 +155,14 @@ class Git
return;
}
}
- } elseif (preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
+ } elseif (
+ preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
+ || preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)
+ ) {
+ if ($match[1] === 'git') {
+ $match[1] = 'https';
+ }
+
if (!$this->io->hasAuthentication($match[2])) {
$gitLabUtil = new GitLab($this->io, $this->config, $this->process);
$message = 'Cloning failed, enter your GitLab credentials to access private repos';
@@ -165,17 +174,18 @@ class Git
if ($this->io->hasAuthentication($match[2])) {
$auth = $this->io->getAuthentication($match[2]);
- if($auth['password'] === 'private-token' || $auth['password'] === 'oauth2') {
+ if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') {
$authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
} else {
$authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
}
+
$command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
return;
}
}
- } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate
+ } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate
if (strpos($match[2], '@')) {
list($authParts, $match[2]) = explode('@', $match[2], 2);
}
@@ -193,7 +203,7 @@ class Git
}
}
- $this->io->writeError(' Authentication required (' . parse_url($url, PHP_URL_HOST) . '):');
+ $this->io->writeError(' Authentication required (' . $match[2] . '):');
$auth = array(
'username' => $this->io->ask(' Username: ', $defaultUsername),
'password' => $this->io->askAndHideAnswer(' Password: '),
@@ -215,10 +225,12 @@ class Git
}
}
+ $errorMsg = $this->process->getErrorOutput();
if ($initialClone) {
$this->filesystem->removeDirectory($origCwd);
}
- $this->throwException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(), $url);
+
+ $this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
}
}
@@ -232,7 +244,9 @@ class Git
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
try {
$commandCallable = function ($url) {
- return sprintf('git remote set-url origin %s && git remote update --prune origin', ProcessExecutor::escape($url));
+ $sanitizedUrl = preg_replace('{://([^@]+?):(.+?)@}', '://', $url);
+
+ return sprintf('git remote set-url origin %s && git remote update --prune origin && git remote set-url origin %s', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
};
$this->runCommand($commandCallable, $url, $dir);
} catch (\Exception $e) {
@@ -255,16 +269,29 @@ class Git
}
public function fetchRefOrSyncMirror($url, $dir, $ref)
+ {
+ if ($this->checkRefIsInMirror($url, $dir, $ref)) {
+ return true;
+ }
+
+ if ($this->syncMirror($url, $dir)) {
+ return $this->checkRefIsInMirror($url, $dir, $ref);
+ }
+
+ return false;
+ }
+
+ private function checkRefIsInMirror($url, $dir, $ref)
{
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
$escapedRef = ProcessExecutor::escape($ref.'^{commit}');
- $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $output, $dir);
+ $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir);
if ($exitCode === 0) {
return true;
}
}
- return $this->syncMirror($url, $dir);
+ return false;
}
private function isAuthenticationFailure($url, &$match)
diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php
index a163290fe..99ee1ca3c 100644
--- a/src/Composer/Util/Http/CurlDownloader.php
+++ b/src/Composer/Util/Http/CurlDownloader.php
@@ -195,7 +195,7 @@ class CurlDownloader
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
if ($attributes['redirects'] === 0) {
- $this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG);
+ $this->io->writeError('Downloading ' . $this->authHelper->stripCredentialsFromUrl($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
}
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
@@ -254,12 +254,12 @@ class CurlDownloader
$contents = stream_get_contents($job['bodyHandle']);
}
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
- $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
+ $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
} else {
rewind($job['bodyHandle']);
$contents = stream_get_contents($job['bodyHandle']);
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
- $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
+ $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
}
fclose($job['bodyHandle']);
@@ -362,7 +362,7 @@ class CurlDownloader
}
if (!empty($targetUrl)) {
- $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG);
+ $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
return $targetUrl;
}
diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php
index 76a2176a8..cf39aaf9d 100644
--- a/src/Composer/Util/RemoteFilesystem.php
+++ b/src/Composer/Util/RemoteFilesystem.php
@@ -246,7 +246,7 @@ class RemoteFilesystem
$actualContextOptions = stream_context_get_options($ctx);
$usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
- $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG);
+ $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $this->authHelper->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
unset($origFileUrl, $actualContextOptions);
// Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
@@ -704,7 +704,7 @@ class RemoteFilesystem
$this->redirects++;
$this->io->writeError('', true, IOInterface::DEBUG);
- $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl), true, IOInterface::DEBUG);
+ $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
$additionalOptions['redirects'] = $this->redirects;
diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php
index 8c79d106c..ab10d5bbf 100644
--- a/src/Composer/Util/Zip.php
+++ b/src/Composer/Util/Zip.php
@@ -21,7 +21,6 @@ class Zip
* Gets content of the root composer.json inside a ZIP archive.
*
* @param string $pathToZip
- * @param string $filename
*
* @return string|null
*/
diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php
index 39d462544..94128e737 100644
--- a/tests/Composer/Test/ApplicationTest.php
+++ b/tests/Composer/Test/ApplicationTest.php
@@ -25,12 +25,18 @@ class ApplicationTest extends TestCase
$inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
$outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
+ putenv('COMPOSER_NO_INTERACTION=1');
+
$index = 0;
$inputMock->expects($this->at($index++))
->method('hasParameterOption')
->with($this->equalTo('--no-plugins'))
->will($this->returnValue(true));
+ $inputMock->expects($this->at($index++))
+ ->method('setInteractive')
+ ->with($this->equalTo(false));
+
$inputMock->expects($this->at($index++))
->method('hasParameterOption')
->with($this->equalTo('--no-cache'))
@@ -83,12 +89,18 @@ class ApplicationTest extends TestCase
$inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
$outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
+ putenv('COMPOSER_NO_INTERACTION=1');
+
$index = 0;
$inputMock->expects($this->at($index++))
->method('hasParameterOption')
->with($this->equalTo('--no-plugins'))
->will($this->returnValue(true));
+ $inputMock->expects($this->at($index++))
+ ->method('setInteractive')
+ ->with($this->equalTo(false));
+
$inputMock->expects($this->at($index++))
->method('hasParameterOption')
->with($this->equalTo('--no-cache'))
diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
index 84ac16df7..ba89ccea9 100644
--- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
+++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -548,6 +548,48 @@ class AutoloadGeneratorTest extends TestCase
);
}
+ public function testPSRToClassMapIgnoresNonPSRClasses()
+ {
+ $package = new Package('a', '1.0', '1.0');
+
+ $this->markTestSkipped('Skipped until ClassMapGenerator ignoring of invalid PSR-x classes is enabled');
+
+ $package->setAutoload(array(
+ 'psr-0' => array('psr0_' => 'psr0/'),
+ 'psr-4' => array('psr4\\' => 'psr4/'),
+ ));
+
+ $this->repository->expects($this->once())
+ ->method('getCanonicalPackages')
+ ->will($this->returnValue(array()));
+
+ $this->fs->ensureDirectoryExists($this->workingDir.'/psr0/psr0');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/psr4');
+ file_put_contents($this->workingDir.'/psr0/psr0/match.php', 'workingDir.'/psr0/psr0/badfile.php', 'workingDir.'/psr4/match.php', 'workingDir.'/psr4/badfile.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1');
+ $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated.");
+
+ $expectedClassmap = << \$baseDir . '/psr0/psr0/match.php',
+ 'psr4\\\\match' => \$baseDir . '/psr4/match.php',
+);
+
+EOF;
+ $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap);
+ }
+
public function testVendorsClassMapAutoloading()
{
$package = new Package('a', '1.0', '1.0');
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
index e830e21f8..4e5e0a5cb 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoloadOrder
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::$loader) {
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
index ede883fd7..0f1201b8e 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::$loader) {
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php
index 6459cac05..fc457c406 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php
@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::$loader) {
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php
index 75306684a..b07825176 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php
@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::$loader) {
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
index 32f822812..12ac24108 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
@@ -13,6 +13,9 @@ class ComposerAutoloaderInitIncludePath
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::$loader) {
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
index 1d6676c83..084d04f30 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
@@ -13,6 +13,9 @@ class ComposerAutoloaderInitTargetDir
}
}
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
public static function getLoader()
{
if (null !== self::$loader) {
diff --git a/tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php b/tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php
index 1ad22ecf8..7fe3bee86 100644
--- a/tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php
+++ b/tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php
@@ -1385,6 +1385,10 @@ namespace Foo;
+ public function a1381() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null)); }
+ winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer");
+ $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin 'https://example.com/composer/composer' && git remote set-url composer 'https://example.com/composer/composer'");
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($expectedGitCommand))
@@ -170,6 +170,9 @@ class GitDownloaderTest extends TestCase
$this->setupConfig($config);
$cachePath = $config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', 'https://example.com/composer/composer').'/';
+ $filesystem = new \Composer\Util\Filesystem;
+ $filesystem->removeDirectory($cachePath);
+
$expectedGitCommand = $this->winCompat(sprintf("git clone --mirror 'https://example.com/composer/composer' '%s'", $cachePath));
$processExecutor->expects($this->at(1))
->method('execute')
@@ -179,24 +182,36 @@ class GitDownloaderTest extends TestCase
return 0;
}));
+ $processExecutor->expects($this->at(2))
+ ->method('execute')
+ ->with($this->equalTo('git rev-parse --git-dir'), $this->anything(), $this->equalTo($this->winCompat($cachePath)))
+ ->will($this->returnCallback(function ($command, &$output = null) {
+ $output = '.';
+
+ return 0;
+ }));
+ $processExecutor->expects($this->at(3))
+ ->method('execute')
+ ->with($this->equalTo($this->winCompat('git rev-parse --quiet --verify \'1234567890123456789012345678901234567890^{commit}\'')), $this->equalTo(null), $this->equalTo($this->winCompat($cachePath)))
+ ->will($this->returnValue(0));
$expectedGitCommand = $this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin 'https://example.com/composer/composer' && git remote add composer 'https://example.com/composer/composer'", $cachePath));
- $processExecutor->expects($this->at(2))
+ $processExecutor->expects($this->at(4))
->method('execute')
->with($this->equalTo($expectedGitCommand))
->will($this->returnValue(0));
- $processExecutor->expects($this->at(3))
+ $processExecutor->expects($this->at(5))
->method('execute')
->with($this->equalTo($this->winCompat("git branch -r")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0));
- $processExecutor->expects($this->at(4))
+ $processExecutor->expects($this->at(6))
->method('execute')
->with($this->equalTo($this->winCompat("git checkout 'master' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0));
- $processExecutor->expects($this->at(5))
+ $processExecutor->expects($this->at(7))
->method('execute')
->with($this->equalTo($this->winCompat("git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0));
@@ -235,7 +250,7 @@ class GitDownloaderTest extends TestCase
return 0;
}));
- $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://github.com/mirrors/composer' && git fetch composer");
+ $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://github.com/mirrors/composer' && git fetch composer && git remote set-url origin 'https://github.com/mirrors/composer' && git remote set-url composer 'https://github.com/mirrors/composer'");
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($expectedGitCommand))
@@ -246,7 +261,7 @@ class GitDownloaderTest extends TestCase
->with()
->will($this->returnValue('Error1'));
- $expectedGitCommand = $this->winCompat("git clone --no-checkout 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'git@github.com:mirrors/composer' && git fetch composer");
+ $expectedGitCommand = $this->winCompat("git clone --no-checkout 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'git@github.com:mirrors/composer' && git fetch composer && git remote set-url origin 'git@github.com:mirrors/composer' && git remote set-url composer 'git@github.com:mirrors/composer'");
$processExecutor->expects($this->at(3))
->method('execute')
->with($this->equalTo($expectedGitCommand))
@@ -322,7 +337,7 @@ class GitDownloaderTest extends TestCase
return 0;
}));
- $expectedGitCommand = $this->winCompat("git clone --no-checkout '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer '{$url}' && git fetch composer");
+ $expectedGitCommand = $this->winCompat("git clone --no-checkout '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer '{$url}' && git fetch composer && git remote set-url origin '{$url}' && git remote set-url composer '{$url}'");
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($expectedGitCommand))
@@ -350,7 +365,7 @@ class GitDownloaderTest extends TestCase
public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
{
- $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer");
+ $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin 'https://example.com/composer/composer' && git remote set-url composer 'https://example.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
->method('getSourceReference')
@@ -408,7 +423,7 @@ class GitDownloaderTest extends TestCase
public function testUpdate()
{
- $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+ $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@@ -440,7 +455,7 @@ class GitDownloaderTest extends TestCase
public function testUpdateWithNewRepoUrl()
{
- $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+ $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@@ -519,8 +534,8 @@ composer https://github.com/old/url (push)
*/
public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
{
- $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
- $expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+ $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
+ $expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'git@github.com:composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@@ -563,8 +578,8 @@ composer https://github.com/old/url (push)
public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
{
- $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
- $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+ $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer '/'");
+ $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
diff --git a/tests/Composer/Test/Fixtures/installer/install-without-lock.test b/tests/Composer/Test/Fixtures/installer/install-without-lock.test
new file mode 100644
index 000000000..c5d73dcbb
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/install-without-lock.test
@@ -0,0 +1,25 @@
+--TEST--
+Installs from composer.json without writing a lock file
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "a/a", "version": "1.0.0" }
+ ]
+ }
+ ],
+ "require": {
+ "a/a": "1.0.0"
+ },
+ "config": {
+ "lock": "false"
+ }
+}
+--RUN--
+install
+--EXPECT--
+Installing a/a (1.0.0)
+--EXPECT-LOCK--
+false
diff --git a/tests/Composer/Test/Fixtures/installer/update-without-lock.test b/tests/Composer/Test/Fixtures/installer/update-without-lock.test
new file mode 100644
index 000000000..0fd1562c3
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/update-without-lock.test
@@ -0,0 +1,25 @@
+--TEST--
+Updates when no lock file is present without writing a lock file
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "a/a", "version": "1.0.0" }
+ ]
+ }
+ ],
+ "require": {
+ "a/a": "1.0.0"
+ },
+ "config": {
+ "lock": false
+ }
+}
+--RUN--
+update
+--EXPECT--
+Installing a/a (1.0.0)
+--EXPECT-LOCK--
+false
diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php
index 758121979..bb0643cf1 100644
--- a/tests/Composer/Test/InstallerTest.php
+++ b/tests/Composer/Test/InstallerTest.php
@@ -243,6 +243,9 @@ class InstallerTest extends TestCase
// so store value temporarily in reference for later assetion
$actualLock = $hash;
}));
+ } elseif ($expectLock === false) {
+ $lockJsonMock->expects($this->never())
+ ->method('write');
}
$contents = json_encode($composerConfig);
@@ -334,15 +337,15 @@ class InstallerTest extends TestCase
continue;
}
- $testData = $this->readTestFile($file, $fixturesDir);
-
- $installed = array();
- $installedDev = array();
- $lock = array();
- $expectLock = array();
- $expectResult = 0;
-
try {
+ $testData = $this->readTestFile($file, $fixturesDir);
+
+ $installed = array();
+ $installedDev = array();
+ $lock = array();
+ $expectLock = array();
+ $expectResult = 0;
+
$message = $testData['TEST'];
$condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
$composer = JsonFile::parseJson($testData['COMPOSER']);
@@ -373,7 +376,11 @@ class InstallerTest extends TestCase
}
$run = $testData['RUN'];
if (!empty($testData['EXPECT-LOCK'])) {
- $expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
+ if ($testData['EXPECT-LOCK'] === 'false') {
+ $expectLock = false;
+ } else {
+ $expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
+ }
}
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
$expect = $testData['EXPECT'];
diff --git a/tests/Composer/Test/Repository/PathRepositoryTest.php b/tests/Composer/Test/Repository/PathRepositoryTest.php
index abe6063f4..4f7b41809 100644
--- a/tests/Composer/Test/Repository/PathRepositoryTest.php
+++ b/tests/Composer/Test/Repository/PathRepositoryTest.php
@@ -19,6 +19,22 @@ use Composer\Package\Version\VersionParser;
class PathRepositoryTest extends TestCase
{
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadPackageFromFileSystemWithIncorrectPath()
+ {
+ $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
+ ->getMock();
+
+ $config = new \Composer\Config();
+
+ $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'missing'));
+ $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
+ $repository->getPackages();
+ }
+
public function testLoadPackageFromFileSystemWithVersion()
{
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php
index 9f684dbfb..f3fb30278 100644
--- a/tests/Composer/Test/Util/FilesystemTest.php
+++ b/tests/Composer/Test/Util/FilesystemTest.php
@@ -300,16 +300,16 @@ class FilesystemTest extends TestCase
// Create and detect junction
$fs->junction($target, $junction);
- $this->assertTrue($fs->isJunction($junction));
- $this->assertFalse($fs->isJunction($target));
- $this->assertTrue($fs->isJunction($target . '/../../junction'));
- $this->assertFalse($fs->isJunction($junction . '/../real'));
- $this->assertTrue($fs->isJunction($junction . '/../junction'));
+ $this->assertTrue($fs->isJunction($junction), $junction . ': is a junction');
+ $this->assertFalse($fs->isJunction($target), $target . ': is not a junction');
+ $this->assertTrue($fs->isJunction($target . '/../../junction'), $target . '/../../junction: is a junction');
+ $this->assertFalse($fs->isJunction($junction . '/../real'), $junction . '/../real: is not a junction');
+ $this->assertTrue($fs->isJunction($junction . '/../junction'), $junction . '/../junction: is a junction');
// Remove junction
- $this->assertTrue(is_dir($junction));
- $this->assertTrue($fs->removeJunction($junction));
- $this->assertFalse(is_dir($junction));
+ $this->assertTrue(is_dir($junction), $junction . ' is a directory');
+ $this->assertTrue($fs->removeJunction($junction), $junction . ' has been removed');
+ $this->assertFalse(is_dir($junction), $junction . ' is not a directory');
}
public function testCopy()