Browse Source

Merge branch 'master' into 2.0

* master: (48 commits)
  SVN: hide passwords for debug output
  Free $solver asap
  fixes #8179
  [minor] Fixed a typo in the CHANGELOG.md.
  Update deps
  Update changelog
  Revert "Allow overriding self-update target file with envvar COMPOSER_SELF_UPDATE_TARGET" Revert "Add docs for COMPOSER_SELF_UPDATE_TARGET, refs #8151"
  Add docs for COMPOSER_SELF_UPDATE_TARGET, refs #8151
  Fix display of HHVM warning appearing when HHVM is not in use, fixes #8138
  Read classmap-authoritative and apcu-autoloader from project config when installing via create-project, fixes #8155
  Use possessive quantifiers
  Update xdebug-handler to 1.3.3
  fixes #8159
  Allow overriding self-update target file with envvar COMPOSER_SELF_UPDATE_TARGET
  flag should come before script name
  use full command name, not abbreviated/alias
  modify text
  Document the alternatives to disable the default script timeout
  Anchor pattern
  Fix URL resolution for Composer repositories
  ...
Nils Adermann 6 years ago
parent
commit
d2fa1e1319
36 changed files with 776 additions and 202 deletions
  1. 5 0
      .gitattributes
  2. 16 0
      CHANGELOG.md
  3. 30 31
      composer.lock
  4. 4 0
      doc/03-cli.md
  5. 14 0
      doc/06-config.md
  6. 5 0
      doc/articles/plugins.md
  7. 51 1
      doc/articles/scripts.md
  8. 1 1
      doc/articles/versions.md
  9. 18 66
      src/Composer/Autoload/AutoloadGenerator.php
  10. 1 1
      src/Composer/Autoload/ClassMapGenerator.php
  11. 3 1
      src/Composer/Command/CreateProjectCommand.php
  12. 16 4
      src/Composer/Command/RequireCommand.php
  13. 17 0
      src/Composer/Config.php
  14. 37 32
      src/Composer/DependencyResolver/Problem.php
  15. 3 1
      src/Composer/Installer.php
  16. 1 1
      src/Composer/Json/JsonManipulator.php
  17. 10 0
      src/Composer/Package/AliasPackage.php
  18. 7 1
      src/Composer/Package/Loader/ValidatingArrayLoader.php
  19. 28 0
      src/Composer/Package/PackageInterface.php
  20. 6 3
      src/Composer/Plugin/PluginManager.php
  21. 5 1
      src/Composer/Repository/ComposerRepository.php
  22. 8 2
      src/Composer/Repository/PlatformRepository.php
  23. 7 0
      src/Composer/Repository/Vcs/BitbucketDriver.php
  24. 3 2
      src/Composer/Repository/Vcs/HgDriver.php
  25. 32 29
      src/Composer/Repository/VcsRepository.php
  26. 92 0
      src/Composer/Util/PackageSorter.php
  27. 1 0
      src/Composer/Util/ProcessExecutor.php
  28. 41 0
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  29. 13 0
      tests/Composer/Test/Autoload/Fixtures/autoload_phar.php
  30. 13 0
      tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php
  31. 87 0
      tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php
  32. 61 22
      tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php
  33. 20 0
      tests/Composer/Test/Json/JsonManipulatorTest.php
  34. 36 0
      tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php
  35. 67 0
      tests/Composer/Test/Repository/ComposerRepositoryTest.php
  36. 17 3
      tests/Composer/Test/Util/ProcessExecutorTest.php

+ 5 - 0
.gitattributes

@@ -10,3 +10,8 @@
 
 # Exclude non-essential files from dist
 /tests export-ignore
+.github export-ignore
+.php_cs export-ignore
+.travis.yml export-ignore
+appveyor.yml export-ignore
+phpunit.xml.dist export-ignore

+ 16 - 0
CHANGELOG.md

@@ -1,3 +1,17 @@
+### [1.8.6] 2019-06-11
+
+  * Fixed handling of backslash-escapes handling in composer.json when using the require command
+  * Fixed create-project not following classmap-authoritative and apcu-autoloader config values
+  * Fixed HHVM version warning showing up in some cases when it was not in use
+
+### [1.8.5] 2019-04-09
+
+  * HHVM 4.0 is no longer compatible with Composer. Please use PHP instead going forward.
+  * Added forward compatibility with upcoming 2.0 changes
+  * Fixed support for PHP 7.3-style heredoc/nowdoc syntax changes in autoload generation
+  * Fixed require command usage when combined with --ignore-platform-reqs
+  * Fixed and cleaned up various Windows junctions handling issues
+
 ### [1.8.4] 2019-02-11
 
   * Fixed long standing solver bug leading to odd solving issues in edge cases, see #7946
@@ -737,6 +751,8 @@
 
   * Initial release
 
+[1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6
+[1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5
 [1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4
 [1.8.3]: https://github.com/composer/composer/compare/1.8.2...1.8.3
 [1.8.2]: https://github.com/composer/composer/compare/1.8.1...1.8.2

+ 30 - 31
composer.lock

@@ -126,24 +126,23 @@
         },
         {
             "name": "composer/spdx-licenses",
-            "version": "1.5.0",
+            "version": "1.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/spdx-licenses.git",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2"
+                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2",
+                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
+                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.2 || ^7.0"
+                "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
-                "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7"
             },
             "type": "library",
             "extra": {
@@ -183,20 +182,20 @@
                 "spdx",
                 "validator"
             ],
-            "time": "2018-11-01T09:45:54+00:00"
+            "time": "2019-03-26T10:23:26+00:00"
         },
         {
             "name": "composer/xdebug-handler",
-            "version": "1.3.2",
+            "version": "1.3.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "d17708133b6c276d6e42ef887a877866b909d892"
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892",
-                "reference": "d17708133b6c276d6e42ef887a877866b909d892",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f",
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f",
                 "shasum": ""
             },
             "require": {
@@ -227,7 +226,7 @@
                 "Xdebug",
                 "performance"
             ],
-            "time": "2019-01-28T20:25:53+00:00"
+            "time": "2019-05-27T17:52:04+00:00"
         },
         {
             "name": "justinrainbow/json-schema",
@@ -481,7 +480,7 @@
         },
         {
             "name": "symfony/console",
-            "version": "v2.8.49",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
@@ -542,7 +541,7 @@
         },
         {
             "name": "symfony/debug",
-            "version": "v2.8.49",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
@@ -599,7 +598,7 @@
         },
         {
             "name": "symfony/filesystem",
-            "version": "v2.8.49",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
@@ -649,7 +648,7 @@
         },
         {
             "name": "symfony/finder",
-            "version": "v2.8.49",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
@@ -698,16 +697,16 @@
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.10.0",
+            "version": "v1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+                "reference": "82ebae02209c21113908c229e9883c419720738a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
+                "reference": "82ebae02209c21113908c229e9883c419720738a",
                 "shasum": ""
             },
             "require": {
@@ -719,7 +718,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.11-dev"
                 }
             },
             "autoload": {
@@ -752,20 +751,20 @@
                 "polyfill",
                 "portable"
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2019-02-06T07:57:58+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.10.0",
+            "version": "v1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
+                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
                 "shasum": ""
             },
             "require": {
@@ -777,7 +776,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.11-dev"
                 }
             },
             "autoload": {
@@ -811,11 +810,11 @@
                 "portable",
                 "shim"
             ],
-            "time": "2018-09-21T13:07:52+00:00"
+            "time": "2019-02-06T07:57:58+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.49",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
@@ -1780,7 +1779,7 @@
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.8.49",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",

+ 4 - 0
doc/03-cli.md

@@ -920,6 +920,10 @@ If you use a proxy but it does not support the request_fulluri flag for HTTPS
 requests, then you should set this env var to `false` or `0` to prevent Composer
 from setting the request_fulluri option.
 
+### COMPOSER_SELF_UPDATE_TARGET
+
+If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem.
+
 ### no_proxy or NO_PROXY
 
 If you are behind a proxy and would like to disable it for certain domains, you

+ 14 - 0
doc/06-config.md

@@ -9,6 +9,20 @@ Defaults to `300`. The duration processes like git clones can run before
 Composer assumes they died out. You may need to make this higher if you have a
 slow connection or huge vendors.
 
+To disable the process timeout on a custom command under `scripts`, a static
+helper is available:
+
+```json
+{
+    "scripts": {
+        "test": [
+            "Composer\\Config::disableProcessTimeout",
+            "phpunit"
+        ]
+    }
+}
+```
+
 ## use-include-path
 
 Defaults to `false`. If `true`, the Composer autoloader will also look for classes

+ 5 - 0
doc/articles/plugins.md

@@ -261,6 +261,11 @@ Now the `custom-plugin-command` is available alongside Composer commands.
 
 > _Composer commands are based on the [Symfony Console Component][10]._
 
+## Running plugins manually
+
+Plugins for an event can be run manually by the `run-script` command. This works the same way as 
+[running scripts manually](scripts.md#running-scripts-manually).
+
 ## Using Plugins
 
 Plugin packages are automatically loaded as soon as they are installed and will

+ 51 - 1
doc/articles/scripts.md

@@ -189,7 +189,7 @@ composer run-script [--dev] [--no-dev] script
 ```
 
 For example `composer run-script post-install-cmd` will run any
-**post-install-cmd** scripts that have been defined.
+**post-install-cmd** scripts and [plugins](plugins.md) that have been defined.
 
 You can also give additional arguments to the script handler by appending `--`
 followed by the handler arguments. e.g.
@@ -221,6 +221,56 @@ to the `phpunit` script.
 > are easily accessible. In this example no matter if the `phpunit` binary is
 > actually in `vendor/bin/phpunit` or `bin/phpunit` it will be found and executed.
 
+Although Composer is not intended to manage long-running processes and other
+such aspects of PHP projects, it can sometimes be handy to disable the process
+timeout on custom commands. This timeout defaults to 300 seconds and can be
+overridden in a variety of ways depending on the desired effect:
+
+- disable it for all commands using the config key `process-timeout`,
+- disable it for the current or future invocations of composer using the
+  environment variable `COMPOSER_PROCESS_TIMEOUT`,
+- for a specific invocation using the `--timeout` flag of the `run-script` command,
+- using a static helper for specific scripts.
+
+To disable the timeout for specific scripts with the static helper directly in
+composer.json:
+
+```json
+{
+    "scripts": {
+        "test": [
+            "Composer\\Config::disableProcessTimeout",
+            "phpunit"
+        ]
+    }
+}
+```
+
+To disable the timeout for every script on a given project, you can use the
+composer.json configuration:
+
+```json
+{
+    "config": {
+        "process-timeout": 0
+    }
+}
+```
+
+It's also possible to set the global environment variable to disable the timeout
+of all following scripts in the current terminal environment:
+
+```
+export COMPOSER_PROCESS_TIMEOUT=0
+```
+
+To disable the timeout of a single script call, you must use the `run-script` composer
+command and specify the `--timeout` parameter:
+
+```
+composer run-script --timeout=0 test
+```
+
 ## Referencing scripts
 
 To enable script re-use and avoid duplicates, you can call a script from another

+ 1 - 1
doc/articles/versions.md

@@ -32,7 +32,7 @@ repository:*
 v1
 v2
 my-feature
-nother-feature
+another-feature
 
 ~/my-library$ git tag
 v1.0

+ 18 - 66
src/Composer/Autoload/AutoloadGenerator.php

@@ -21,6 +21,7 @@ use Composer\Package\PackageInterface;
 use Composer\Repository\InstalledRepositoryInterface;
 use Composer\Util\Filesystem;
 use Composer\Script\ScriptEvents;
+use Composer\Util\PackageSorter;
 
 /**
  * @author Igor Wiedler <igor@wiedler.ch>
@@ -545,7 +546,7 @@ EOF;
             }
         }
 
-        if (preg_match('/\.phar.+$/', $path)) {
+        if (strpos($path, '.phar') !== false) {
             $baseDir = "'phar://' . " . $baseDir;
         }
 
@@ -769,10 +770,14 @@ HEADER;
         $filesystem = new Filesystem();
 
         $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
+        $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
         $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
+        $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
 
         $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1);
+        $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1);
         $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1);
+        $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1);
 
         $initializer = '';
         $prefix = "\0Composer\Autoload\ClassLoader\0";
@@ -795,9 +800,15 @@ HEADER;
                 // See https://bugs.php.net/68057
                 $staticPhpVersion = 70000;
             }
-            $value = var_export($value, true);
-            $value = str_replace($absoluteVendorPathCode, $vendorPathCode, $value);
-            $value = str_replace($absoluteAppBaseDirCode, $appBaseDirCode, $value);
+            $value = strtr(
+                var_export($value, true),
+                array(
+                    $absoluteVendorPathCode => $vendorPathCode,
+                    $absoluteVendorPharPathCode => $vendorPharPathCode,
+                    $absoluteAppBaseDirCode => $appBaseDirCode,
+                    $absoluteAppBaseDirPharCode => $appBaseDirPharCode,
+                )
+            );
             $value = ltrim(preg_replace('/^ */m', '    $0$0', $value));
 
             $file .= sprintf("    public static $%s = %s;\n\n", $prop, $value);
@@ -963,80 +974,21 @@ INITIALIZER;
     {
         $packages = array();
         $paths = array();
-        $usageList = array();
 
         foreach ($packageMap as $item) {
             list($package, $path) = $item;
             $name = $package->getName();
             $packages[$name] = $package;
             $paths[$name] = $path;
-
-            foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) {
-                $target = $link->getTarget();
-                $usageList[$target][] = $name;
-            }
         }
 
-        $computing = array();
-        $computed = array();
-        $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) {
-            // reusing computed importance
-            if (isset($computed[$name])) {
-                return $computed[$name];
-            }
-
-            // canceling circular dependency
-            if (isset($computing[$name])) {
-                return 0;
-            }
+        $sortedPackages = PackageSorter::sortPackages($packages);
 
-            $computing[$name] = true;
-            $weight = 0;
-
-            if (isset($usageList[$name])) {
-                foreach ($usageList[$name] as $user) {
-                    $weight -= 1 - $computeImportance($user);
-                }
-            }
-
-            unset($computing[$name]);
-            $computed[$name] = $weight;
-
-            return $weight;
-        };
-
-        $weightList = array();
-
-        foreach ($packages as $name => $package) {
-            $weight = $computeImportance($name);
-            $weightList[$name] = $weight;
-        }
-
-        $stable_sort = function (&$array) {
-            static $transform, $restore;
-
-            $i = 0;
-
-            if (!$transform) {
-                $transform = function (&$v, $k) use (&$i) {
-                    $v = array($v, ++$i, $k, $v);
-                };
-
-                $restore = function (&$v, $k) {
-                    $v = $v[3];
-                };
-            }
-
-            array_walk($array, $transform);
-            asort($array);
-            array_walk($array, $restore);
-        };
-
-        $stable_sort($weightList);
 
         $sortedPackageMap = array();
 
-        foreach (array_keys($weightList) as $name) {
+        foreach ($sortedPackages as $package) {
+            $name = $package->getName();
             $sortedPackageMap[] = array($packages[$name], $paths[$name]);
         }
 

+ 1 - 1
src/Composer/Autoload/ClassMapGenerator.php

@@ -162,7 +162,7 @@ class ClassMapGenerator
         }
 
         // strip heredocs/nowdocs
-        $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
+        $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents);
         // strip strings
         $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
         // strip leading non-php code if needed

+ 3 - 1
src/Composer/Command/CreateProjectCommand.php

@@ -184,7 +184,9 @@ EOT
                 ->setRunScripts(!$noScripts)
                 ->setIgnorePlatformRequirements($ignorePlatformReqs)
                 ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
-                ->setOptimizeAutoloader($config->get('optimize-autoloader'));
+                ->setOptimizeAutoloader($config->get('optimize-autoloader'))
+                ->setClassMapAuthoritative($config->get('classmap-authoritative'))
+                ->setApcuAutoloader($config->get('apcu-autoloader'));
 
             if ($disablePlugins) {
                 $installer->disablePlugins();

+ 16 - 4
src/Composer/Command/RequireCommand.php

@@ -25,6 +25,7 @@ use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
+use Composer\IO\IOInterface;
 
 /**
  * @author Jérémy Romey <jeremy@free-agent.fr>
@@ -160,15 +161,26 @@ EOT
         if ($input->getOption('no-update')) {
             return 0;
         }
-        $updateDevMode = !$input->getOption('update-no-dev');
-        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
-        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
-        $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
 
+        try {
+            return $this->doUpdate($input, $output, $io, $requirements);
+        } catch (\Exception $e) {
+            $this->revertComposerFile(false);
+            throw $e;
+        }
+    }
+
+    private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements)
+    {
         // Update packages
         $this->resetComposer();
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
 
+        $updateDevMode = !$input->getOption('update-no-dev');
+        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
+        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
+        $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
+
         $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
         $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
 

+ 17 - 0
src/Composer/Config.php

@@ -16,6 +16,7 @@ use Composer\Config\ConfigSourceInterface;
 use Composer\Downloader\TransportException;
 use Composer\IO\IOInterface;
 use Composer\Util\Platform;
+use Composer\Util\ProcessExecutor;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -459,4 +460,20 @@ class Config
             }
         }
     }
+
+    /**
+     * Used by long-running custom scripts in composer.json
+     *
+     * "scripts": {
+     *   "watch": [
+     *     "Composer\\Config::disableProcessTimeout",
+     *     "vendor/bin/long-running-script --watch"
+     *   ]
+     * }
+     */
+    public static function disableProcessTimeout()
+    {
+        // Override global timeout set earlier by environment or config
+        ProcessExecutor::setTimeout(0);
+    }
 }

+ 37 - 32
src/Composer/DependencyResolver/Problem.php

@@ -81,8 +81,11 @@ class Problem
 
             $job = $reason['job'];
 
-            if (isset($job['constraint'])) {
-                $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
+            $packageName = $job['packageName'];
+            $constraint = $job['constraint'];
+
+            if (isset($constraint)) {
+                $packages = $this->pool->whatProvides($packageName, $constraint);
             } else {
                 $packages = array();
             }
@@ -90,9 +93,9 @@ class Problem
             if ($job && $job['cmd'] === 'install' && empty($packages)) {
 
                 // handle php/hhvm
-                if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') {
+                if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
                     $version = phpversion();
-                    $available = $this->pool->whatProvides($job['packageName']);
+                    $available = $this->pool->whatProvides($packageName);
 
                     if (count($available)) {
                         $firstAvailable = reset($available);
@@ -103,13 +106,13 @@ class Problem
                         }
                     }
 
-                    $msg = "\n    - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but ';
+                    $msg = "\n    - This package requires ".$packageName.$this->constraintToText($constraint).' but ';
 
-                    if (defined('HHVM_VERSION') || count($available)) {
+                    if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
                         return $msg . 'your HHVM version does not satisfy that requirement.';
                     }
 
-                    if ($job['packageName'] === 'hhvm') {
+                    if ($packageName === 'hhvm') {
                         return $msg . 'you are running this with PHP and not HHVM.';
                     }
 
@@ -117,43 +120,43 @@ class Problem
                 }
 
                 // handle php extensions
-                if (0 === stripos($job['packageName'], 'ext-')) {
-                    if (false !== strpos($job['packageName'], ' ')) {
-                        return "\n    - The requested PHP extension ".$job['packageName'].' should be required as '.str_replace(' ', '-', $job['packageName']).'.';
+                if (0 === stripos($packageName, 'ext-')) {
+                    if (false !== strpos($packageName, ' ')) {
+                        return "\n    - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.';
                     }
 
-                    $ext = substr($job['packageName'], 4);
+                    $ext = substr($packageName, 4);
                     $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
 
-                    return "\n    - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
+                    return "\n    - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
                 }
 
                 // handle linked libs
-                if (0 === stripos($job['packageName'], 'lib-')) {
-                    if (strtolower($job['packageName']) === 'lib-icu') {
+                if (0 === stripos($packageName, 'lib-')) {
+                    if (strtolower($packageName) === 'lib-icu') {
                         $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.';
 
-                        return "\n    - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error;
+                        return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error;
                     }
 
-                    return "\n    - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.';
+                    return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.';
                 }
 
-                if (!preg_match('{^[A-Za-z0-9_./-]+$}', $job['packageName'])) {
-                    $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $job['packageName']);
+                if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
+                    $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
 
-                    return "\n    - The requested package ".$job['packageName'].' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.';
+                    return "\n    - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.';
                 }
 
-                if ($providers = $this->pool->whatProvides($job['packageName'], $job['constraint'], true, true)) {
-                    return "\n    - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
+                if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) {
+                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
                 }
 
-                if ($providers = $this->pool->whatProvides($job['packageName'], null, true, true)) {
-                    return "\n    - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
+                if ($providers = $this->pool->whatProvides($packageName, null, true, true)) {
+                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
                 }
 
-                return "\n    - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.';
+                return "\n    - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.';
             }
         }
 
@@ -202,27 +205,29 @@ class Problem
      */
     protected function jobToText($job)
     {
+        $packageName = $job['packageName'];
+        $constraint = $job['constraint'];
         switch ($job['cmd']) {
             case 'install':
-                $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
+                $packages = $this->pool->whatProvides($packageName, $constraint);
                 if (!$packages) {
-                    return 'No package found to satisfy install request for '.$job['packageName'].$this->constraintToText($job['constraint']);
+                    return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint);
                 }
 
-                return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($packages).'.';
+                return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.';
             case 'update':
-                return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.';
+                return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
             case 'remove':
-                return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).'';
+                return 'Removal request for '.$packageName.$this->constraintToText($constraint).'';
         }
 
-        if (isset($job['constraint'])) {
-            $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
+        if (isset($constraint)) {
+            $packages = $this->pool->whatProvides($packageName, $constraint);
         } else {
             $packages = array();
         }
 
-        return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])';
+        return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])';
     }
 
     protected function getPackageList($packages)

+ 3 - 1
src/Composer/Installer.php

@@ -477,6 +477,8 @@ class Installer
         $solver = new Solver($policy, $pool, $installedRepo, $this->io);
         try {
             $operations = $solver->solve($request, $this->ignorePlatformReqs);
+            $ruleSetSize = $solver->getRuleSetSize();
+            $solver = null;
         } catch (SolverProblemsException $e) {
             $this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
             $this->io->writeError($e->getMessage());
@@ -493,7 +495,7 @@ class Installer
         $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations);
 
         $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
-        $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE);
+        $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);
 
         // execute operations
         if (!$operations) {

+ 1 - 1
src/Composer/Json/JsonManipulator.php

@@ -22,7 +22,7 @@ class JsonManipulator
     private static $DEFINES = '(?(DEFINE)
        (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
        (?<boolean>   true | false | null )
-       (?<string>    " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
+       (?<string>    " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " )
        (?<array>     \[  (?:  (?&json) \s* (?: , (?&json) \s* )*  )?  \s* \] )
        (?<pair>      \s* (?&string) \s* : (?&json) \s* )
        (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \s* \} )

+ 10 - 0
src/Composer/Package/AliasPackage.php

@@ -401,4 +401,14 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
     {
         return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')';
     }
+
+    public function setDistUrl($url)
+    {
+        return $this->aliasOf->setDistUrl($url);
+    }
+
+    public function setDistType($type)
+    {
+        return $this->aliasOf->setDistType($type);
+    }
 }

+ 7 - 1
src/Composer/Package/Loader/ValidatingArrayLoader.php

@@ -49,6 +49,10 @@ class ValidatingArrayLoader implements LoaderInterface
         $this->warnings = array();
         $this->config = $config;
 
+        if ($err = self::hasPackageNamingError($config['name'])) {
+            $this->warnings[] = 'Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.';
+        }
+
         if ($this->strictName) {
             $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true);
         } else {
@@ -195,7 +199,9 @@ class ValidatingArrayLoader implements LoaderInterface
         foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
             if ($this->validateArray($linkType) && isset($this->config[$linkType])) {
                 foreach ($this->config[$linkType] as $package => $constraint) {
-                    if (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) {
+                    if ($err = self::hasPackageNamingError($package, true)) {
+                        $this->warnings[] = 'Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.';
+                    } elseif (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) {
                         $this->warnings[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]';
                     }
                     if (!is_string($constraint)) {

+ 28 - 0
src/Composer/Package/PackageInterface.php

@@ -358,4 +358,32 @@ interface PackageInterface
      * @return array
      */
     public function getTransportOptions();
+
+    /**
+     * @param string $reference
+     *
+     * @return void
+     */
+    public function setSourceReference($reference);
+
+    /**
+     * @param string $url
+     *
+     * @return void
+     */
+    public function setDistUrl($url);
+
+    /**
+     * @param string $type
+     *
+     * @return void
+     */
+    public function setDistType($type);
+
+    /**
+     * @param string $reference
+     *
+     * @return void
+     */
+    public function setDistReference($reference);
 }

+ 6 - 3
src/Composer/Plugin/PluginManager.php

@@ -15,15 +15,16 @@ namespace Composer\Plugin;
 use Composer\Composer;
 use Composer\EventDispatcher\EventSubscriberInterface;
 use Composer\IO\IOInterface;
+use Composer\Package\CompletePackage;
 use Composer\Package\Package;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\RepositoryInterface;
-use Composer\Package\AliasPackage;
 use Composer\Package\PackageInterface;
 use Composer\Package\Link;
 use Composer\Repository\RepositorySet;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Plugin\Capability\Capability;
+use Composer\Util\PackageSorter;
 
 /**
  * Plugin manager
@@ -253,8 +254,10 @@ class PluginManager
      */
     private function loadRepository(RepositoryInterface $repo)
     {
-        foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */
-            if ($package instanceof AliasPackage) {
+        $packages = $repo->getPackages();
+        $sortedPackages = array_reverse(PackageSorter::sortPackages($packages));
+        foreach ($sortedPackages as $package) {
+            if (!($package instanceof CompletePackage)) {
                 continue;
             }
             if ('composer-plugin' === $package->getType()) {

+ 5 - 1
src/Composer/Repository/ComposerRepository.php

@@ -803,7 +803,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
     private function canonicalizeUrl($url)
     {
         if ('/' === $url[0]) {
-            return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url);
+            if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) {
+                return $matches[0] . $url;
+            }
+
+            return $this->url;
         }
 
         return $url;

+ 8 - 2
src/Composer/Repository/PlatformRepository.php

@@ -166,8 +166,14 @@ class PlatformRepository extends ArrayRepository
                 case 'imagick':
                     $imagick = new \Imagick();
                     $imageMagickVersion = $imagick->getVersion();
-                    preg_match('/^ImageMagick ([\d.]+)-(\d+)/', $imageMagickVersion['versionString'], $matches);
-                    $prettyVersion = "{$matches[1]}.{$matches[2]}";
+                    // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org
+                    // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org
+                    preg_match('/^ImageMagick ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches);
+                    if (isset($matches[2])) {
+                        $prettyVersion = "{$matches[1]}.{$matches[2]}";
+                    } else {
+                        $prettyVersion = $matches[1];
+                    }
                     break;
 
                 case 'libxml':

+ 7 - 0
src/Composer/Repository/Vcs/BitbucketDriver.php

@@ -219,6 +219,13 @@ abstract class BitbucketDriver extends VcsDriver
             return $this->fallbackDriver->getChangeDate($identifier);
         }
 
+        if (strpos($identifier, '/') !== false) {
+            $branches = $this->getBranches();
+            if (isset($branches[$identifier])) {
+                $identifier = $branches[$identifier];
+            }
+        }
+
         $resource = sprintf(
             'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
             $this->owner,

+ 3 - 2
src/Composer/Repository/Vcs/HgDriver.php

@@ -66,8 +66,9 @@ class HgDriver extends VcsDriver
                 // clean up directory and do a fresh clone into it
                 $fs->removeDirectory($this->repoDir);
 
-                $command = function ($url) {
-                    return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($this->repoDir));
+                $repoDir = $this->repoDir;
+                $command = function ($url) use ($repoDir) {
+                    return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
                 };
 
                 $hgUtils->runCommand($command, $this->url, $this->repoDir);

+ 32 - 29
src/Composer/Repository/VcsRepository.php

@@ -32,7 +32,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 {
     protected $url;
     protected $packageName;
-    protected $verbose;
+    protected $isVerbose;
+    protected $isVeryVerbose;
     protected $io;
     protected $config;
     protected $versionParser;
@@ -68,7 +69,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         $this->url = $repoConfig['url'];
         $this->io = $io;
         $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
-        $this->verbose = $io->isVeryVerbose();
+        $this->isVerbose = $io->isVerbose();
+        $this->isVeryVerbose = $io->isVeryVerbose();
         $this->config = $config;
         $this->repoConfig = $repoConfig;
         $this->versionCache = $versionCache;
@@ -133,7 +135,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
     {
         parent::initialize();
 
-        $verbose = $this->verbose;
+        $isVerbose = $this->isVerbose;
+        $isVeryVerbose = $this->isVeryVerbose;
 
         $driver = $this->getDriver();
         if (!$driver) {
@@ -151,23 +154,23 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $this->packageName = !empty($data['name']) ? $data['name'] : null;
             }
         } catch (\Exception $e) {
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
             }
         }
 
         foreach ($driver->getTags() as $tag => $identifier) {
             $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError($msg);
-            } else {
+            } elseif ($isVerbose) {
                 $this->io->overwriteError($msg, false);
             }
 
             // strip the release- prefix from tags if present
             $tag = str_replace('release-', '', $tag);
 
-            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $verbose);
+            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose);
             if ($cachedPackage) {
                 $this->addPackage($cachedPackage);
 
@@ -179,7 +182,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
             }
 
             if (!$parsedTag = $this->validateTag($tag)) {
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name</warning>');
                 }
                 continue;
@@ -187,7 +190,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
             try {
                 if (!$data = $driver->getComposerInformation($identifier)) {
-                    if ($verbose) {
+                    if ($isVeryVerbose) {
                         $this->io->writeError('<warning>Skipped tag '.$tag.', no composer file</warning>');
                     }
                     $this->emptyReferences[] = $identifier;
@@ -209,7 +212,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
                 // broken package, version doesn't match tag
                 if ($data['version_normalized'] !== $parsedTag) {
-                    if ($verbose) {
+                    if ($isVeryVerbose) {
                         $this->io->writeError('<warning>Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json</warning>');
                     }
                     continue;
@@ -217,13 +220,13 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
                 $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName;
                 if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) {
-                    if ($verbose) {
+                    if ($isVeryVerbose) {
                         $this->io->writeError('<warning>Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally</warning>');
                     }
                     continue;
                 }
 
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                 }
 
@@ -232,35 +235,35 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 if ($e instanceof TransportException && $e->getCode() === 404) {
                     $this->emptyReferences[] = $identifier;
                 }
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).'</warning>');
                 }
                 continue;
             }
         }
 
-        if (!$verbose) {
+        if (!$isVeryVerbose) {
             $this->io->overwriteError('', false);
         }
 
         $branches = $driver->getBranches();
         foreach ($branches as $branch => $identifier) {
             $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError($msg);
-            } else {
+            } elseif ($isVerbose) {
                 $this->io->overwriteError($msg, false);
             }
 
             if ($branch === 'trunk' && isset($branches['master'])) {
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped branch '.$branch.', can not parse both master and trunk branches as they both resolve to 9999999-dev internally</warning>');
                 }
                 continue;
             }
 
             if (!$parsedBranch = $this->validateBranch($branch)) {
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped branch '.$branch.', invalid name</warning>');
                 }
                 continue;
@@ -274,7 +277,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
             }
 
-            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $verbose);
+            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose);
             if ($cachedPackage) {
                 $this->addPackage($cachedPackage);
 
@@ -287,7 +290,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
             try {
                 if (!$data = $driver->getComposerInformation($identifier)) {
-                    if ($verbose) {
+                    if ($isVeryVerbose) {
                         $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file</warning>');
                     }
                     $this->emptyReferences[] = $identifier;
@@ -298,7 +301,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $data['version'] = $version;
                 $data['version_normalized'] = $parsedBranch;
 
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')');
                 }
 
@@ -312,12 +315,12 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 if ($e->getCode() === 404) {
                     $this->emptyReferences[] = $identifier;
                 }
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found</warning>');
                 }
                 continue;
             } catch (\Exception $e) {
-                if (!$verbose) {
+                if (!$isVeryVerbose) {
                     $this->io->writeError('');
                 }
                 $this->branchErrorOccurred = true;
@@ -328,7 +331,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         }
         $driver->cleanup();
 
-        if (!$verbose) {
+        if (!$isVeryVerbose) {
             $this->io->overwriteError('', false);
         }
 
@@ -373,7 +376,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         return false;
     }
 
-    private function getCachedPackageVersion($version, $identifier, $verbose)
+    private function getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose)
     {
         if (!$this->versionCache) {
             return;
@@ -381,7 +384,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
         $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
         if ($cachedPackage === false) {
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError('<warning>Skipped '.$version.', no composer file (cached from ref '.$identifier.')</warning>');
             }
 
@@ -390,14 +393,14 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
         if ($cachedPackage) {
             $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError($msg);
-            } else {
+            } elseif ($isVerbose) {
                 $this->io->overwriteError($msg, false);
             }
 
             if ($existingPackage = $this->findPackage($cachedPackage['name'], $cachedPackage['version_normalized'])) {
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally</warning>');
                 }
                 $cachedPackage = null;

+ 92 - 0
src/Composer/Util/PackageSorter.php

@@ -0,0 +1,92 @@
+<?php
+
+
+namespace Composer\Util;
+
+use Composer\Package\Link;
+use Composer\Package\PackageInterface;
+
+class PackageSorter
+{
+    /**
+     * Sorts packages by dependency weight
+     *
+     * Packages of equal weight retain the original order
+     *
+     * @param  array $packages
+     * @return array
+     */
+    public static function sortPackages(array $packages) {
+        $usageList = array();
+
+        foreach ($packages as $package) { /** @var PackageInterface $package */
+            foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { /** @var Link $link */
+                $target = $link->getTarget();
+                $usageList[$target][] = $package->getName();
+            }
+        }
+        $computing = array();
+        $computed = array();
+        $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) {
+            // reusing computed importance
+            if (isset($computed[$name])) {
+                return $computed[$name];
+            }
+
+            // canceling circular dependency
+            if (isset($computing[$name])) {
+                return 0;
+            }
+
+            $computing[$name] = true;
+            $weight = 0;
+
+            if (isset($usageList[$name])) {
+                foreach ($usageList[$name] as $user) {
+                    $weight -= 1 - $computeImportance($user);
+                }
+            }
+
+            unset($computing[$name]);
+            $computed[$name] = $weight;
+
+            return $weight;
+        };
+
+        $weightList = array();
+
+        foreach ($packages as $name => $package) {
+            $weight = $computeImportance($name);
+            $weightList[$name] = $weight;
+        }
+
+        $stable_sort = function (&$array) {
+            static $transform, $restore;
+
+            $i = 0;
+
+            if (!$transform) {
+                $transform = function (&$v, $k) use (&$i) {
+                    $v = array($v, ++$i, $k, $v);
+                };
+
+                $restore = function (&$v) {
+                    $v = $v[3];
+                };
+            }
+
+            array_walk($array, $transform);
+            asort($array);
+            array_walk($array, $restore);
+        };
+
+        $stable_sort($weightList);
+
+        $sortedPackages = array();
+
+        foreach (array_keys($weightList) as $name) {
+            $sortedPackages[] = $packages[$name];
+        }
+        return $sortedPackages;
+    }
+}

+ 1 - 0
src/Composer/Util/ProcessExecutor.php

@@ -51,6 +51,7 @@ class ProcessExecutor
 
                 return '://'.$m['user'].':***@';
             }, $command);
+            $safeCommand = preg_replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand);
             $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand);
         }
 

+ 41 - 0
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -486,6 +486,47 @@ class AutoloadGeneratorTest extends TestCase
         $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty.");
     }
 
+    public function testPharAutoload()
+    {
+        $package = new Package('a', '1.0', '1.0');
+        $package->setRequires(array(
+            new Link('a', 'a/a'),
+        ));
+
+        $package->setAutoload(array(
+            'psr-0' => array(
+                'Foo' => 'foo.phar',
+                'Bar' => 'dir/bar.phar/src',
+            ),
+            'psr-4' => array(
+                'Baz\\' => 'baz.phar',
+                'Qux\\' => 'dir/qux.phar/src',
+            ),
+        ));
+
+        $vendorPackage = new Package('a/a', '1.0', '1.0');
+        $vendorPackage->setAutoload(array(
+            'psr-0' => array(
+                'Lorem' => 'lorem.phar',
+                'Ipsum' => 'dir/ipsum.phar/src',
+            ),
+            'psr-4' => array(
+                'Dolor\\' => 'dolor.phar',
+                'Sit\\' => 'dir/sit.phar/src',
+            ),
+        ));
+
+        $this->repository->expects($this->once())
+            ->method('getCanonicalPackages')
+            ->will($this->returnValue(array($vendorPackage)));
+
+        $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, 'Phar');
+
+        $this->assertAutoloadFiles('phar', $this->vendorDir . '/composer');
+        $this->assertAutoloadFiles('phar_psr4', $this->vendorDir . '/composer', 'psr4');
+        $this->assertAutoloadFiles('phar_static', $this->vendorDir . '/composer', 'static');
+    }
+
     public function testPSRToClassMapIgnoresNonExistingDir()
     {
         $package = new Package('a', '1.0', '1.0');

+ 13 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_phar.php

@@ -0,0 +1,13 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Lorem' => array('phar://' . $vendorDir . '/a/a/lorem.phar'),
+    'Ipsum' => array('phar://' . $vendorDir . '/a/a/dir/ipsum.phar/src'),
+    'Foo' => array('phar://' . $baseDir . '/foo.phar'),
+    'Bar' => array('phar://' . $baseDir . '/dir/bar.phar/src'),
+);

+ 13 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php

@@ -0,0 +1,13 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Sit\\' => array('phar://' . $vendorDir . '/a/a/dir/sit.phar/src'),
+    'Qux\\' => array('phar://' . $baseDir . '/dir/qux.phar/src'),
+    'Dolor\\' => array('phar://' . $vendorDir . '/a/a/dolor.phar'),
+    'Baz\\' => array('phar://' . $baseDir . '/baz.phar'),
+);

+ 87 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php

@@ -0,0 +1,87 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInitPhar
+{
+    public static $prefixLengthsPsr4 = array (
+        'S' => 
+        array (
+            'Sit\\' => 4,
+        ),
+        'Q' => 
+        array (
+            'Qux\\' => 4,
+        ),
+        'D' => 
+        array (
+            'Dolor\\' => 6,
+        ),
+        'B' => 
+        array (
+            'Baz\\' => 4,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Sit\\' => 
+        array (
+            0 => 'phar://' . __DIR__ . '/..' . '/a/a/dir/sit.phar/src',
+        ),
+        'Qux\\' => 
+        array (
+            0 => 'phar://' . __DIR__ . '/../..' . '/dir/qux.phar/src',
+        ),
+        'Dolor\\' => 
+        array (
+            0 => 'phar://' . __DIR__ . '/..' . '/a/a/dolor.phar',
+        ),
+        'Baz\\' => 
+        array (
+            0 => 'phar://' . __DIR__ . '/../..' . '/baz.phar',
+        ),
+    );
+
+    public static $prefixesPsr0 = array (
+        'L' => 
+        array (
+            'Lorem' => 
+            array (
+                0 => 'phar://' . __DIR__ . '/..' . '/a/a/lorem.phar',
+            ),
+        ),
+        'I' => 
+        array (
+            'Ipsum' => 
+            array (
+                0 => 'phar://' . __DIR__ . '/..' . '/a/a/dir/ipsum.phar/src',
+            ),
+        ),
+        'F' => 
+        array (
+            'Foo' => 
+            array (
+                0 => 'phar://' . __DIR__ . '/../..' . '/foo.phar',
+            ),
+        ),
+        'B' => 
+        array (
+            'Bar' => 
+            array (
+                0 => 'phar://' . __DIR__ . '/../..' . '/dir/bar.phar/src',
+            ),
+        ),
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInitPhar::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitPhar::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInitPhar::$prefixesPsr0;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 61 - 22
tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php

@@ -7,42 +7,81 @@ namespace Foo;
  */
 class StripNoise
 {
-    public function test()
+    public function test_heredoc()
     {
-        return <<<A
-class Fail2
+        return <<<HEREDOC
+class FailHeredocBasic
 {
-
 }
-A
-. <<<  AB
-class Fail3
+HEREDOC . <<<  WHITESPACE
+class FailHeredocWhitespace
 {
-
 }
-AB
-. <<<'TEST'
-class Fail4
+WHITESPACE . <<<"DOUBLEQUOTES"
+class FailHeredocDoubleQuotes
 {
-
 }
-TEST
-. <<< 'ANOTHER'
-class Fail5
+DOUBLEQUOTES . <<<	"DOUBLEQUOTESTABBED"
+class FailHeredocDoubleQuotesTabbed
 {
+}
+DOUBLEQUOTESTABBED . <<<HEREDOCPHP73
+  class FailHeredocPHP73
+  {
+  }
+  HEREDOCPHP73;
+    }
 
+    public function test_nowdoc()
+    {
+        return <<<'NOWDOC'
+class FailNowdocBasic
+{
 }
-ANOTHER
-. <<<	'ONEMORE'
-class Fail6
+NOWDOC . <<<  'WHITESPACE'
+class FailNowdocWhitespace
+{
+}
+WHITESPACE . <<<	'NOWDOCTABBED'
+class FailNowdocTabbed
 {
-
 }
-ONEMORE;
+NOWDOCTABBED . <<<'NOWDOCPHP73'
+  class FailNowdocPHP73
+  {
+  }
+  NOWDOCPHP73;
+    }
+
+    public function test_followed_by_parentheses()
+    {
+        return array(<<<PARENTHESES
+            class FailParentheses
+            {
+            }
+            PARENTHESES);
+    }
+
+    public function test_followed_by_comma()
+    {
+        return array(1, 2, <<<COMMA
+            class FailComma
+            {
+            }
+            COMMA, 3, 4);
+    }
+
+    public function test_followed_by_period()
+    {
+        return <<<PERIOD
+            class FailPeriod
+            {
+            }
+            PERIOD.'?>';
     }
 
-    public function test2()
+    public function test_simple_string()
     {
-        $class = 'class Fail4 {}';
+        return 'class FailSimpleString {}';
     }
 }

+ 20 - 0
tests/Composer/Test/Json/JsonManipulatorTest.php

@@ -2374,6 +2374,26 @@ class JsonManipulatorTest extends TestCase
         "package/a": "*"
     }
 }
+', $manipulator->getContents());
+    }
+
+    public function testEscapedUnicodeDoesNotCauseBacktrackLimitErrorGithubIssue8131()
+    {
+        $manipulator = new JsonManipulator('{
+  "description": "Some U\u00F1icode",
+  "require": {
+    "foo/bar": "^1.0"
+  }
+}');
+
+        $this->assertTrue($manipulator->addLink('require', 'foo/baz', '^1.0'));
+        $this->assertEquals('{
+  "description": "Some U\u00F1icode",
+  "require": {
+    "foo/bar": "^1.0",
+    "foo/baz": "^1.0"
+  }
+}
 ', $manipulator->getContents());
     }
 }

+ 36 - 0
tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php

@@ -298,6 +298,30 @@ class ValidatingArrayLoaderTest extends TestCase
                     'homepage : invalid value (foo:bar), must be an http/https URL',
                 ),
             ),
+            array(
+                array(
+                    'name' => 'foo/bar.json',
+                ),
+                array(
+                    'Deprecation warning: Your package name foo/bar.json is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead. Make sure you fix this as Composer 2.0 will error.',
+                ),
+            ),
+            array(
+                array(
+                    'name' => 'com1/foo',
+                ),
+                array(
+                    'Deprecation warning: Your package name com1/foo is reserved, package and vendor names can not match any of: nul, con, prn, aux, com1, com2, com3, com4, com5, com6, com7, com8, com9, lpt1, lpt2, lpt3, lpt4, lpt5, lpt6, lpt7, lpt8, lpt9. Make sure you fix this as Composer 2.0 will error.',
+                ),
+            ),
+            array(
+                array(
+                    'name' => 'Foo/Bar',
+                ),
+                array(
+                    'Deprecation warning: Your package name Foo/Bar is invalid, it should not contain uppercase characters. We suggest using foo/bar instead. Make sure you fix this as Composer 2.0 will error.',
+                ),
+            ),
             array(
                 array(
                     'name' => 'foo/bar',
@@ -337,6 +361,18 @@ class ValidatingArrayLoaderTest extends TestCase
                 ),
                 false,
             ),
+            array(
+                array(
+                    'name' => 'foo/bar',
+                    'require' => array(
+                        'Foo/Baz' => '^1.0',
+                    ),
+                ),
+                array(
+                    'Deprecation warning: require.Foo/Baz is invalid, it should not contain uppercase characters. Please use foo/baz instead. Make sure you fix this as Composer 2.0 will error.',
+                ),
+                false,
+            ),
             array(
                 array(
                     'name' => 'foo/bar',

+ 67 - 0
tests/Composer/Test/Repository/ComposerRepositoryTest.php

@@ -214,4 +214,71 @@ class ComposerRepositoryTest extends TestCase
             $repository->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'library')
         );
     }
+
+    /**
+     * @dataProvider canonicalizeUrlProvider
+     *
+     * @param string $expected
+     * @param string $url
+     * @param string $repositoryUrl
+     */
+    public function testCanonicalizeUrl($expected, $url, $repositoryUrl)
+    {
+        $repository = new ComposerRepository(
+            array('url' => $repositoryUrl),
+            new NullIO(),
+            FactoryMock::createConfig()
+        );
+
+        $object = new \ReflectionObject($repository);
+
+        $method = $object->getMethod('canonicalizeUrl');
+        $method->setAccessible(true);
+
+        // ComposerRepository::__construct ensures that the repository URL has a
+        // protocol, so reset it here in order to test all cases.
+        $property = $object->getProperty('url');
+        $property->setAccessible(true);
+        $property->setValue($repository, $repositoryUrl);
+
+        $this->assertSame($expected, $method->invoke($repository, $url));
+    }
+
+    public function canonicalizeUrlProvider()
+    {
+        return array(
+            array(
+                'https://example.org/path/to/file',
+                '/path/to/file',
+                'https://example.org',
+            ),
+            array(
+                'https://example.org/canonic_url',
+                'https://example.org/canonic_url',
+                'https://should-not-see-me.test',
+            ),
+            array(
+                'file:///path/to/repository/file',
+                '/path/to/repository/file',
+                'file:///path/to/repository',
+            ),
+            array(
+                // Assert that the repository URL is returned unchanged if it is
+                // not a URL.
+                // (Backward compatibility test)
+                'invalid_repo_url',
+                '/path/to/file',
+                'invalid_repo_url',
+            ),
+            array(
+                // Assert that URLs can contain sequences resembling pattern
+                // references as understood by preg_replace() without messing up
+                // the result.
+                // (Regression test)
+                'https://example.org/path/to/unusual_$0_filename',
+                '/path/to/unusual_$0_filename',
+                'https://example.org',
+            ),
+        );
+    }
 }

+ 17 - 3
tests/Composer/Test/Util/ProcessExecutorTest.php

@@ -61,11 +61,25 @@ class ProcessExecutorTest extends TestCase
         ProcessExecutor::setTimeout(60);
     }
 
-    public function testHidePasswords()
+    /**
+     * @dataProvider hidePasswordProvider
+     */
+    public function testHidePasswords($command, $expectedCommandOutput)
     {
         $process = new ProcessExecutor($buffer = new BufferIO('', StreamOutput::VERBOSITY_DEBUG));
-        $process->execute('echo https://foo:bar@example.org/ && echo http://foo@example.org && echo http://abcdef1234567890234578:x-oauth-token@github.com/', $output);
-        $this->assertEquals('Executing command (CWD): echo https://foo:***@example.org/ && echo http://foo@example.org && echo http://***:***@github.com/', trim($buffer->getOutput()));
+        $process->execute($command, $output);
+        $this->assertEquals('Executing command (CWD): ' . $expectedCommandOutput, trim($buffer->getOutput()));
+    }
+
+    public function hidePasswordProvider()
+    {
+        return array(
+            array('echo https://foo:bar@example.org/', 'echo https://foo:***@example.org/'),
+            array('echo http://foo@example.org', 'echo http://foo@example.org'),
+            array('echo http://abcdef1234567890234578:x-oauth-token@github.com/', 'echo http://***:***@github.com/'),
+            array("svn ls --verbose --non-interactive  --username 'foo' --password 'bar'  'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive  --username 'foo' --password '***'  'https://foo.example.org/svn/'"),
+            array("svn ls --verbose --non-interactive  --username 'foo' --password 'bar \'bar'  'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive  --username 'foo' --password '***'  'https://foo.example.org/svn/'"),
+        );
     }
 
     public function testDoesntHidePorts()