Browse Source

Merge branch '2.0' into solve-without-installed

* 2.0: (101 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 5 years ago
parent
commit
9053d74282
100 changed files with 833 additions and 443 deletions
  1. 5 0
      .gitattributes
  2. 8 1
      .travis.yml
  3. 28 0
      CHANGELOG.md
  4. 1 1
      appveyor.yml
  5. 5 0
      bin/composer
  6. 2 2
      composer.json
  7. 42 43
      composer.lock
  8. 3 3
      doc/00-intro.md
  9. 4 0
      doc/03-cli.md
  10. 4 0
      doc/05-repositories.md
  11. 14 0
      doc/06-config.md
  12. 5 0
      doc/articles/plugins.md
  13. 51 1
      doc/articles/scripts.md
  14. 1 1
      doc/articles/versions.md
  15. 5 0
      phpstan/autoload.php
  16. 42 0
      phpstan/config.neon
  17. 18 66
      src/Composer/Autoload/AutoloadGenerator.php
  18. 1 1
      src/Composer/Autoload/ClassMapGenerator.php
  19. 2 1
      src/Composer/Cache.php
  20. 1 0
      src/Composer/Command/ArchiveCommand.php
  21. 4 2
      src/Composer/Command/BaseCommand.php
  22. 2 0
      src/Composer/Command/ClearCacheCommand.php
  23. 3 1
      src/Composer/Command/ConfigCommand.php
  24. 4 1
      src/Composer/Command/CreateProjectCommand.php
  25. 1 0
      src/Composer/Command/DependsCommand.php
  26. 2 15
      src/Composer/Command/DiagnoseCommand.php
  27. 2 0
      src/Composer/Command/DumpAutoloadCommand.php
  28. 7 0
      src/Composer/Command/ExecCommand.php
  29. 1 0
      src/Composer/Command/GlobalCommand.php
  30. 2 0
      src/Composer/Command/HomeCommand.php
  31. 12 4
      src/Composer/Command/InitCommand.php
  32. 1 0
      src/Composer/Command/InstallCommand.php
  33. 1 0
      src/Composer/Command/LicensesCommand.php
  34. 1 1
      src/Composer/Command/OutdatedCommand.php
  35. 1 0
      src/Composer/Command/ProhibitsCommand.php
  36. 1 0
      src/Composer/Command/RemoveCommand.php
  37. 17 4
      src/Composer/Command/RequireCommand.php
  38. 2 0
      src/Composer/Command/RunScriptCommand.php
  39. 2 0
      src/Composer/Command/ScriptAliasCommand.php
  40. 1 0
      src/Composer/Command/SearchCommand.php
  41. 1 0
      src/Composer/Command/SelfUpdateCommand.php
  42. 7 6
      src/Composer/Command/ShowCommand.php
  43. 1 0
      src/Composer/Command/StatusCommand.php
  44. 5 1
      src/Composer/Command/SuggestsCommand.php
  45. 1 0
      src/Composer/Command/UpdateCommand.php
  46. 1 0
      src/Composer/Command/ValidateCommand.php
  47. 1 0
      src/Composer/Compiler.php
  48. 37 1
      src/Composer/Composer.php
  49. 17 0
      src/Composer/Config.php
  50. 1 1
      src/Composer/Config/ConfigSourceInterface.php
  51. 1 1
      src/Composer/Config/JsonConfigSource.php
  52. 4 4
      src/Composer/Console/Application.php
  53. 4 4
      src/Composer/DependencyResolver/GenericRule.php
  54. 6 6
      src/Composer/DependencyResolver/PoolBuilder.php
  55. 38 34
      src/Composer/DependencyResolver/Problem.php
  56. 3 2
      src/Composer/DependencyResolver/Solver.php
  57. 1 3
      src/Composer/Downloader/DownloadManager.php
  58. 3 3
      src/Composer/Downloader/GitDownloader.php
  59. 1 0
      src/Composer/Downloader/GzipDownloader.php
  60. 27 0
      src/Composer/Downloader/PathDownloader.php
  61. 2 2
      src/Composer/Downloader/PerforceDownloader.php
  62. 1 0
      src/Composer/Downloader/RarDownloader.php
  63. 1 0
      src/Composer/Downloader/XzDownloader.php
  64. 2 0
      src/Composer/Downloader/ZipDownloader.php
  65. 22 2
      src/Composer/EventDispatcher/EventDispatcher.php
  66. 1 1
      src/Composer/Factory.php
  67. 1 2
      src/Composer/IO/BaseIO.php
  68. 5 4
      src/Composer/IO/IOInterface.php
  69. 4 1
      src/Composer/Installer.php
  70. 1 1
      src/Composer/Installer/InstallerInterface.php
  71. 1 1
      src/Composer/Installer/LibraryInstaller.php
  72. 1 1
      src/Composer/Json/JsonManipulator.php
  73. 10 0
      src/Composer/Package/AliasPackage.php
  74. 1 1
      src/Composer/Package/BasePackage.php
  75. 4 27
      src/Composer/Package/Loader/ArrayLoader.php
  76. 7 1
      src/Composer/Package/Loader/ValidatingArrayLoader.php
  77. 8 0
      src/Composer/Package/Locker.php
  78. 29 1
      src/Composer/Package/PackageInterface.php
  79. 1 0
      src/Composer/Package/Version/VersionGuesser.php
  80. 1 1
      src/Composer/Package/Version/VersionSelector.php
  81. 6 3
      src/Composer/Plugin/PluginManager.php
  82. 5 3
      src/Composer/Repository/BaseRepository.php
  83. 62 63
      src/Composer/Repository/ComposerRepository.php
  84. 5 0
      src/Composer/Repository/FilesystemRepository.php
  85. 1 1
      src/Composer/Repository/Pear/BaseChannelReader.php
  86. 1 1
      src/Composer/Repository/Pear/ChannelRest10Reader.php
  87. 1 1
      src/Composer/Repository/PearRepository.php
  88. 8 2
      src/Composer/Repository/PlatformRepository.php
  89. 2 1
      src/Composer/Repository/RepositoryInterface.php
  90. 1 7
      src/Composer/Repository/RepositoryManager.php
  91. 47 38
      src/Composer/Repository/Vcs/BitbucketDriver.php
  92. 1 1
      src/Composer/Repository/Vcs/GitHubDriver.php
  93. 3 2
      src/Composer/Repository/Vcs/HgDriver.php
  94. 3 3
      src/Composer/Repository/Vcs/VcsDriverInterface.php
  95. 61 28
      src/Composer/Repository/VcsRepository.php
  96. 1 1
      src/Composer/Repository/VersionCacheInterface.php
  97. 2 7
      src/Composer/Util/AuthHelper.php
  98. 6 0
      src/Composer/Util/Bitbucket.php
  99. 41 21
      src/Composer/Util/Filesystem.php
  100. 4 0
      src/Composer/Util/GitHub.php

+ 5 - 0
.gitattributes

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

+ 8 - 1
.travis.yml

@@ -25,8 +25,10 @@ matrix:
     - php: 7.1
     - php: 7.1
     - php: 7.2
     - php: 7.2
     - php: 7.3
     - php: 7.3
+      env: PHPSTAN=1
     - php: 7.3
     - php: 7.3
-      env: deps=high
+      env:
+        - deps=high
     - php: nightly
     - php: nightly
   fast_finish: true
   fast_finish: true
   allow_failures:
   allow_failures:
@@ -58,6 +60,11 @@ before_script:
 script:
 script:
   # run test suite directories in parallel using GNU parallel
   # run test suite directories in parallel using GNU parallel
   - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
   - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
+  # Run PHPStan
+  - if [[ $PHPSTAN == "1" ]]; then
+      composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs &&
+      vendor/bin/phpstan.phar analyse src tests --configuration=phpstan/config.neon --autoload-file=phpstan/autoload.php;
+    fi
 
 
 before_deploy:
 before_deploy:
   - php -d phar.readonly=0 bin/compile
   - php -d phar.readonly=0 bin/compile

+ 28 - 0
CHANGELOG.md

@@ -1,3 +1,25 @@
+### [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
+  * Fixed HHVM support for upcoming releases
+  * Fixed unix proxy for binaries to be POSIX compatible instead of breaking some shells
+  * Fixed invalid deprecation warning for composer-plugin-api
+  * Fixed edge case issues with Windows junctions when working with path repositories
+
 ### [1.8.3] 2019-01-30
 ### [1.8.3] 2019-01-30
 
 
   * Fixed regression when executing partial updates
   * Fixed regression when executing partial updates
@@ -729,6 +751,12 @@
 
 
   * Initial release
   * 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
+[1.8.1]: https://github.com/composer/composer/compare/1.8.0...1.8.1
 [1.8.0]: https://github.com/composer/composer/compare/1.7.3...1.8.0
 [1.8.0]: https://github.com/composer/composer/compare/1.7.3...1.8.0
 [1.7.3]: https://github.com/composer/composer/compare/1.7.2...1.7.3
 [1.7.3]: https://github.com/composer/composer/compare/1.7.2...1.7.3
 [1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2
 [1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2

+ 1 - 1
appveyor.yml

@@ -3,7 +3,7 @@ clone_depth: 5
 
 
 environment:
 environment:
   # This sets the PHP version (from Chocolatey)
   # This sets the PHP version (from Chocolatey)
-  PHPCI_CHOCO_VERSION: 7.2.9
+  PHPCI_CHOCO_VERSION: 7.3.1
   PHPCI_CACHE: C:\tools\phpci
   PHPCI_CACHE: C:\tools\phpci
   PHPCI_PHP: C:\tools\phpci\php
   PHPCI_PHP: C:\tools\phpci\php
   PHPCI_COMPOSER: C:\tools\phpci\composer
   PHPCI_COMPOSER: C:\tools\phpci\composer

+ 5 - 0
bin/composer

@@ -18,6 +18,11 @@ $xdebug = new XdebugHandler('Composer', '--ansi');
 $xdebug->check();
 $xdebug->check();
 unset($xdebug);
 unset($xdebug);
 
 
+if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) {
+    echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL;
+    exit(1);
+}
+
 if (function_exists('ini_set')) {
 if (function_exists('ini_set')) {
     @ini_set('display_errors', 1);
     @ini_set('display_errors', 1);
 
 

+ 2 - 2
composer.json

@@ -1,7 +1,7 @@
 {
 {
     "name": "composer/composer",
     "name": "composer/composer",
     "type": "library",
     "type": "library",
-    "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
+    "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.",
     "keywords": [
     "keywords": [
         "package",
         "package",
         "dependency",
         "dependency",
@@ -56,7 +56,7 @@
     },
     },
     "extra": {
     "extra": {
         "branch-alias": {
         "branch-alias": {
-            "dev-master": "1.9-dev"
+            "dev-master": "2.0-dev"
         }
         }
     },
     },
     "autoload": {
     "autoload": {

+ 42 - 43
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
         "This file is @generated automatically"
     ],
     ],
-    "content-hash": "b078b12b2912d599e0c6904f64def484",
+    "content-hash": "280f5d5184039085b5f22236d267ae82",
     "packages": [
     "packages": [
         {
         {
             "name": "composer/ca-bundle",
             "name": "composer/ca-bundle",
@@ -64,16 +64,16 @@
         },
         },
         {
         {
             "name": "composer/semver",
             "name": "composer/semver",
-            "version": "1.4.2",
+            "version": "1.5.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/composer/semver.git",
                 "url": "https://github.com/composer/semver.git",
-                "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
+                "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
-                "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
+                "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
+                "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -122,28 +122,27 @@
                 "validation",
                 "validation",
                 "versioning"
                 "versioning"
             ],
             ],
-            "time": "2016-08-30T16:08:34+00:00"
+            "time": "2019-03-19T17:25:45+00:00"
         },
         },
         {
         {
             "name": "composer/spdx-licenses",
             "name": "composer/spdx-licenses",
-            "version": "1.5.0",
+            "version": "1.5.1",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/composer/spdx-licenses.git",
                 "url": "https://github.com/composer/spdx-licenses.git",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2"
+                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
-                "php": "^5.3.2 || ^7.0"
+                "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             },
             "require-dev": {
             "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",
             "type": "library",
             "extra": {
             "extra": {
@@ -183,20 +182,20 @@
                 "spdx",
                 "spdx",
                 "validator"
                 "validator"
             ],
             ],
-            "time": "2018-11-01T09:45:54+00:00"
+            "time": "2019-03-26T10:23:26+00:00"
         },
         },
         {
         {
             "name": "composer/xdebug-handler",
             "name": "composer/xdebug-handler",
-            "version": "1.3.2",
+            "version": "1.3.3",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/composer/xdebug-handler.git",
                 "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "d17708133b6c276d6e42ef887a877866b909d892"
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -227,27 +226,27 @@
                 "Xdebug",
                 "Xdebug",
                 "performance"
                 "performance"
             ],
             ],
-            "time": "2019-01-28T20:25:53+00:00"
+            "time": "2019-05-27T17:52:04+00:00"
         },
         },
         {
         {
             "name": "justinrainbow/json-schema",
             "name": "justinrainbow/json-schema",
-            "version": "5.2.7",
+            "version": "5.2.8",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/justinrainbow/json-schema.git",
                 "url": "https://github.com/justinrainbow/json-schema.git",
-                "reference": "8560d4314577199ba51bf2032f02cd1315587c23"
+                "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23",
-                "reference": "8560d4314577199ba51bf2032f02cd1315587c23",
+                "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4",
+                "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
                 "php": ">=5.3.3"
                 "php": ">=5.3.3"
             },
             },
             "require-dev": {
             "require-dev": {
-                "friendsofphp/php-cs-fixer": "^2.1",
+                "friendsofphp/php-cs-fixer": "~2.2.20",
                 "json-schema/json-schema-test-suite": "1.2.0",
                 "json-schema/json-schema-test-suite": "1.2.0",
                 "phpunit/phpunit": "^4.8.35"
                 "phpunit/phpunit": "^4.8.35"
             },
             },
@@ -293,7 +292,7 @@
                 "json",
                 "json",
                 "schema"
                 "schema"
             ],
             ],
-            "time": "2018-02-14T22:26:30+00:00"
+            "time": "2019-01-14T23:55:14+00:00"
         },
         },
         {
         {
             "name": "psr/log",
             "name": "psr/log",
@@ -481,7 +480,7 @@
         },
         },
         {
         {
             "name": "symfony/console",
             "name": "symfony/console",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
                 "url": "https://github.com/symfony/console.git",
@@ -542,7 +541,7 @@
         },
         },
         {
         {
             "name": "symfony/debug",
             "name": "symfony/debug",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
                 "url": "https://github.com/symfony/debug.git",
@@ -599,7 +598,7 @@
         },
         },
         {
         {
             "name": "symfony/filesystem",
             "name": "symfony/filesystem",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
                 "url": "https://github.com/symfony/filesystem.git",
@@ -649,7 +648,7 @@
         },
         },
         {
         {
             "name": "symfony/finder",
             "name": "symfony/finder",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
                 "url": "https://github.com/symfony/finder.git",
@@ -698,16 +697,16 @@
         },
         },
         {
         {
             "name": "symfony/polyfill-ctype",
             "name": "symfony/polyfill-ctype",
-            "version": "v1.10.0",
+            "version": "v1.11.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+                "reference": "82ebae02209c21113908c229e9883c419720738a"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -719,7 +718,7 @@
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
                 "branch-alias": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.11-dev"
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
@@ -752,20 +751,20 @@
                 "polyfill",
                 "polyfill",
                 "portable"
                 "portable"
             ],
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2019-02-06T07:57:58+00:00"
         },
         },
         {
         {
             "name": "symfony/polyfill-mbstring",
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.10.0",
+            "version": "v1.11.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -777,7 +776,7 @@
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
                 "branch-alias": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.11-dev"
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
@@ -811,11 +810,11 @@
                 "portable",
                 "portable",
                 "shim"
                 "shim"
             ],
             ],
-            "time": "2018-09-21T13:07:52+00:00"
+            "time": "2019-02-06T07:57:58+00:00"
         },
         },
         {
         {
             "name": "symfony/process",
             "name": "symfony/process",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
                 "url": "https://github.com/symfony/process.git",
@@ -1780,7 +1779,7 @@
         },
         },
         {
         {
             "name": "symfony/yaml",
             "name": "symfony/yaml",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
                 "url": "https://github.com/symfony/yaml.git",

+ 3 - 3
doc/00-intro.md

@@ -10,7 +10,7 @@ Composer is **not** a package manager in the same sense as Yum or Apt are. Yes,
 it deals with "packages" or libraries, but it manages them on a per-project
 it deals with "packages" or libraries, but it manages them on a per-project
 basis, installing them in a directory (e.g. `vendor`) inside your project. By
 basis, installing them in a directory (e.g. `vendor`) inside your project. By
 default it does not install anything globally. Thus, it is a dependency
 default it does not install anything globally. Thus, it is a dependency
-manager. It does however support a "global" project for convenience via the
+manager. It does however support a "global" project for convenience via the 
 [global](03-cli.md#global) command.
 [global](03-cli.md#global) command.
 
 
 This idea is not new and Composer is strongly inspired by node's
 This idea is not new and Composer is strongly inspired by node's
@@ -47,7 +47,7 @@ Linux and macOS.
 ### Downloading the Composer Executable
 ### Downloading the Composer Executable
 
 
 Composer offers a convenient installer that you can execute directly from the
 Composer offers a convenient installer that you can execute directly from the
-commandline. Feel free to [download this file](https://getcomposer.org/installer)
+command line. Feel free to [download this file](https://getcomposer.org/installer)
 or review it on [GitHub](https://github.com/composer/getcomposer.org/blob/master/web/installer)
 or review it on [GitHub](https://github.com/composer/getcomposer.org/blob/master/web/installer)
 if you wish to know more about the inner workings of the installer. The source
 if you wish to know more about the inner workings of the installer. The source
 is plain PHP.
 is plain PHP.
@@ -82,7 +82,7 @@ Now run `php bin/composer` in order to run Composer.
 #### Globally
 #### Globally
 
 
 You can place the Composer PHAR anywhere you wish. If you put it in a directory
 You can place the Composer PHAR anywhere you wish. If you put it in a directory
-that is part of your `PATH`, you can access it globally. On unixy systems you
+that is part of your `PATH`, you can access it globally. On Unix systems you
 can even make it executable and invoke it without directly using the `php`
 can even make it executable and invoke it without directly using the `php`
 interpreter.
 interpreter.
 
 

+ 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
 requests, then you should set this env var to `false` or `0` to prevent Composer
 from setting the request_fulluri option.
 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
 ### no_proxy or NO_PROXY
 
 
 If you are behind a proxy and would like to disable it for certain domains, you
 If you are behind a proxy and would like to disable it for certain domains, you

+ 4 - 0
doc/05-repositories.md

@@ -666,6 +666,10 @@ Instead of default fallback strategy you can force to use symlink with
 mirroring can be useful when deploying or generating package from a
 mirroring can be useful when deploying or generating package from a
 monolithic repository.
 monolithic repository.
 
 
+> **Note:** On Windows, directory symlinks are implemented using NTFS junctions
+> because they can be created by non-admin users. Mirroring will always be used
+> on versions below Windows 7 or if `proc_open` has been disabled.
+
 ```json
 ```json
 {
 {
     "repositories": [
     "repositories": [

+ 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
 Composer assumes they died out. You may need to make this higher if you have a
 slow connection or huge vendors.
 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
 ## use-include-path
 
 
 Defaults to `false`. If `true`, the Composer autoloader will also look for classes
 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]._
 > _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
 ## Using Plugins
 
 
 Plugin packages are automatically loaded as soon as they are installed and will
 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
 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 `--`
 You can also give additional arguments to the script handler by appending `--`
 followed by the handler arguments. e.g.
 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
 > 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.
 > 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
 ## Referencing scripts
 
 
 To enable script re-use and avoid duplicates, you can call a script from another
 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
 v1
 v2
 v2
 my-feature
 my-feature
-nother-feature
+another-feature
 
 
 ~/my-library$ git tag
 ~/my-library$ git tag
 v1.0
 v1.0

+ 5 - 0
phpstan/autoload.php

@@ -0,0 +1,5 @@
+<?php
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+require_once __DIR__ . '/../src/bootstrap.php';

+ 42 - 0
phpstan/config.neon

@@ -0,0 +1,42 @@
+parameters:
+    level: 0
+    excludes_analyse:
+        - 'tests/Composer/Test/Fixtures'
+        - 'tests/Composer/Test/Autoload/Fixtures'
+        - 'tests/Composer/Test/Plugin/Fixtures'
+    ignoreErrors:
+        # unused parameters
+        - '~^Constructor of class Composer\\Repository\\VcsRepository has an unused parameter \$dispatcher\.$~'
+        - '~^Constructor of class Composer\\Repository\\PearRepository has an unused parameter \$dispatcher\.$~'
+        - '~^Constructor of class Composer\\Util\\Http\\CurlDownloader has an unused parameter \$disableTls\.$~'
+        - '~^Constructor of class Composer\\Util\\Http\\CurlDownloader has an unused parameter \$options\.$~'
+        - '~^Constructor of class Composer\\Repository\\PearRepository has an unused parameter \$config\.$~'
+
+        # unused uses
+        - '~^Anonymous function has an unused use \$io\.$~'
+        - '~^Anonymous function has an unused use \$cache\.$~'
+        - '~^Anonymous function has an unused use \$path\.$~'
+        - '~^Anonymous function has an unused use \$fileName\.$~'
+
+        # ion cube is not installed
+        - '~^Function ioncube_loader_\w+ not found\.$~'
+        # rar is not installed
+        - '~^Call to static method open\(\) on an unknown class RarArchive\.$~'
+        # imagick is not installed
+        - '~^Instantiated class Imagick not found\.$~'
+
+        # variables from global scope
+        - '~^Undefined variable: \$vendorDir$~'
+        - '~^Undefined variable: \$baseDir$~'
+
+        # variable defined in eval
+        - '~^Undefined variable: \$res$~'
+
+        # always checked whether the class exists
+        - '~^Instantiated class Symfony\\Component\\Console\\Terminal not found\.$~'
+        - '~^Class Symfony\\Component\\Console\\Input\\StreamableInputInterface not found\.$~'
+        - '~^Call to an undefined static method Symfony\\Component\\Process\\Process::fromShellCommandline\(\).$~'
+
+        # parent call in test mocks
+        - '~^Composer\\Test\\Mock\\HttpDownloaderMock::__construct\(\) does not call parent constructor from Composer\\Util\\HttpDownloader\.$~'
+        - '~^Composer\\Test\\Mock\\InstallationManagerMock::__construct\(\) does not call parent constructor from Composer\\Installer\\InstallationManager\.$~'

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

@@ -21,6 +21,7 @@ use Composer\Package\PackageInterface;
 use Composer\Repository\InstalledRepositoryInterface;
 use Composer\Repository\InstalledRepositoryInterface;
 use Composer\Util\Filesystem;
 use Composer\Util\Filesystem;
 use Composer\Script\ScriptEvents;
 use Composer\Script\ScriptEvents;
+use Composer\Util\PackageSorter;
 
 
 /**
 /**
  * @author Igor Wiedler <igor@wiedler.ch>
  * @author Igor Wiedler <igor@wiedler.ch>
@@ -545,7 +546,7 @@ EOF;
             }
             }
         }
         }
 
 
-        if (preg_match('/\.phar.+$/', $path)) {
+        if (strpos($path, '.phar') !== false) {
             $baseDir = "'phar://' . " . $baseDir;
             $baseDir = "'phar://' . " . $baseDir;
         }
         }
 
 
@@ -769,10 +770,14 @@ HEADER;
         $filesystem = new Filesystem();
         $filesystem = new Filesystem();
 
 
         $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
         $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) . " . '/";
         $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);
         $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);
         $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1);
+        $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1);
 
 
         $initializer = '';
         $initializer = '';
         $prefix = "\0Composer\Autoload\ClassLoader\0";
         $prefix = "\0Composer\Autoload\ClassLoader\0";
@@ -795,9 +800,15 @@ HEADER;
                 // See https://bugs.php.net/68057
                 // See https://bugs.php.net/68057
                 $staticPhpVersion = 70000;
                 $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));
             $value = ltrim(preg_replace('/^ */m', '    $0$0', $value));
 
 
             $file .= sprintf("    public static $%s = %s;\n\n", $prop, $value);
             $file .= sprintf("    public static $%s = %s;\n\n", $prop, $value);
@@ -963,80 +974,21 @@ INITIALIZER;
     {
     {
         $packages = array();
         $packages = array();
         $paths = array();
         $paths = array();
-        $usageList = array();
 
 
         foreach ($packageMap as $item) {
         foreach ($packageMap as $item) {
             list($package, $path) = $item;
             list($package, $path) = $item;
             $name = $package->getName();
             $name = $package->getName();
             $packages[$name] = $package;
             $packages[$name] = $package;
             $paths[$name] = $path;
             $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();
         $sortedPackageMap = array();
 
 
-        foreach (array_keys($weightList) as $name) {
+        foreach ($sortedPackages as $package) {
+            $name = $package->getName();
             $sortedPackageMap[] = array($packages[$name], $paths[$name]);
             $sortedPackageMap[] = array($packages[$name], $paths[$name]);
         }
         }
 
 

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

@@ -162,7 +162,7 @@ class ClassMapGenerator
         }
         }
 
 
         // strip heredocs/nowdocs
         // 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
         // strip strings
         $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
         $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
         // strip leading non-php code if needed
         // strip leading non-php code if needed

+ 2 - 1
src/Composer/Cache.php

@@ -189,7 +189,8 @@ class Cache
     public function clear()
     public function clear()
     {
     {
         if ($this->enabled) {
         if ($this->enabled) {
-            return $this->filesystem->removeDirectory($this->root);
+            $this->filesystem->emptyDirectory($this->root);
+            return true;
         }
         }
 
 
         return false;
         return false;

+ 1 - 0
src/Composer/Command/ArchiveCommand.php

@@ -57,6 +57,7 @@ package in the specified version and writes it to the specified directory.
 
 
 <info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info>
 <info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#archive
 EOT
 EOT
             )
             )
         ;
         ;

+ 4 - 2
src/Composer/Command/BaseCommand.php

@@ -27,6 +27,8 @@ use Symfony\Component\Console\Command\Command;
 /**
 /**
  * Base class for Composer commands
  * Base class for Composer commands
  *
  *
+ * @method Application getApplication()
+ *
  * @author Ryan Weaver <ryan@knplabs.com>
  * @author Ryan Weaver <ryan@knplabs.com>
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
  */
  */
@@ -46,7 +48,7 @@ abstract class BaseCommand extends Command
      * @param  bool              $required
      * @param  bool              $required
      * @param  bool|null         $disablePlugins
      * @param  bool|null         $disablePlugins
      * @throws \RuntimeException
      * @throws \RuntimeException
-     * @return Composer
+     * @return Composer|null
      */
      */
     public function getComposer($required = true, $disablePlugins = null)
     public function getComposer($required = true, $disablePlugins = null)
     {
     {
@@ -173,7 +175,7 @@ abstract class BaseCommand extends Command
 
 
         if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
         if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
             $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
             $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
-            $preferDist = $input->getOption('prefer-dist');
+            $preferDist = (bool) $input->getOption('prefer-dist');
         }
         }
 
 
         return array($preferSource, $preferDist);
         return array($preferSource, $preferDist);

+ 2 - 0
src/Composer/Command/ClearCacheCommand.php

@@ -32,6 +32,8 @@ class ClearCacheCommand extends BaseCommand
                 <<<EOT
                 <<<EOT
 The <info>clear-cache</info> deletes all cached packages from composer's
 The <info>clear-cache</info> deletes all cached packages from composer's
 cache directory.
 cache directory.
+
+Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-
 EOT
 EOT
             )
             )
         ;
         ;

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

@@ -125,6 +125,8 @@ You can always pass more than one option. As an example, if you want to edit the
 global config.json file.
 global config.json file.
 
 
     <comment>%command.full_name% --editor --global</comment>
     <comment>%command.full_name% --editor --global</comment>
+
+Read more at https://getcomposer.org/doc/03-cli.md#config
 EOT
 EOT
             )
             )
         ;
         ;
@@ -226,7 +228,7 @@ EOT
         }
         }
 
 
         $settingKey = $input->getArgument('setting-key');
         $settingKey = $input->getArgument('setting-key');
-        if (!$settingKey) {
+        if (!$settingKey || !is_string($settingKey)) {
             return 0;
             return 0;
         }
         }
 
 

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

@@ -105,6 +105,7 @@ controlled code by appending the <info>'--prefer-source'</info> flag.
 To install a package from another repository than the default one you
 To install a package from another repository than the default one you
 can pass the <info>'--repository=https://myrepository.org'</info> flag.
 can pass the <info>'--repository=https://myrepository.org'</info> flag.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#create-project
 EOT
 EOT
             )
             )
         ;
         ;
@@ -183,7 +184,9 @@ EOT
                 ->setRunScripts(!$noScripts)
                 ->setRunScripts(!$noScripts)
                 ->setIgnorePlatformRequirements($ignorePlatformReqs)
                 ->setIgnorePlatformRequirements($ignorePlatformReqs)
                 ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
                 ->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) {
             if ($disablePlugins) {
                 $installer->disablePlugins();
                 $installer->disablePlugins();

+ 1 - 0
src/Composer/Command/DependsCommand.php

@@ -37,6 +37,7 @@ Displays detailed information about where a package is referenced.
 
 
 <info>php composer.phar depends composer/composer</info>
 <info>php composer.phar depends composer/composer</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#depends-why-
 EOT
 EOT
             )
             )
         ;
         ;

+ 2 - 15
src/Composer/Command/DiagnoseCommand.php

@@ -55,6 +55,7 @@ The <info>diagnose</info> command checks common errors to help debugging problem
 
 
 The process exit code will be 1 in case of warnings and 2 for errors.
 The process exit code will be 1 in case of warnings and 2 for errors.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#diagnose
 EOT
 EOT
             )
             )
         ;
         ;
@@ -602,20 +603,6 @@ EOT
                         $text .= "Install either of them or recompile php without --disable-iconv";
                         $text .= "Install either of them or recompile php without --disable-iconv";
                         break;
                         break;
 
 
-                    case 'unicode':
-                        $text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL;
-                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
-                        $text .= "    detect_unicode = Off";
-                        $displayIniMessage = true;
-                        break;
-
-                    case 'suhosin':
-                        $text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL;
-                        $text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL;
-                        $text .= "    suhosin.executor.include.whitelist = phar ".$current;
-                        $displayIniMessage = true;
-                        break;
-
                     case 'php':
                     case 'php':
                         $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
                         $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
                         break;
                         break;
@@ -713,7 +700,7 @@ EOT
     /**
     /**
      * Check if allow_url_fopen is ON
      * Check if allow_url_fopen is ON
      *
      *
-     * @return bool|string
+     * @return true|string
      */
      */
     private function checkConnectivity()
     private function checkConnectivity()
     {
     {

+ 2 - 0
src/Composer/Command/DumpAutoloadCommand.php

@@ -39,6 +39,8 @@ class DumpAutoloadCommand extends BaseCommand
             ->setHelp(
             ->setHelp(
                 <<<EOT
                 <<<EOT
 <info>php composer.phar dump-autoload</info>
 <info>php composer.phar dump-autoload</info>
+
+Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
 EOT
 EOT
             )
             )
         ;
         ;

+ 7 - 0
src/Composer/Command/ExecCommand.php

@@ -36,6 +36,13 @@ class ExecCommand extends BaseCommand
                     'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments'
                     'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments'
                 ),
                 ),
             ))
             ))
+            ->setHelp(
+                <<<EOT
+Executes a vendored binary/script.
+                
+Read more at https://getcomposer.org/doc/03-cli.md#exec
+EOT
+            )
         ;
         ;
     }
     }
 
 

+ 1 - 0
src/Composer/Command/GlobalCommand.php

@@ -50,6 +50,7 @@ XDG_CONFIG_HOME or default to /home/<user>/.config/composer
 Note: This path may vary depending on customizations to bin-dir in
 Note: This path may vary depending on customizations to bin-dir in
 composer.json or the environmental variable COMPOSER_BIN_DIR.
 composer.json or the environmental variable COMPOSER_BIN_DIR.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#global
 EOT
 EOT
             )
             )
         ;
         ;

+ 2 - 0
src/Composer/Command/HomeCommand.php

@@ -49,6 +49,8 @@ homepage in your default browser.
 
 
 To open the homepage by default, use -H or --homepage.
 To open the homepage by default, use -H or --homepage.
 To show instead of open the repository or homepage URL, use -s or --show.
 To show instead of open the repository or homepage URL, use -s or --show.
+
+Read more at https://getcomposer.org/doc/03-cli.md#browse-home
 EOT
 EOT
             );
             );
     }
     }

+ 12 - 4
src/Composer/Command/InitCommand.php

@@ -72,6 +72,7 @@ in the current directory.
 
 
 <info>php composer.phar init</info>
 <info>php composer.phar init</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#init
 EOT
 EOT
             )
             )
         ;
         ;
@@ -694,15 +695,22 @@ EOT
     {
     {
         // find the latest version allowed in this repo set
         // find the latest version allowed in this repo set
         $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
         $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
-        $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);
+        $ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs');
 
 
-        // retry without phpVersion if platform requirements are ignored in case nothing was found
-        if ($input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs')) {
+        // ignore phpVersion if platform requirements are ignored
+        if ($ignorePlatformReqs) {
             $phpVersion = null;
             $phpVersion = null;
-            $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);
         }
         }
 
 
+        $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);
+
         if (!$package) {
         if (!$package) {
+            // platform packages can not be found in the pool in versions other than the local platform's has
+            // so if platform reqs are ignored we just take the user's word for it
+            if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
+                return array($name, $requiredVersion ?: '*');
+            }
+
             // Check whether the PHP version was the problem
             // Check whether the PHP version was the problem
             if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) {
             if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) {
                 throw new \InvalidArgumentException(sprintf(
                 throw new \InvalidArgumentException(sprintf(

+ 1 - 0
src/Composer/Command/InstallCommand.php

@@ -61,6 +61,7 @@ exist it will look for composer.json and do the same.
 
 
 <info>php composer.phar install</info>
 <info>php composer.phar install</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#install-i
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 0
src/Composer/Command/LicensesCommand.php

@@ -41,6 +41,7 @@ class LicensesCommand extends BaseCommand
 The license command displays detailed information about the licenses of
 The license command displays detailed information about the licenses of
 the installed dependencies.
 the installed dependencies.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#licenses
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 1
src/Composer/Command/OutdatedCommand.php

@@ -50,7 +50,7 @@ The color coding (or signage if you have ANSI colors disabled) for dependency ve
   may involve work.
   may involve work.
 - <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.
 - <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.
 
 
-
+Read more at https://getcomposer.org/doc/03-cli.md#outdated
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 0
src/Composer/Command/ProhibitsCommand.php

@@ -37,6 +37,7 @@ Displays detailed information about why a package cannot be installed.
 
 
 <info>php composer.phar prohibits composer/composer</info>
 <info>php composer.phar prohibits composer/composer</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not-
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 0
src/Composer/Command/RemoveCommand.php

@@ -56,6 +56,7 @@ list of installed packages
 
 
 <info>php composer.phar remove</info>
 <info>php composer.phar remove</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#remove
 EOT
 EOT
             )
             )
         ;
         ;

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

@@ -25,6 +25,7 @@ use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
 use Composer\Plugin\PluginEvents;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\PlatformRepository;
+use Composer\IO\IOInterface;
 
 
 /**
 /**
  * @author Jérémy Romey <jeremy@free-agent.fr>
  * @author Jérémy Romey <jeremy@free-agent.fr>
@@ -73,6 +74,7 @@ If you do not specify a version constraint, composer will choose a suitable one
 
 
 If you do not want to install the new dependencies immediately you can call it with --no-update
 If you do not want to install the new dependencies immediately you can call it with --no-update
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#require
 EOT
 EOT
             )
             )
         ;
         ;
@@ -159,15 +161,26 @@ EOT
         if ($input->getOption('no-update')) {
         if ($input->getOption('no-update')) {
             return 0;
             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
         // Update packages
         $this->resetComposer();
         $this->resetComposer();
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $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);
         $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
         $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
         $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
 
 

+ 2 - 0
src/Composer/Command/RunScriptCommand.php

@@ -63,6 +63,8 @@ class RunScriptCommand extends BaseCommand
 The <info>run-script</info> command runs scripts defined in composer.json:
 The <info>run-script</info> command runs scripts defined in composer.json:
 
 
 <info>php composer.phar run-script post-update-cmd</info>
 <info>php composer.phar run-script post-update-cmd</info>
+
+Read more at https://getcomposer.org/doc/03-cli.md#run-script
 EOT
 EOT
             )
             )
         ;
         ;

+ 2 - 0
src/Composer/Command/ScriptAliasCommand.php

@@ -48,6 +48,8 @@ class ScriptAliasCommand extends BaseCommand
 The <info>run-script</info> command runs scripts defined in composer.json:
 The <info>run-script</info> command runs scripts defined in composer.json:
 
 
 <info>php composer.phar run-script post-update-cmd</info>
 <info>php composer.phar run-script post-update-cmd</info>
+
+Read more at https://getcomposer.org/doc/03-cli.md#run-script
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 0
src/Composer/Command/SearchCommand.php

@@ -49,6 +49,7 @@ class SearchCommand extends BaseCommand
 The search command searches for packages by its name
 The search command searches for packages by its name
 <info>php composer.phar search symfony composer</info>
 <info>php composer.phar search symfony composer</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#search
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 0
src/Composer/Command/SelfUpdateCommand.php

@@ -60,6 +60,7 @@ versions of composer and if found, installs the latest.
 
 
 <info>php composer.phar self-update</info>
 <info>php composer.phar self-update</info>
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate-
 EOT
 EOT
             )
             )
         ;
         ;

+ 7 - 6
src/Composer/Command/ShowCommand.php

@@ -85,6 +85,7 @@ class ShowCommand extends BaseCommand
 The show command displays detailed information about a package, or
 The show command displays detailed information about a package, or
 lists all packages available.
 lists all packages available.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#show
 EOT
 EOT
             )
             )
         ;
         ;
@@ -823,10 +824,10 @@ EOT
     /**
     /**
      * Display a package tree
      * Display a package tree
      *
      *
-     * @param PackageInterface|string $package
-     * @param array                   $packagesInTree
-     * @param string                  $previousTreeBar
-     * @param int                     $level
+     * @param array|string $package
+     * @param array        $packagesInTree
+     * @param string       $previousTreeBar
+     * @param int          $level
      */
      */
     protected function displayTree(
     protected function displayTree(
         $package,
         $package,
@@ -835,7 +836,7 @@ EOT
         $level = 1
         $level = 1
     ) {
     ) {
         $previousTreeBar = str_replace('├', '│', $previousTreeBar);
         $previousTreeBar = str_replace('├', '│', $previousTreeBar);
-        if (isset($package['requires'])) {
+        if (is_array($package) && isset($package['requires'])) {
             $requires = $package['requires'];
             $requires = $package['requires'];
             $treeBar = $previousTreeBar . '  ├';
             $treeBar = $previousTreeBar . '  ├';
             $i = 0;
             $i = 0;
@@ -968,7 +969,7 @@ EOT
      * @param string           $phpVersion
      * @param string           $phpVersion
      * @param bool             $minorOnly
      * @param bool             $minorOnly
      *
      *
-     * @return PackageInterface|null
+     * @return PackageInterface|false
      */
      */
     private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false)
     private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false)
     {
     {

+ 1 - 0
src/Composer/Command/StatusCommand.php

@@ -52,6 +52,7 @@ class StatusCommand extends BaseCommand
 The status command displays a list of dependencies that have
 The status command displays a list of dependencies that have
 been modified locally.
 been modified locally.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#status
 EOT
 EOT
             )
             )
         ;
         ;

+ 5 - 1
src/Composer/Command/SuggestsCommand.php

@@ -38,11 +38,15 @@ The <info>%command.name%</info> command shows a sorted list of suggested package
 
 
 Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists.
 Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#suggests
 EOT
 EOT
             )
             )
         ;
         ;
     }
     }
 
 
+    /**
+     * {@inheritDoc}
+     */
     protected function execute(InputInterface $input, OutputInterface $output)
     protected function execute(InputInterface $input, OutputInterface $output)
     {
     {
         $lock = $this->getComposer()->getLocker()->getLockData();
         $lock = $this->getComposer()->getLocker()->getLockData();
@@ -117,7 +121,7 @@ EOT
                 $io->write(sprintf('<info>%s</info>', $suggestion));
                 $io->write(sprintf('<info>%s</info>', $suggestion));
             }
             }
 
 
-            return;
+            return null;
         }
         }
 
 
         // Grouped by package
         // Grouped by package

+ 1 - 0
src/Composer/Command/UpdateCommand.php

@@ -81,6 +81,7 @@ from a specific vendor:
 
 
 To select packages names interactively with auto-completion use <info>-i</info>.
 To select packages names interactively with auto-completion use <info>-i</info>.
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#update-u
 EOT
 EOT
             )
             )
         ;
         ;

+ 1 - 0
src/Composer/Command/ValidateCommand.php

@@ -55,6 +55,7 @@ Exit codes in case of errors are:
 2 validation error(s)
 2 validation error(s)
 3 file unreadable or missing
 3 file unreadable or missing
 
 
+Read more at https://getcomposer.org/doc/03-cli.md#validate
 EOT
 EOT
             );
             );
     }
     }

+ 1 - 0
src/Composer/Compiler.php

@@ -194,6 +194,7 @@ class Compiler
             $content = str_replace('@package_version@', $this->version, $content);
             $content = str_replace('@package_version@', $this->version, $content);
             $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
             $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
             $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content);
             $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content);
+            $content = preg_replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
         }
         }
 
 
         $phar->addFromString($path, $content);
         $phar->addFromString($path, $content);

+ 37 - 1
src/Composer/Composer.php

@@ -29,10 +29,46 @@ use Composer\Package\Archiver\ArchiveManager;
  */
  */
 class Composer
 class Composer
 {
 {
+    /*
+     * Examples of the following constants in the various configurations they can be in
+     *
+     * releases (phar):
+     * const VERSION = '1.8.2';
+     * const BRANCH_ALIAS_VERSION = '';
+     * const RELEASE_DATE = '2019-01-29 15:00:53';
+     * const SOURCE_VERSION = '';
+     *
+     * snapshot builds (phar):
+     * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7';
+     * const BRANCH_ALIAS_VERSION = '1.9-dev';
+     * const RELEASE_DATE = '2019-02-20 07:43:56';
+     * const SOURCE_VERSION = '';
+     *
+     * source (git clone):
+     * const VERSION = '@package_version@';
+     * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
+     * const RELEASE_DATE = '@release_date@';
+     * const SOURCE_VERSION = '1.8-dev+source';
+     */
     const VERSION = '@package_version@';
     const VERSION = '@package_version@';
     const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
     const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
     const RELEASE_DATE = '@release_date@';
     const RELEASE_DATE = '@release_date@';
-    const SOURCE_VERSION = '2.0-source';
+    const SOURCE_VERSION = '2.0-dev+source';
+
+    public static function getVersion()
+    {
+        // no replacement done, this must be a source checkout
+        if (self::VERSION === '@package_version'.'@') {
+            return self::SOURCE_VERSION;
+        }
+
+        // we have a branch alias and version is a commit id, this must be a snapshot build
+        if (self::BRANCH_ALIAS_VERSION !== '' && preg_match('{^[a-f0-9]{40}$}', self::VERSION)) {
+            return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION;
+        }
+
+        return self::VERSION;
+    }
 
 
     /**
     /**
      * @var Package\RootPackageInterface
      * @var Package\RootPackageInterface

+ 17 - 0
src/Composer/Config.php

@@ -16,6 +16,7 @@ use Composer\Config\ConfigSourceInterface;
 use Composer\Downloader\TransportException;
 use Composer\Downloader\TransportException;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
 use Composer\Util\Platform;
 use Composer\Util\Platform;
+use Composer\Util\ProcessExecutor;
 
 
 /**
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  * @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);
+    }
 }
 }

+ 1 - 1
src/Composer/Config/ConfigSourceInterface.php

@@ -39,7 +39,7 @@ interface ConfigSourceInterface
      * Add a config setting
      * Add a config setting
      *
      *
      * @param string $name  Name
      * @param string $name  Name
-     * @param string $value Value
+     * @param string|array $value Value
      */
      */
     public function addConfigSetting($name, $value);
     public function addConfigSetting($name, $value);
 
 

+ 1 - 1
src/Composer/Config/JsonConfigSource.php

@@ -259,7 +259,7 @@ class JsonConfigSource implements ConfigSourceInterface
      *
      *
      * @param  array $array
      * @param  array $array
      * @param  mixed $value
      * @param  mixed $value
-     * @return array
+     * @return int
      */
      */
     private function arrayUnshiftRef(&$array, &$value)
     private function arrayUnshiftRef(&$array, &$value)
     {
     {

+ 4 - 4
src/Composer/Console/Application.php

@@ -91,7 +91,7 @@ class Application extends BaseApplication
 
 
         $this->io = new NullIO();
         $this->io = new NullIO();
 
 
-        parent::__construct('Composer', Composer::VERSION);
+        parent::__construct('Composer', Composer::getVersion());
     }
     }
 
 
     /**
     /**
@@ -190,7 +190,7 @@ class Application extends BaseApplication
         if (!$isProxyCommand) {
         if (!$isProxyCommand) {
             $io->writeError(sprintf(
             $io->writeError(sprintf(
                 'Running %s (%s) with %s on %s',
                 'Running %s (%s) with %s on %s',
-                Composer::VERSION,
+                Composer::getVersion(),
                 Composer::RELEASE_DATE,
                 Composer::RELEASE_DATE,
                 defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION,
                 defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION,
                 function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS'
                 function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS'
@@ -284,7 +284,7 @@ class Application extends BaseApplication
 
 
             return $result;
             return $result;
         } catch (ScriptExecutionException $e) {
         } catch (ScriptExecutionException $e) {
-            return $e->getCode();
+            return (int) $e->getCode();
         } catch (\Exception $e) {
         } catch (\Exception $e) {
             $this->hintCommonErrors($e);
             $this->hintCommonErrors($e);
             restore_error_handler();
             restore_error_handler();
@@ -440,7 +440,7 @@ class Application extends BaseApplication
      */
      */
     public function getLongVersion()
     public function getLongVersion()
     {
     {
-        if (Composer::BRANCH_ALIAS_VERSION) {
+        if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') {
             return sprintf(
             return sprintf(
                 '<info>%s</info> version <comment>%s (%s)</comment> %s',
                 '<info>%s</info> version <comment>%s (%s)</comment> %s',
                 $this->getName(),
                 $this->getName(),

+ 4 - 4
src/Composer/DependencyResolver/GenericRule.php

@@ -23,10 +23,10 @@ class GenericRule extends Rule
     protected $literals;
     protected $literals;
 
 
     /**
     /**
-     * @param array                 $literals
-     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
-     * @param Link|PackageInterface $reasonData
-     * @param array                 $job        The job this rule was created from
+     * @param array                          $literals
+     * @param int|null                       $reason     A RULE_* constant describing the reason for generating this rule
+     * @param Link|PackageInterface|int|null $reasonData
+     * @param array                          $job        The job this rule was created from
      */
      */
     public function __construct(array $literals, $reason, $reasonData, $job = null)
     public function __construct(array $literals, $reason, $reasonData, $job = null)
     {
     {

+ 6 - 6
src/Composer/DependencyResolver/PoolBuilder.php

@@ -48,7 +48,7 @@ class PoolBuilder
 
 
     public function buildPool(array $repositories, array $rootAliases, Request $request)
     public function buildPool(array $repositories, array $rootAliases, Request $request)
     {
     {
-        $this->pool = new Pool($this->filterRequires);
+        $pool = new Pool($this->filterRequires);
         $this->rootAliases = $rootAliases;
         $this->rootAliases = $rootAliases;
 
 
         // TODO do we really want the request here? kind of want a root requirements thingy instead
         // TODO do we really want the request here? kind of want a root requirements thingy instead
@@ -137,13 +137,13 @@ class PoolBuilder
             }
             }
         }
         }
 
 
-        $this->pool->setPackages($this->packages, $this->priorities);
+        $pool->setPackages($this->packages, $this->priorities);
 
 
         unset($this->aliasMap);
         unset($this->aliasMap);
         unset($this->loadedNames);
         unset($this->loadedNames);
         unset($this->nameConstraints);
         unset($this->nameConstraints);
 
 
-        return $this->pool;
+        return $pool;
     }
     }
 
 
     private function loadPackage(PackageInterface $package, $repoIndex)
     private function loadPackage(PackageInterface $package, $repoIndex)
@@ -180,12 +180,12 @@ class PoolBuilder
             if (!isset($this->loadedNames[$require])) {
             if (!isset($this->loadedNames[$require])) {
                 $loadNames[$require] = null;
                 $loadNames[$require] = null;
             }
             }
-            if ($link->getConstraint()) {
+            if ($linkConstraint = $link->getConstraint()) {
                 if (!array_key_exists($require, $this->nameConstraints)) {
                 if (!array_key_exists($require, $this->nameConstraints)) {
-                    $this->nameConstraints[$require] = new MultiConstraint(array($link->getConstraint()), false);
+                    $this->nameConstraints[$require] = new MultiConstraint(array($linkConstraint), false);
                 } elseif ($this->nameConstraints[$require]) {
                 } elseif ($this->nameConstraints[$require]) {
                     // TODO addConstraint function?
                     // TODO addConstraint function?
-                    $this->nameConstraints[$require] = new MultiConstraint(array_merge(array($link->getConstraint()), $this->nameConstraints[$require]->getConstraints()), false);
+                    $this->nameConstraints[$require] = new MultiConstraint(array_merge(array($linkConstraint), $this->nameConstraints[$require]->getConstraints()), false);
                 }
                 }
             } else {
             } else {
                 $this->nameConstraints[$require] = null;
                 $this->nameConstraints[$require] = null;

+ 38 - 34
src/Composer/DependencyResolver/Problem.php

@@ -79,11 +79,13 @@ class Problem
             reset($reasons);
             reset($reasons);
             $reason = current($reasons);
             $reason = current($reasons);
 
 
-            $rule = $reason['rule'];
             $job = $reason['job'];
             $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 {
             } else {
                 $packages = array();
                 $packages = array();
             }
             }
@@ -91,9 +93,9 @@ class Problem
             if ($job && $job['cmd'] === 'install' && empty($packages)) {
             if ($job && $job['cmd'] === 'install' && empty($packages)) {
 
 
                 // handle php/hhvm
                 // handle php/hhvm
-                if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') {
+                if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
                     $version = phpversion();
                     $version = phpversion();
-                    $available = $this->pool->whatProvides($job['packageName']);
+                    $available = $this->pool->whatProvides($packageName);
 
 
                     if (count($available)) {
                     if (count($available)) {
                         $firstAvailable = reset($available);
                         $firstAvailable = reset($available);
@@ -104,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.';
                         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.';
                         return $msg . 'you are running this with PHP and not HHVM.';
                     }
                     }
 
 
@@ -118,43 +120,43 @@ class Problem
                 }
                 }
 
 
                 // handle php extensions
                 // 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';
                     $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
                 // 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.';
                         $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.';
             }
             }
         }
         }
 
 
@@ -180,7 +182,7 @@ class Problem
      * Store a reason descriptor but ignore duplicates
      * Store a reason descriptor but ignore duplicates
      *
      *
      * @param string $id     A canonical identifier for the reason
      * @param string $id     A canonical identifier for the reason
-     * @param string $reason The reason descriptor
+     * @param string|array $reason The reason descriptor
      */
      */
     protected function addReason($id, $reason)
     protected function addReason($id, $reason)
     {
     {
@@ -203,27 +205,29 @@ class Problem
      */
      */
     protected function jobToText($job)
     protected function jobToText($job)
     {
     {
+        $packageName = $job['packageName'];
+        $constraint = $job['constraint'];
         switch ($job['cmd']) {
         switch ($job['cmd']) {
             case 'install':
             case 'install':
-                $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
+                $packages = $this->pool->whatProvides($packageName, $constraint);
                 if (!$packages) {
                 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':
             case 'update':
-                return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.';
+                return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
             case 'remove':
             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 {
         } else {
             $packages = $this->pool->whatProvides($job['packageName'], null);
             $packages = $this->pool->whatProvides($job['packageName'], null);
         }
         }
 
 
-        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)
     protected function getPackageList($packages)

+ 3 - 2
src/Composer/DependencyResolver/Solver.php

@@ -13,6 +13,7 @@
 namespace Composer\DependencyResolver;
 namespace Composer\DependencyResolver;
 
 
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
+use Composer\Package\PackageInterface;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\RepositorySet;
 use Composer\Repository\RepositorySet;
@@ -39,7 +40,7 @@ class Solver
     protected $watchGraph;
     protected $watchGraph;
     /** @var Decisions */
     /** @var Decisions */
     protected $decisions;
     protected $decisions;
-    /** @var Package[] */
+    /** @var PackageInterface[] */
     protected $fixedMap;
     protected $fixedMap;
 
 
     /** @var int */
     /** @var int */
@@ -659,7 +660,7 @@ class Solver
         /**
         /**
          * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
          * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
          */
          */
-        $disableRules = array();
+        $disableRules = false;
 
 
         $level = 1;
         $level = 1;
         $systemLevel = $level + 1;
         $systemLevel = $level + 1;

+ 1 - 3
src/Composer/Downloader/DownloadManager.php

@@ -294,9 +294,7 @@ class DownloadManager
 
 
         // if downloader type changed, or update failed and user asks for reinstall,
         // if downloader type changed, or update failed and user asks for reinstall,
         // we wipe the dir and do a new install instead of updating it
         // we wipe the dir and do a new install instead of updating it
-        if ($initialDownloader) {
-            $initialDownloader->remove($initial, $targetDir);
-        }
+        $initialDownloader->remove($initial, $targetDir);
         $this->install($target, $targetDir);
         $this->install($target, $targetDir);
     }
     }
 
 

+ 3 - 3
src/Composer/Downloader/GitDownloader.php

@@ -362,7 +362,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
         ) {
         ) {
             $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
             $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
             if (0 === $this->process->execute($command, $output, $path)) {
             if (0 === $this->process->execute($command, $output, $path)) {
-                return;
+                return null;
             }
             }
         }
         }
 
 
@@ -380,14 +380,14 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
             ) {
             ) {
                 $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
                 $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
                 if (0 === $this->process->execute($command, $output, $path)) {
                 if (0 === $this->process->execute($command, $output, $path)) {
-                    return;
+                    return null;
                 }
                 }
             }
             }
         }
         }
 
 
         $command = sprintf($template, ProcessExecutor::escape($gitRef));
         $command = sprintf($template, ProcessExecutor::escape($gitRef));
         if (0 === $this->process->execute($command, $output, $path)) {
         if (0 === $this->process->execute($command, $output, $path)) {
-            return;
+            return null;
         }
         }
 
 
         // reference was not found (prints "fatal: reference is not a tree: $ref")
         // reference was not found (prints "fatal: reference is not a tree: $ref")

+ 1 - 0
src/Composer/Downloader/GzipDownloader.php

@@ -28,6 +28,7 @@ use Composer\IO\IOInterface;
  */
  */
 class GzipDownloader extends ArchiveDownloader
 class GzipDownloader extends ArchiveDownloader
 {
 {
+    /** @var ProcessExecutor */
     protected $process;
     protected $process;
 
 
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)

+ 27 - 0
src/Composer/Downloader/PathDownloader.php

@@ -91,6 +91,12 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
             $allowedStrategies = array(self::STRATEGY_MIRROR);
             $allowedStrategies = array(self::STRATEGY_MIRROR);
         }
         }
 
 
+        // Check we can use junctions safely if we are on Windows
+        if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) {
+            $currentStrategy = self::STRATEGY_MIRROR;
+            $allowedStrategies = array(self::STRATEGY_MIRROR);
+        }
+
         $fileSystem = new Filesystem();
         $fileSystem = new Filesystem();
         $this->filesystem->removeDirectory($path);
         $this->filesystem->removeDirectory($path);
 
 
@@ -181,4 +187,25 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
             return $packageVersion['commit'];
             return $packageVersion['commit'];
         }
         }
     }
     }
+
+    /**
+     * Returns true if junctions can be created and safely used on Windows
+     *
+     * A PHP bug makes junction detection fragile, leading to possible data loss
+     * when removing a package. See https://bugs.php.net/bug.php?id=77552
+     *
+     * For safety we require a minimum version of Windows 7, so we can call the
+     * system rmdir which will preserve target content if given a junction.
+     *
+     * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7).
+     *
+     * @return bool
+     */
+    private function safeJunctions()
+    {
+        // We need to call mklink, and rmdir on Windows 7 (version 6.1)
+        return function_exists('proc_open') &&
+            (PHP_WINDOWS_VERSION_MAJOR > 6 ||
+            (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1));
+    }
 }
 }

+ 2 - 2
src/Composer/Downloader/PerforceDownloader.php

@@ -78,7 +78,7 @@ class PerforceDownloader extends VcsDownloader
      */
      */
     public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
     public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
     {
     {
-        $this->doDownload($target, $path, $url);
+        $this->doInstall($target, $path, $url);
     }
     }
 
 
     /**
     /**
@@ -88,7 +88,7 @@ class PerforceDownloader extends VcsDownloader
     {
     {
         $this->io->writeError('Perforce driver does not check for local changes before overriding', true);
         $this->io->writeError('Perforce driver does not check for local changes before overriding', true);
 
 
-        return;
+        return null;
     }
     }
 
 
     /**
     /**

+ 1 - 0
src/Composer/Downloader/RarDownloader.php

@@ -32,6 +32,7 @@ use RarArchive;
  */
  */
 class RarDownloader extends ArchiveDownloader
 class RarDownloader extends ArchiveDownloader
 {
 {
+    /** @var ProcessExecutor */
     protected $process;
     protected $process;
 
 
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)

+ 1 - 0
src/Composer/Downloader/XzDownloader.php

@@ -28,6 +28,7 @@ use Composer\IO\IOInterface;
  */
  */
 class XzDownloader extends ArchiveDownloader
 class XzDownloader extends ArchiveDownloader
 {
 {
+    /** @var ProcessExecutor */
     protected $process;
     protected $process;
 
 
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)

+ 2 - 0
src/Composer/Downloader/ZipDownloader.php

@@ -33,7 +33,9 @@ class ZipDownloader extends ArchiveDownloader
     private static $hasZipArchive;
     private static $hasZipArchive;
     private static $isWindows;
     private static $isWindows;
 
 
+    /** @var ProcessExecutor */
     protected $process;
     protected $process;
+    /** @var ZipArchive|null */
     private $zipArchiveObject;
     private $zipArchiveObject;
 
 
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
     public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)

+ 22 - 2
src/Composer/EventDispatcher/EventDispatcher.php

@@ -47,7 +47,7 @@ class EventDispatcher
     protected $io;
     protected $io;
     protected $loader;
     protected $loader;
     protected $process;
     protected $process;
-    protected $listeners;
+    protected $listeners = array();
     private $eventStack;
     private $eventStack;
 
 
     /**
     /**
@@ -173,6 +173,9 @@ class EventDispatcher
 
 
                     throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
                     throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
                 }
                 }
+                if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
+                    $this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1] ), true, IOInterface::VERBOSE);
+                }
                 $event = $this->checkListenerExpectedEvent($callable, $event);
                 $event = $this->checkListenerExpectedEvent($callable, $event);
                 $return = false === call_user_func($callable, $event) ? 1 : 0;
                 $return = false === call_user_func($callable, $event) ? 1 : 0;
             } elseif ($this->isComposerScript($callable)) {
             } elseif ($this->isComposerScript($callable)) {
@@ -197,6 +200,7 @@ class EventDispatcher
                     }
                     }
 
 
                     try {
                     try {
+                        /** @var InstallerEvent $event */
                         $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags));
                         $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags));
                     } catch (ScriptExecutionException $e) {
                     } catch (ScriptExecutionException $e) {
                         $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
                         $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
@@ -365,6 +369,22 @@ class EventDispatcher
         $this->listeners[$eventName][$priority][] = $listener;
         $this->listeners[$eventName][$priority][] = $listener;
     }
     }
 
 
+    /**
+     * @param callable|object $listener A callable or an object instance for which all listeners should be removed
+     */
+    public function removeListener($listener)
+    {
+        foreach ($this->listeners as $eventName => $priorities) {
+            foreach ($priorities as $priority => $listeners) {
+                foreach ($listeners as $index => $candidate) {
+                    if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) {
+                        unset($this->listeners[$eventName][$priority][$index]);
+                    }
+                }
+            }
+        }
+    }
+
     /**
     /**
      * Adds object methods as listeners for the events in getSubscribedEvents
      * Adds object methods as listeners for the events in getSubscribedEvents
      *
      *
@@ -481,7 +501,7 @@ class EventDispatcher
      *
      *
      * @param  Event             $event
      * @param  Event             $event
      * @throws \RuntimeException
      * @throws \RuntimeException
-     * @return number
+     * @return int
      */
      */
     protected function pushEvent(Event $event)
     protected function pushEvent(Event $event)
     {
     {

+ 1 - 1
src/Composer/Factory.php

@@ -413,7 +413,7 @@ class Factory
     /**
     /**
      * @param  IOInterface $io             IO instance
      * @param  IOInterface $io             IO instance
      * @param  bool        $disablePlugins Whether plugins should not be loaded
      * @param  bool        $disablePlugins Whether plugins should not be loaded
-     * @return Composer
+     * @return Composer|null
      */
      */
     public static function createGlobal(IOInterface $io, $disablePlugins = false)
     public static function createGlobal(IOInterface $io, $disablePlugins = false)
     {
     {

+ 1 - 2
src/Composer/IO/BaseIO.php

@@ -14,10 +14,9 @@ namespace Composer\IO;
 
 
 use Composer\Config;
 use Composer\Config;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\ProcessExecutor;
-use Psr\Log\LoggerInterface;
 use Psr\Log\LogLevel;
 use Psr\Log\LogLevel;
 
 
-abstract class BaseIO implements IOInterface, LoggerInterface
+abstract class BaseIO implements IOInterface
 {
 {
     protected $authentications = array();
     protected $authentications = array();
 
 

+ 5 - 4
src/Composer/IO/IOInterface.php

@@ -13,13 +13,14 @@
 namespace Composer\IO;
 namespace Composer\IO;
 
 
 use Composer\Config;
 use Composer\Config;
+use Psr\Log\LoggerInterface;
 
 
 /**
 /**
  * The Input/Output helper interface.
  * The Input/Output helper interface.
  *
  *
  * @author François Pluchino <francois.pluchino@opendisplay.com>
  * @author François Pluchino <francois.pluchino@opendisplay.com>
  */
  */
-interface IOInterface
+interface IOInterface extends LoggerInterface
 {
 {
     const QUIET = 1;
     const QUIET = 1;
     const NORMAL = 2;
     const NORMAL = 2;
@@ -107,7 +108,7 @@ interface IOInterface
      * @param string $default  The default answer if none is given by the user
      * @param string $default  The default answer if none is given by the user
      *
      *
      * @throws \RuntimeException If there is no data to read in the input stream
      * @throws \RuntimeException If there is no data to read in the input stream
-     * @return string            The user answer
+     * @return string|null       The user answer
      */
      */
     public function ask($question, $default = null);
     public function ask($question, $default = null);
 
 
@@ -145,7 +146,7 @@ interface IOInterface
      *
      *
      * @param string $question The question to ask
      * @param string $question The question to ask
      *
      *
-     * @return string The answer
+     * @return string|null The answer
      */
      */
     public function askAndHideAnswer($question);
     public function askAndHideAnswer($question);
 
 
@@ -160,7 +161,7 @@ interface IOInterface
      * @param bool        $multiselect  Select more than one value separated by comma
      * @param bool        $multiselect  Select more than one value separated by comma
      *
      *
      * @throws \InvalidArgumentException
      * @throws \InvalidArgumentException
-     * @return int|string|array          The selected value or values (the key of the choices array)
+     * @return int|string|array|bool    The selected value or values (the key of the choices array)
      */
      */
     public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false);
     public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false);
 
 

+ 4 - 1
src/Composer/Installer.php

@@ -399,6 +399,8 @@ class Installer
         $solver = new Solver($policy, $pool, $this->io);
         $solver = new Solver($policy, $pool, $this->io);
         try {
         try {
             $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
             $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
+            $ruleSetSize = $solver->getRuleSetSize();
+            $solver = null;
         } catch (SolverProblemsException $e) {
         } 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('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
             $this->io->writeError($e->getMessage());
             $this->io->writeError($e->getMessage());
@@ -413,7 +415,7 @@ class Installer
         //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $lockedRepository, $request, $lockTransaction);
         //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $lockedRepository, $request, $lockTransaction);
 
 
         $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
         $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);
 
 
         if (!$lockTransaction->getOperations()) {
         if (!$lockTransaction->getOperations()) {
             $this->io->writeError('Nothing to modify in lock file');
             $this->io->writeError('Nothing to modify in lock file');
@@ -559,6 +561,7 @@ class Installer
             $solver = new Solver($policy, $pool, $this->io);
             $solver = new Solver($policy, $pool, $this->io);
             try {
             try {
                 $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
                 $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
+                $solver = null;
 
 
                 // installing the locked packages on this platfom resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system
                 // installing the locked packages on this platfom resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system
                 if (0 !== count($lockTransaction->getOperations())) {
                 if (0 !== count($lockTransaction->getOperations())) {

+ 1 - 1
src/Composer/Installer/InstallerInterface.php

@@ -48,7 +48,7 @@ interface InstallerInterface
      *
      *
      * @param  PackageInterface $package     package instance
      * @param  PackageInterface $package     package instance
      * @param  PackageInterface $prevPackage previous package instance in case of an update
      * @param  PackageInterface $prevPackage previous package instance in case of an update
-     * @return PromiseInterface
+     * @return PromiseInterface|null
      */
      */
     public function download(PackageInterface $package, PackageInterface $prevPackage = null);
     public function download(PackageInterface $package, PackageInterface $prevPackage = null);
 
 

+ 1 - 1
src/Composer/Installer/LibraryInstaller.php

@@ -43,7 +43,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
      *
      *
      * @param IOInterface     $io
      * @param IOInterface     $io
      * @param Composer        $composer
      * @param Composer        $composer
-     * @param string          $type
+     * @param string|null     $type
      * @param Filesystem      $filesystem
      * @param Filesystem      $filesystem
      * @param BinaryInstaller $binaryInstaller
      * @param BinaryInstaller $binaryInstaller
      */
      */

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

@@ -22,7 +22,7 @@ class JsonManipulator
     private static $DEFINES = '(?(DEFINE)
     private static $DEFINES = '(?(DEFINE)
        (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
        (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
        (?<boolean>   true | false | null )
        (?<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* \] )
        (?<array>     \[  (?:  (?&json) \s* (?: , (?&json) \s* )*  )?  \s* \] )
        (?<pair>      \s* (?&string) \s* : (?&json) \s* )
        (?<pair>      \s* (?&string) \s* : (?&json) \s* )
        (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \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().')';
         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);
+    }
 }
 }

+ 1 - 1
src/Composer/Package/BasePackage.php

@@ -239,7 +239,7 @@ abstract class BasePackage implements PackageInterface
      * Build a regexp from a package name, expanding * globs as required
      * Build a regexp from a package name, expanding * globs as required
      *
      *
      * @param  string $whiteListedPattern
      * @param  string $whiteListedPattern
-     * @param  bool $wrap Wrap the cleaned string by the given string
+     * @param  string $wrap Wrap the cleaned string by the given string
      * @return string
      * @return string
      */
      */
     public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i')
     public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i')

+ 4 - 27
src/Composer/Package/Loader/ArrayLoader.php

@@ -62,39 +62,16 @@ class ArrayLoader implements LoaderInterface
 
 
     public function loadPackages(array $versions, $class)
     public function loadPackages(array $versions, $class)
     {
     {
-        static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
-
         $packages = array();
         $packages = array();
         $linkCache = array();
         $linkCache = array();
 
 
         foreach ($versions as $version) {
         foreach ($versions as $version) {
-            if (isset($version['versions'])) {
-                $baseVersion = $version;
-                foreach ($uniqKeys as $key) {
-                    unset($baseVersion[$key.'s']);
-                }
-
-                foreach ($version['versions'] as $index => $dummy) {
-                    $unpackedVersion = $baseVersion;
-                    foreach ($uniqKeys as $key) {
-                        $unpackedVersion[$key] = $version[$key.'s'][$index];
-                    }
-
-                    $package = $this->createObject($unpackedVersion, $class);
+            $package = $this->createObject($version, $class);
 
 
-                    $this->configureCachedLinks($linkCache, $package, $unpackedVersion);
-                    $package = $this->configureObject($package, $unpackedVersion);
+            $this->configureCachedLinks($linkCache, $package, $version);
+            $package = $this->configureObject($package, $version);
 
 
-                    $packages[] = $package;
-                }
-            } else {
-                $package = $this->createObject($version, $class);
-
-                $this->configureCachedLinks($linkCache, $package, $version);
-                $package = $this->configureObject($package, $version);
-
-                $packages[] = $package;
-            }
+            $packages[] = $package;
         }
         }
 
 
         return $packages;
         return $packages;

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

@@ -49,6 +49,10 @@ class ValidatingArrayLoader implements LoaderInterface
         $this->warnings = array();
         $this->warnings = array();
         $this->config = $config;
         $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) {
         if ($this->strictName) {
             $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true);
             $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true);
         } else {
         } else {
@@ -195,7 +199,9 @@ class ValidatingArrayLoader implements LoaderInterface
         foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
         foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
             if ($this->validateArray($linkType) && isset($this->config[$linkType])) {
             if ($this->validateArray($linkType) && isset($this->config[$linkType])) {
                 foreach ($this->config[$linkType] as $package => $constraint) {
                 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_./-]';
                         $this->warnings[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]';
                     }
                     }
                     if (!is_string($constraint)) {
                     if (!is_string($constraint)) {

+ 8 - 0
src/Composer/Package/Locker.php

@@ -31,13 +31,21 @@ use Seld\JsonLint\ParsingException;
  */
  */
 class Locker
 class Locker
 {
 {
+    /** @var JsonFile */
     private $lockFile;
     private $lockFile;
+    /** @var RepositoryManager */
     private $repositoryManager;
     private $repositoryManager;
+    /** @var InstallationManager */
     private $installationManager;
     private $installationManager;
+    /** @var string */
     private $hash;
     private $hash;
+    /** @var string */
     private $contentHash;
     private $contentHash;
+    /** @var ArrayLoader */
     private $loader;
     private $loader;
+    /** @var ArrayDumper */
     private $dumper;
     private $dumper;
+    /** @var ProcessExecutor */
     private $process;
     private $process;
     private $lockDataCache;
     private $lockDataCache;
     private $virtualFileWritten;
     private $virtualFileWritten;

+ 29 - 1
src/Composer/Package/PackageInterface.php

@@ -76,7 +76,7 @@ interface PackageInterface
     /**
     /**
      * Returns the package targetDir property
      * Returns the package targetDir property
      *
      *
-     * @return string The package targetDir
+     * @return string|null The package targetDir
      */
      */
     public function getTargetDir();
     public function getTargetDir();
 
 
@@ -358,4 +358,32 @@ interface PackageInterface
      * @return array
      * @return array
      */
      */
     public function getTransportOptions();
     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);
 }
 }

+ 1 - 0
src/Composer/Package/Version/VersionGuesser.php

@@ -17,6 +17,7 @@ use Composer\Repository\Vcs\HgDriver;
 use Composer\IO\NullIO;
 use Composer\IO\NullIO;
 use Composer\Semver\VersionParser as SemverVersionParser;
 use Composer\Semver\VersionParser as SemverVersionParser;
 use Composer\Util\Git as GitUtil;
 use Composer\Util\Git as GitUtil;
+use Composer\Util\HttpDownloader;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\Svn as SvnUtil;
 use Composer\Util\Svn as SvnUtil;
 
 

+ 1 - 1
src/Composer/Package/Version/VersionSelector.php

@@ -45,7 +45,7 @@ class VersionSelector
      * @param  string                $targetPackageVersion
      * @param  string                $targetPackageVersion
      * @param  string                $targetPhpVersion
      * @param  string                $targetPhpVersion
      * @param  string                $preferredStability
      * @param  string                $preferredStability
-     * @return PackageInterface|bool
+     * @return PackageInterface|false
      */
      */
     public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable')
     public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable')
     {
     {

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

@@ -15,15 +15,16 @@ namespace Composer\Plugin;
 use Composer\Composer;
 use Composer\Composer;
 use Composer\EventDispatcher\EventSubscriberInterface;
 use Composer\EventDispatcher\EventSubscriberInterface;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
+use Composer\Package\CompletePackage;
 use Composer\Package\Package;
 use Composer\Package\Package;
 use Composer\Package\Version\VersionParser;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositoryInterface;
-use Composer\Package\AliasPackage;
 use Composer\Package\PackageInterface;
 use Composer\Package\PackageInterface;
 use Composer\Package\Link;
 use Composer\Package\Link;
 use Composer\Repository\RepositorySet;
 use Composer\Repository\RepositorySet;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Plugin\Capability\Capability;
 use Composer\Plugin\Capability\Capability;
+use Composer\Util\PackageSorter;
 
 
 /**
 /**
  * Plugin manager
  * Plugin manager
@@ -253,8 +254,10 @@ class PluginManager
      */
      */
     private function loadRepository(RepositoryInterface $repo)
     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;
                 continue;
             }
             }
             if ('composer-plugin' === $package->getType()) {
             if ('composer-plugin' === $package->getType()) {

+ 5 - 3
src/Composer/Repository/BaseRepository.php

@@ -32,9 +32,11 @@ abstract class BaseRepository implements RepositoryInterface
 
 
         $result = array();
         $result = array();
         foreach ($packages as $package) {
         foreach ($packages as $package) {
-            if (array_key_exists($package->getName(), $packageMap) &&
-                (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) &&
-                call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability())) {
+            if (
+                array_key_exists($package->getName(), $packageMap)
+                && (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion())))
+                && call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability())
+            ) {
                 $result[spl_object_hash($package)] = $package;
                 $result[spl_object_hash($package)] = $package;
                 if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) {
                 if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) {
                     $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                     $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();

+ 62 - 63
src/Composer/Repository/ComposerRepository.php

@@ -19,6 +19,7 @@ use Composer\Package\Version\VersionParser;
 use Composer\Json\JsonFile;
 use Composer\Json\JsonFile;
 use Composer\Cache;
 use Composer\Cache;
 use Composer\Config;
 use Composer\Config;
+use Composer\Composer;
 use Composer\Factory;
 use Composer\Factory;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
 use Composer\Util\HttpDownloader;
 use Composer\Util\HttpDownloader;
@@ -103,7 +104,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
 
 
         $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
         $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
         $this->io = $io;
         $this->io = $io;
-        $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
+        $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$~');
         $this->versionParser = new VersionParser();
         $this->versionParser = new VersionParser();
         $this->loader = new ArrayLoader($this->versionParser);
         $this->loader = new ArrayLoader($this->versionParser);
         $this->httpDownloader = $httpDownloader;
         $this->httpDownloader = $httpDownloader;
@@ -139,9 +140,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 return;
                 return;
             }
             }
 
 
-            $packages = $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) {
-                return true;
-            });
+            $packages = $this->loadAsyncPackages(array($name => $constraint));
 
 
             return reset($packages);
             return reset($packages);
         }
         }
@@ -181,9 +180,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 return array();
                 return array();
             }
             }
 
 
-            return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) {
-                return true;
-            });
+            return $this->loadAsyncPackages(array($name => $constraint));
         }
         }
 
 
         if ($hasProviders) {
         if ($hasProviders) {
@@ -241,7 +238,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                     $packageMap[$name] = new EmptyConstraint();
                     $packageMap[$name] = new EmptyConstraint();
                 }
                 }
 
 
-                return array_values($this->loadAsyncPackages($packageMap, function ($name, $stability) { return true; }));
+                return array_values($this->loadAsyncPackages($packageMap));
             }
             }
 
 
             throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getProviderNames instead.');
             throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getProviderNames instead.');
@@ -313,9 +310,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                         }
                         }
                     }
                     }
                 }
                 }
+
+                // add aliases of matched packages even if they did not match the constraint
                 foreach ($candidates as $candidate) {
                 foreach ($candidates as $candidate) {
                     if ($candidate instanceof AliasPackage) {
                     if ($candidate instanceof AliasPackage) {
-                        if (isset($result[spl_object_hash($candidate->getAliasOf())])) {
+                        if (isset($matches[spl_object_hash($candidate->getAliasOf())])) {
                             $matches[spl_object_hash($candidate)] = $candidate;
                             $matches[spl_object_hash($candidate)] = $candidate;
                         }
                         }
                     }
                     }
@@ -511,11 +510,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 }
                 }
 
 
                 if (!isset($versionsToLoad[$version['uid']])) {
                 if (!isset($versionsToLoad[$version['uid']])) {
-                    if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, $normalizedName, VersionParser::parseStability($version['version']))) {
-                        continue;
+                    if (!isset($version['version_normalized'])) {
+                        $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                     }
                     }
 
 
-                    $versionsToLoad[$version['uid']] = $version;
+                    if ($this->isVersionAcceptable($isPackageAcceptableCallable, null, $normalizedName, $version)) {
+                        $versionsToLoad[$version['uid']] = $version;
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -567,7 +568,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
         $this->configurePackageTransportOptions($package);
         $this->configurePackageTransportOptions($package);
     }
     }
 
 
-    private function loadAsyncPackages(array $packageNames, $isPackageAcceptableCallable)
+    /**
+     * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only packages matching it will be loaded
+     */
+    private function loadAsyncPackages(array $packageNames, $isPackageAcceptableCallable = null)
     {
     {
         $this->loadRootServerFile();
         $this->loadRootServerFile();
 
 
@@ -579,16 +583,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
             throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url');
             throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url');
         }
         }
 
 
+        // load ~dev variants as well if present
+        // TODO ideally there should be a flag set from the repositoryset/poolbuilder to know which packages should have the dev packages loaded
+        // so we can optimize away some requests entirely
+        foreach ($packageNames as $name => $constraint) {
+            $packageNames[$name.'~dev'] = $constraint;
+        }
+
         foreach ($packageNames as $name => $constraint) {
         foreach ($packageNames as $name => $constraint) {
             $name = strtolower($name);
             $name = strtolower($name);
 
 
+            $realName = preg_replace('{~dev$}', '', $name);
             // skip platform packages, root package and composer-plugin-api
             // skip platform packages, root package and composer-plugin-api
-            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) {
+            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $realName) || '__root__' === $realName || 'composer-plugin-api' === $realName) {
                 continue;
                 continue;
             }
             }
 
 
             $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
             $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
-            $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
+            $cacheKey = 'provider-'.strtr($name, '/', '~').'.json';
 
 
             $lastModified = null;
             $lastModified = null;
             if ($contents = $this->cache->read($cacheKey)) {
             if ($contents = $this->cache->read($cacheKey)) {
@@ -597,16 +609,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
             }
             }
 
 
             $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified)
             $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified)
-                ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) {
+                ->then(function ($response) use (&$packages, $contents, $realName, $constraint, $repo, $isPackageAcceptableCallable) {
                     if (true === $response) {
                     if (true === $response) {
                         $response = $contents;
                         $response = $contents;
                     }
                     }
 
 
-                    if (!isset($response['packages'][$name])) {
+                    if (!isset($response['packages'][$realName])) {
                         return;
                         return;
                     }
                     }
 
 
-                    $versions = $response['packages'][$name];
+                    $versions = $response['packages'][$realName];
 
 
                     if (isset($response['minified']) && $response['minified'] === 'composer/2.0') {
                     if (isset($response['minified']) && $response['minified'] === 'composer/2.0') {
                         // TODO extract in other method
                         // TODO extract in other method
@@ -635,37 +647,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                         unset($expanded, $expandedVersion, $versionData);
                         unset($expanded, $expandedVersion, $versionData);
                     }
                     }
 
 
-                    static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
                     $versionsToLoad = array();
                     $versionsToLoad = array();
                     foreach ($versions as $version) {
                     foreach ($versions as $version) {
-                        if (isset($version['version_normalizeds'])) {
-                            foreach ($version['version_normalizeds'] as $index => $normalizedVersion) {
-                                if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) {
-                                    foreach ($uniqKeys as $key) {
-                                        unset($version[$key.'s'][$index]);
-                                    }
-                                }
-                            }
-                            if (count($version['version_normalizeds'])) {
-                                $versionsToLoad[] = $version;
-                            }
-                        } else {
-                            if (!isset($version['version_normalized'])) {
-                                $version['version_normalized'] = $repo->versionParser->normalize($version['version']);
-                            }
+                        if (!isset($version['version_normalized'])) {
+                            $version['version_normalized'] = $repo->versionParser->normalize($version['version']);
+                        }
 
 
-                            if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) {
-                                $versionsToLoad[] = $version;
-                            }
+                        if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $version)) {
+                            $versionsToLoad[] = $version;
                         }
                         }
                     }
                     }
 
 
                     $loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage');
                     $loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage');
                     foreach ($loadedPackages as $package) {
                     foreach ($loadedPackages as $package) {
                         $package->setRepository($repo);
                         $package->setRepository($repo);
-
                         $packages[spl_object_hash($package)] = $package;
                         $packages[spl_object_hash($package)] = $package;
+
                         if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
                         if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
+                            $package->getAliasOf()->setRepository($repo);
                             $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                             $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                         }
                         }
                     }
                     }
@@ -681,19 +680,30 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
     /**
     /**
      * TODO v3 should make this private once we can drop PHP 5.3 support
      * TODO v3 should make this private once we can drop PHP 5.3 support
      *
      *
+     * @param string $name package name (must be lowercased already)
      * @private
      * @private
      */
      */
-    public function isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $versionNormalized)
+    public function isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $versionData)
     {
     {
-        if (!call_user_func($isPackageAcceptableCallable, strtolower($name), VersionParser::parseStability($versionNormalized))) {
-            return false;
+        $versions = array($versionData['version_normalized']);
+
+        if ($alias = $this->loader->getBranchAlias($versionData)) {
+            $versions[] = $alias;
         }
         }
 
 
-        if ($constraint && !$constraint->matches(new Constraint('==', $versionNormalized))) {
-            return false;
+        foreach ($versions as $version) {
+            if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, $name, VersionParser::parseStability($version))) {
+                continue;
+            }
+
+            if ($constraint && !$constraint->matches(new Constraint('==', $version))) {
+                continue;
+            }
+
+            return true;
         }
         }
 
 
-        return true;
+        return false;
     }
     }
 
 
     protected function loadRootServerFile()
     protected function loadRootServerFile()
@@ -793,7 +803,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
     private function canonicalizeUrl($url)
     private function canonicalizeUrl($url)
     {
     {
         if ('/' === $url[0]) {
         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;
         return $url;
@@ -952,12 +966,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 }
                 }
 
 
                 $data = $response->decodeJson();
                 $data = $response->decodeJson();
-                if (!empty($data['warning'])) {
-                    $this->io->writeError('<warning>Warning from '.$this->url.': '.$data['warning'].'</warning>');
-                }
-                if (!empty($data['info'])) {
-                    $this->io->writeError('<info>Info from '.$this->url.': '.$data['info'].'</info>');
-                }
+                HttpDownloader::outputWarnings($this->io, $this->url, $data);
 
 
                 if ($cacheKey) {
                 if ($cacheKey) {
                     if ($storeLastModifiedTime) {
                     if ($storeLastModifiedTime) {
@@ -1031,12 +1040,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 }
                 }
 
 
                 $data = $response->decodeJson();
                 $data = $response->decodeJson();
-                if (!empty($data['warning'])) {
-                    $this->io->writeError('<warning>Warning from '.$this->url.': '.$data['warning'].'</warning>');
-                }
-                if (!empty($data['info'])) {
-                    $this->io->writeError('<info>Info from '.$this->url.': '.$data['info'].'</info>');
-                }
+                HttpDownloader::outputWarnings($this->io, $this->url, $data);
 
 
                 $lastModifiedDate = $response->getHeader('last-modified');
                 $lastModifiedDate = $response->getHeader('last-modified');
                 $response->collect();
                 $response->collect();
@@ -1101,12 +1105,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
             }
             }
 
 
             $data = $response->decodeJson();
             $data = $response->decodeJson();
-            if (!empty($data['warning'])) {
-                $io->writeError('<warning>Warning from '.$url.': '.$data['warning'].'</warning>');
-            }
-            if (!empty($data['info'])) {
-                $io->writeError('<info>Info from '.$url.': '.$data['info'].'</info>');
-            }
+            HttpDownloader::outputWarnings($io, $url, $data);
 
 
             $lastModifiedDate = $response->getHeader('last-modified');
             $lastModifiedDate = $response->getHeader('last-modified');
             $response->collect();
             $response->collect();

+ 5 - 0
src/Composer/Repository/FilesystemRepository.php

@@ -51,6 +51,11 @@ class FilesystemRepository extends WritableArrayRepository
         try {
         try {
             $packages = $this->file->read();
             $packages = $this->file->read();
 
 
+            // forward compatibility for composer v2 installed.json
+            if (isset($packages['packages'])) {
+                $packages = $packages['packages'];
+            }
+
             if (!is_array($packages)) {
             if (!is_array($packages)) {
                 throw new \UnexpectedValueException('Could not parse package list from the repository');
                 throw new \UnexpectedValueException('Could not parse package list from the repository');
             }
             }

+ 1 - 1
src/Composer/Repository/Pear/BaseChannelReader.php

@@ -47,7 +47,7 @@ abstract class BaseChannelReader
      * @param string $origin server
      * @param string $origin server
      * @param string $path   relative path to content
      * @param string $path   relative path to content
      * @throws \UnexpectedValueException
      * @throws \UnexpectedValueException
-     * @return \SimpleXMLElement
+     * @return string
      */
      */
     protected function requestContent($origin, $path)
     protected function requestContent($origin, $path)
     {
     {

+ 1 - 1
src/Composer/Repository/Pear/ChannelRest10Reader.php

@@ -150,7 +150,7 @@ class ChannelRest10Reader extends BaseChannelReader
      * @param string $baseUrl
      * @param string $baseUrl
      * @param string $packageName
      * @param string $packageName
      * @param string $version
      * @param string $version
-     * @return DependencyInfo[]
+     * @return DependencyInfo
      */
      */
     private function readPackageReleaseDependencies($baseUrl, $packageName, $version)
     private function readPackageReleaseDependencies($baseUrl, $packageName, $version)
     {
     {

+ 1 - 1
src/Composer/Repository/PearRepository.php

@@ -97,7 +97,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
      *
      *
      * @param  ChannelInfo         $channelInfo
      * @param  ChannelInfo         $channelInfo
      * @param  SemverVersionParser $versionParser
      * @param  SemverVersionParser $versionParser
-     * @return CompletePackage
+     * @return CompletePackage[]
      */
      */
     private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser)
     private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser)
     {
     {

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

@@ -166,8 +166,14 @@ class PlatformRepository extends ArrayRepository
                 case 'imagick':
                 case 'imagick':
                     $imagick = new \Imagick();
                     $imagick = new \Imagick();
                     $imageMagickVersion = $imagick->getVersion();
                     $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;
                     break;
 
 
                 case 'libxml':
                 case 'libxml':

+ 2 - 1
src/Composer/Repository/RepositoryInterface.php

@@ -78,8 +78,9 @@ interface RepositoryInterface extends \Countable
      *
      *
      * @param string $query search query
      * @param string $query search query
      * @param int    $mode  a set of SEARCH_* constants to search on, implementations should do a best effort only
      * @param int    $mode  a set of SEARCH_* constants to search on, implementations should do a best effort only
+     * @param string $type  The type of package to search for. Defaults to all types of packages
      *
      *
      * @return array[] an array of array('name' => '...', 'description' => '...')
      * @return array[] an array of array('name' => '...', 'description' => '...')
      */
      */
-    public function search($query, $mode = 0);
+    public function search($query, $mode = 0, $type = null);
 }
 }

+ 1 - 7
src/Composer/Repository/RepositoryManager.php

@@ -125,13 +125,7 @@ class RepositoryManager
 
 
         $class = $this->repositoryClasses[$type];
         $class = $this->repositoryClasses[$type];
 
 
-        $reflMethod = new \ReflectionMethod($class, '__construct');
-        $params = $reflMethod->getParameters();
-        if (isset($params[3]) && $params[3]->getClass() && $params[3]->getClass()->getName() === 'Composer\Util\HttpDownloader') {
-            return new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher);
-        }
-
-        return new $class($config, $this->io, $this->config, $this->eventDispatcher);
+        return new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher);
     }
     }
 
 
     /**
     /**

+ 47 - 38
src/Composer/Repository/Vcs/BitbucketDriver.php

@@ -125,50 +125,52 @@ abstract class BitbucketDriver extends VcsDriver
 
 
             $composer = $this->getBaseComposerInformation($identifier);
             $composer = $this->getBaseComposerInformation($identifier);
 
 
-            // specials for bitbucket
-            if (!isset($composer['support']['source'])) {
-                $label = array_search(
-                    $identifier,
-                    $this->getTags()
-                ) ?: array_search(
-                    $identifier,
-                    $this->getBranches()
-                ) ?: $identifier;
-
-                if (array_key_exists($label, $tags = $this->getTags())) {
-                    $hash = $tags[$label];
-                } elseif (array_key_exists($label, $branches = $this->getBranches())) {
-                    $hash = $branches[$label];
-                }
+            if ($composer) {
+                // specials for bitbucket
+                if (!isset($composer['support']['source'])) {
+                    $label = array_search(
+                        $identifier,
+                        $this->getTags()
+                    ) ?: array_search(
+                        $identifier,
+                        $this->getBranches()
+                    ) ?: $identifier;
+
+                    if (array_key_exists($label, $tags = $this->getTags())) {
+                        $hash = $tags[$label];
+                    } elseif (array_key_exists($label, $branches = $this->getBranches())) {
+                        $hash = $branches[$label];
+                    }
 
 
-                if (! isset($hash)) {
-                    $composer['support']['source'] = sprintf(
-                        'https://%s/%s/%s/src',
+                    if (! isset($hash)) {
+                        $composer['support']['source'] = sprintf(
+                            'https://%s/%s/%s/src',
+                            $this->originUrl,
+                            $this->owner,
+                            $this->repository
+                        );
+                    } else {
+                        $composer['support']['source'] = sprintf(
+                            'https://%s/%s/%s/src/%s/?at=%s',
+                            $this->originUrl,
+                            $this->owner,
+                            $this->repository,
+                            $hash,
+                            $label
+                        );
+                    }
+                }
+                if (!isset($composer['support']['issues']) && $this->hasIssues) {
+                    $composer['support']['issues'] = sprintf(
+                        'https://%s/%s/%s/issues',
                         $this->originUrl,
                         $this->originUrl,
                         $this->owner,
                         $this->owner,
                         $this->repository
                         $this->repository
                     );
                     );
-                } else {
-                    $composer['support']['source'] = sprintf(
-                        'https://%s/%s/%s/src/%s/?at=%s',
-                        $this->originUrl,
-                        $this->owner,
-                        $this->repository,
-                        $hash,
-                        $label
-                    );
                 }
                 }
-            }
-            if (!isset($composer['support']['issues']) && $this->hasIssues) {
-                $composer['support']['issues'] = sprintf(
-                    'https://%s/%s/%s/issues',
-                    $this->originUrl,
-                    $this->owner,
-                    $this->repository
-                );
-            }
-            if (!isset($composer['homepage'])) {
-                $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website;
+                if (!isset($composer['homepage'])) {
+                    $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website;
+                }
             }
             }
 
 
             $this->infoCache[$identifier] = $composer;
             $this->infoCache[$identifier] = $composer;
@@ -217,6 +219,13 @@ abstract class BitbucketDriver extends VcsDriver
             return $this->fallbackDriver->getChangeDate($identifier);
             return $this->fallbackDriver->getChangeDate($identifier);
         }
         }
 
 
+        if (strpos($identifier, '/') !== false) {
+            $branches = $this->getBranches();
+            if (isset($branches[$identifier])) {
+                $identifier = $branches[$identifier];
+            }
+        }
+
         $resource = sprintf(
         $resource = sprintf(
             'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
             'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
             $this->owner,
             $this->owner,

+ 1 - 1
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -154,8 +154,8 @@ class GitHubDriver extends VcsDriver
             }
             }
 
 
             $composer = $this->getBaseComposerInformation($identifier);
             $composer = $this->getBaseComposerInformation($identifier);
-            if ($composer) {
 
 
+            if ($composer) {
                 // specials for github
                 // specials for github
                 if (!isset($composer['support']['source'])) {
                 if (!isset($composer['support']['source'])) {
                     $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
                     $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;

+ 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
                 // clean up directory and do a fresh clone into it
                 $fs->removeDirectory($this->repoDir);
                 $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);
                 $hgUtils->runCommand($command, $this->url, $this->repoDir);

+ 3 - 3
src/Composer/Repository/Vcs/VcsDriverInterface.php

@@ -38,7 +38,7 @@ interface VcsDriverInterface
      *
      *
      * @param  string $file
      * @param  string $file
      * @param  string $identifier
      * @param  string $identifier
-     * @return string
+     * @return string|null
      */
      */
     public function getFileContent($file, $identifier);
     public function getFileContent($file, $identifier);
 
 
@@ -46,7 +46,7 @@ interface VcsDriverInterface
      * Get the changedate for $identifier.
      * Get the changedate for $identifier.
      *
      *
      * @param  string    $identifier
      * @param  string    $identifier
-     * @return \DateTime
+     * @return \DateTime|null
      */
      */
     public function getChangeDate($identifier);
     public function getChangeDate($identifier);
 
 
@@ -73,7 +73,7 @@ interface VcsDriverInterface
 
 
     /**
     /**
      * @param  string $identifier Any identifier to a specific branch/tag/commit
      * @param  string $identifier Any identifier to a specific branch/tag/commit
-     * @return array  With type, url reference and shasum keys.
+     * @return array|null  With type, url reference and shasum keys.
      */
      */
     public function getDist($identifier);
     public function getDist($identifier);
 
 

+ 61 - 28
src/Composer/Repository/VcsRepository.php

@@ -32,7 +32,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 {
 {
     protected $url;
     protected $url;
     protected $packageName;
     protected $packageName;
-    protected $verbose;
+    protected $isVerbose;
+    protected $isVeryVerbose;
     protected $io;
     protected $io;
     protected $config;
     protected $config;
     protected $versionParser;
     protected $versionParser;
@@ -47,6 +48,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
     private $driver;
     private $driver;
     /** @var VersionCacheInterface */
     /** @var VersionCacheInterface */
     private $versionCache;
     private $versionCache;
+    private $emptyReferences = array();
 
 
     public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null)
     public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null)
     {
     {
@@ -67,7 +69,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         $this->url = $repoConfig['url'];
         $this->url = $repoConfig['url'];
         $this->io = $io;
         $this->io = $io;
         $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
         $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
-        $this->verbose = $io->isVeryVerbose();
+        $this->isVerbose = $io->isVerbose();
+        $this->isVeryVerbose = $io->isVeryVerbose();
         $this->config = $config;
         $this->config = $config;
         $this->repoConfig = $repoConfig;
         $this->repoConfig = $repoConfig;
         $this->versionCache = $versionCache;
         $this->versionCache = $versionCache;
@@ -123,11 +126,17 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         return $this->branchErrorOccurred;
         return $this->branchErrorOccurred;
     }
     }
 
 
+    public function getEmptyReferences()
+    {
+        return $this->emptyReferences;
+    }
+
     protected function initialize()
     protected function initialize()
     {
     {
         parent::initialize();
         parent::initialize();
 
 
-        $verbose = $this->verbose;
+        $isVerbose = $this->isVerbose;
+        $isVeryVerbose = $this->isVeryVerbose;
 
 
         $driver = $this->getDriver();
         $driver = $this->getDriver();
         if (!$driver) {
         if (!$driver) {
@@ -145,31 +154,35 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $this->packageName = !empty($data['name']) ? $data['name'] : null;
                 $this->packageName = !empty($data['name']) ? $data['name'] : null;
             }
             }
         } catch (\Exception $e) {
         } catch (\Exception $e) {
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
                 $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
             }
             }
         }
         }
 
 
         foreach ($driver->getTags() as $tag => $identifier) {
         foreach ($driver->getTags() as $tag => $identifier) {
             $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';
             $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError($msg);
                 $this->io->writeError($msg);
-            } else {
+            } elseif ($isVerbose) {
                 $this->io->overwriteError($msg, false);
                 $this->io->overwriteError($msg, false);
             }
             }
 
 
             // strip the release- prefix from tags if present
             // strip the release- prefix from tags if present
             $tag = str_replace('release-', '', $tag);
             $tag = str_replace('release-', '', $tag);
 
 
-            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $verbose);
+            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose);
             if ($cachedPackage) {
             if ($cachedPackage) {
                 $this->addPackage($cachedPackage);
                 $this->addPackage($cachedPackage);
 
 
+                continue;
+            } elseif ($cachedPackage === false) {
+                $this->emptyReferences[] = $identifier;
+
                 continue;
                 continue;
             }
             }
 
 
             if (!$parsedTag = $this->validateTag($tag)) {
             if (!$parsedTag = $this->validateTag($tag)) {
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name</warning>');
                     $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name</warning>');
                 }
                 }
                 continue;
                 continue;
@@ -177,9 +190,10 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
 
             try {
             try {
                 if (!$data = $driver->getComposerInformation($identifier)) {
                 if (!$data = $driver->getComposerInformation($identifier)) {
-                    if ($verbose) {
+                    if ($isVeryVerbose) {
                         $this->io->writeError('<warning>Skipped tag '.$tag.', no composer file</warning>');
                         $this->io->writeError('<warning>Skipped tag '.$tag.', no composer file</warning>');
                     }
                     }
+                    $this->emptyReferences[] = $identifier;
                     continue;
                     continue;
                 }
                 }
 
 
@@ -198,7 +212,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
 
                 // broken package, version doesn't match tag
                 // broken package, version doesn't match tag
                 if ($data['version_normalized'] !== $parsedTag) {
                 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>');
                         $this->io->writeError('<warning>Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json</warning>');
                     }
                     }
                     continue;
                     continue;
@@ -206,47 +220,50 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
 
 
                 $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName;
                 $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName;
                 if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) {
                 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>');
                         $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;
                     continue;
                 }
                 }
 
 
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                     $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                 }
                 }
 
 
                 $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
                 $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
             } catch (\Exception $e) {
             } catch (\Exception $e) {
-                if ($verbose) {
+                if ($e instanceof TransportException && $e->getCode() === 404) {
+                    $this->emptyReferences[] = $identifier;
+                }
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).'</warning>');
                     $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).'</warning>');
                 }
                 }
                 continue;
                 continue;
             }
             }
         }
         }
 
 
-        if (!$verbose) {
+        if (!$isVeryVerbose) {
             $this->io->overwriteError('', false);
             $this->io->overwriteError('', false);
         }
         }
 
 
         $branches = $driver->getBranches();
         $branches = $driver->getBranches();
         foreach ($branches as $branch => $identifier) {
         foreach ($branches as $branch => $identifier) {
             $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
             $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError($msg);
                 $this->io->writeError($msg);
-            } else {
+            } elseif ($isVerbose) {
                 $this->io->overwriteError($msg, false);
                 $this->io->overwriteError($msg, false);
             }
             }
 
 
             if ($branch === 'trunk' && isset($branches['master'])) {
             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>');
                     $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;
                 continue;
             }
             }
 
 
             if (!$parsedBranch = $this->validateBranch($branch)) {
             if (!$parsedBranch = $this->validateBranch($branch)) {
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped branch '.$branch.', invalid name</warning>');
                     $this->io->writeError('<warning>Skipped branch '.$branch.', invalid name</warning>');
                 }
                 }
                 continue;
                 continue;
@@ -260,18 +277,23 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
                 $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
             }
             }
 
 
-            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $verbose);
+            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose);
             if ($cachedPackage) {
             if ($cachedPackage) {
                 $this->addPackage($cachedPackage);
                 $this->addPackage($cachedPackage);
 
 
+                continue;
+            } elseif ($cachedPackage === false) {
+                $this->emptyReferences[] = $identifier;
+
                 continue;
                 continue;
             }
             }
 
 
             try {
             try {
                 if (!$data = $driver->getComposerInformation($identifier)) {
                 if (!$data = $driver->getComposerInformation($identifier)) {
-                    if ($verbose) {
+                    if ($isVeryVerbose) {
                         $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file</warning>');
                         $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file</warning>');
                     }
                     }
+                    $this->emptyReferences[] = $identifier;
                     continue;
                     continue;
                 }
                 }
 
 
@@ -279,7 +301,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $data['version'] = $version;
                 $data['version'] = $version;
                 $data['version_normalized'] = $parsedBranch;
                 $data['version_normalized'] = $parsedBranch;
 
 
-                if ($verbose) {
+                if ($isVeryVerbose) {
                     $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')');
                     $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')');
                 }
                 }
 
 
@@ -290,12 +312,15 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 }
                 }
                 $this->addPackage($package);
                 $this->addPackage($package);
             } catch (TransportException $e) {
             } catch (TransportException $e) {
-                if ($verbose) {
+                if ($e->getCode() === 404) {
+                    $this->emptyReferences[] = $identifier;
+                }
+                if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found</warning>');
                     $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found</warning>');
                 }
                 }
                 continue;
                 continue;
             } catch (\Exception $e) {
             } catch (\Exception $e) {
-                if (!$verbose) {
+                if (!$isVeryVerbose) {
                     $this->io->writeError('');
                     $this->io->writeError('');
                 }
                 }
                 $this->branchErrorOccurred = true;
                 $this->branchErrorOccurred = true;
@@ -306,7 +331,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         }
         }
         $driver->cleanup();
         $driver->cleanup();
 
 
-        if (!$verbose) {
+        if (!$isVeryVerbose) {
             $this->io->overwriteError('', false);
             $this->io->overwriteError('', false);
         }
         }
 
 
@@ -351,23 +376,31 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         return false;
         return false;
     }
     }
 
 
-    private function getCachedPackageVersion($version, $identifier, $verbose)
+    private function getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose)
     {
     {
         if (!$this->versionCache) {
         if (!$this->versionCache) {
             return;
             return;
         }
         }
 
 
         $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
         $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
+        if ($cachedPackage === false) {
+            if ($isVeryVerbose) {
+                $this->io->writeError('<warning>Skipped '.$version.', no composer file (cached from ref '.$identifier.')</warning>');
+            }
+
+            return false;
+        }
+
         if ($cachedPackage) {
         if ($cachedPackage) {
             $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
             $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
-            if ($verbose) {
+            if ($isVeryVerbose) {
                 $this->io->writeError($msg);
                 $this->io->writeError($msg);
-            } else {
+            } elseif ($isVerbose) {
                 $this->io->overwriteError($msg, false);
                 $this->io->overwriteError($msg, false);
             }
             }
 
 
             if ($existingPackage = $this->findPackage($cachedPackage['name'], $cachedPackage['version_normalized'])) {
             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>');
                     $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;
                 $cachedPackage = null;

+ 1 - 1
src/Composer/Repository/VersionCacheInterface.php

@@ -17,7 +17,7 @@ interface VersionCacheInterface
     /**
     /**
      * @param string $version
      * @param string $version
      * @param string $identifier
      * @param string $identifier
-     * @return array Package version data
+     * @return array|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier
      */
      */
     public function getVersionPackage($version, $identifier);
     public function getVersionPackage($version, $identifier);
 }
 }

+ 2 - 7
src/Composer/Util/AuthHelper.php

@@ -71,12 +71,11 @@ class AuthHelper
      * @param string $origin
      * @param string $origin
      * @param int $statusCode HTTP status code that triggered this call
      * @param int $statusCode HTTP status code that triggered this call
      * @param string|null $reason a message/description explaining why this was called
      * @param string|null $reason a message/description explaining why this was called
-     * @param string $warning an authentication warning returned by the server as {"warning": ".."}, if present
      * @param string[] $headers
      * @param string[] $headers
-     * @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be
+     * @return array|null containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be
      *               retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json
      *               retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json
      */
      */
-    public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $warning = null, $headers = array())
+    public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $headers = array())
     {
     {
         $storeAuth = false;
         $storeAuth = false;
         $retry = false;
         $retry = false;
@@ -173,10 +172,6 @@ class AuthHelper
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
             }
             }
 
 
-            $this->io->overwriteError('');
-            if ($warning) {
-                $this->io->writeError('    <warning>'.$warning.'</warning>');
-            }
             $this->io->writeError('    Authentication required (<info>'.parse_url($url, PHP_URL_HOST).'</info>):');
             $this->io->writeError('    Authentication required (<info>'.parse_url($url, PHP_URL_HOST).'</info>):');
             $username = $this->io->ask('      Username: ');
             $username = $this->io->ask('      Username: ');
             $password = $this->io->askAndHideAnswer('      Password: ');
             $password = $this->io->askAndHideAnswer('      Password: ');

+ 6 - 0
src/Composer/Util/Bitbucket.php

@@ -22,11 +22,17 @@ use Composer\Downloader\TransportException;
  */
  */
 class Bitbucket
 class Bitbucket
 {
 {
+    /** @var IOInterface */
     private $io;
     private $io;
+    /** @var Config */
     private $config;
     private $config;
+    /** @var ProcessExecutor */
     private $process;
     private $process;
+    /** @var HttpDownloader */
     private $httpDownloader;
     private $httpDownloader;
+    /** @var array */
     private $token = array();
     private $token = array();
+    /** @var int|null */
     private $time;
     private $time;
 
 
     const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';
     const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';

+ 41 - 21
src/Composer/Util/Filesystem.php

@@ -23,6 +23,7 @@ use Symfony\Component\Finder\Finder;
  */
  */
 class Filesystem
 class Filesystem
 {
 {
+    /** @var ProcessExecutor */
     private $processExecutor;
     private $processExecutor;
 
 
     public function __construct(ProcessExecutor $executor = null)
     public function __construct(ProcessExecutor $executor = null)
@@ -199,9 +200,15 @@ class Filesystem
      */
      */
     public function unlink($path)
     public function unlink($path)
     {
     {
-        if (!@$this->unlinkImplementation($path)) {
+        $unlinked = @$this->unlinkImplementation($path);
+        if (!$unlinked) {
             // retry after a bit on windows since it tends to be touchy with mass removals
             // retry after a bit on windows since it tends to be touchy with mass removals
-            if (!Platform::isWindows() || (usleep(350000) && !@$this->unlinkImplementation($path))) {
+            if (Platform::isWindows()) {
+                usleep(350000);
+                $unlinked = @$this->unlinkImplementation($path);
+            }
+            
+            if (!$unlinked) {
                 $error = error_get_last();
                 $error = error_get_last();
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
                 if (Platform::isWindows()) {
                 if (Platform::isWindows()) {
@@ -224,9 +231,15 @@ class Filesystem
      */
      */
     public function rmdir($path)
     public function rmdir($path)
     {
     {
-        if (!@rmdir($path)) {
+        $deleted = @rmdir($path);
+        if (!$deleted) {
             // retry after a bit on windows since it tends to be touchy with mass removals
             // retry after a bit on windows since it tends to be touchy with mass removals
-            if (!Platform::isWindows() || (usleep(350000) && !@rmdir($path))) {
+            if (Platform::isWindows()) {
+                usleep(350000);
+                $deleted = @rmdir($path);
+            }
+            
+            if (!$deleted) {
                 $error = error_get_last();
                 $error = error_get_last();
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
                 if (Platform::isWindows()) {
                 if (Platform::isWindows()) {
@@ -279,6 +292,7 @@ class Filesystem
         $this->ensureDirectoryExists($target);
         $this->ensureDirectoryExists($target);
 
 
         $result = true;
         $result = true;
+        /** @var RecursiveDirectoryIterator $ri */
         foreach ($ri as $file) {
         foreach ($ri as $file) {
             $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
             $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
             if ($file->isDir()) {
             if ($file->isDir()) {
@@ -428,7 +442,7 @@ class Filesystem
      */
      */
     public function isAbsolutePath($path)
     public function isAbsolutePath($path)
     {
     {
-        return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':';
+        return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path, 0, 2) === '\\\\';
     }
     }
 
 
     /**
     /**
@@ -525,6 +539,9 @@ class Filesystem
         return $size;
         return $size;
     }
     }
 
 
+    /**
+     * @return ProcessExecutor
+     */
     protected function getProcess()
     protected function getProcess()
     {
     {
         return $this->processExecutor;
         return $this->processExecutor;
@@ -648,6 +665,20 @@ class Filesystem
     /**
     /**
      * Returns whether the target directory is a Windows NTFS Junction.
      * Returns whether the target directory is a Windows NTFS Junction.
      *
      *
+     * We test if the path is a directory and not an ordinary link, then check
+     * that the mode value returned from lstat (which gives the status of the
+     * link itself) is not a directory, by replicating the POSIX S_ISDIR test.
+     *
+     * This logic works because PHP does not set the mode value for a junction,
+     * since there is no universal file type flag for it. Unfortunately an
+     * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a
+     * random value to be returned. See https://bugs.php.net/bug.php?id=77552
+     *
+     * If this random value passes the S_ISDIR test, then a junction will not be
+     * detected and a recursive delete operation could lead to loss of data in
+     * the target directory. Note that Windows rmdir can handle this situation
+     * and will only delete the junction (from Windows 7 onwards).
+     *
      * @param  string $junction Path to check.
      * @param  string $junction Path to check.
      * @return bool
      * @return bool
      */
      */
@@ -659,22 +690,13 @@ class Filesystem
         if (!is_dir($junction) || is_link($junction)) {
         if (!is_dir($junction) || is_link($junction)) {
             return false;
             return false;
         }
         }
-        /**
-         * According to MSDN at https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx we can detect a junction now
-         * using the 'mode' value from stat: "The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit
-         * is set if path specifies an ordinary file or a device." We have just tested for a directory above, so if
-         * we have a directory that isn't one according to lstat(...) we must have a junction.
-         *
-         * #define	_S_IFDIR	0x4000
-         * #define	_S_IFREG	0x8000
-         *
-         * Stat cache should be cleared before to avoid accidentally reading wrong information from previous installs.
-         */
+
+        // Important to clear all caches first
         clearstatcache(true, $junction);
         clearstatcache(true, $junction);
-        clearstatcache(false);
         $stat = lstat($junction);
         $stat = lstat($junction);
 
 
-        return !($stat['mode'] & 0xC000);
+        // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
+        return $stat ? 0x4000 !== ($stat['mode'] & 0xF000) : false;
     }
     }
 
 
     /**
     /**
@@ -692,9 +714,7 @@ class Filesystem
         if (!$this->isJunction($junction)) {
         if (!$this->isJunction($junction)) {
             throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction));
             throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction));
         }
         }
-        $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape($junction));
-        clearstatcache(true, $junction);
 
 
-        return ($this->getProcess()->execute($cmd, $output) === 0);
+        return $this->rmdir($junction);
     }
     }
 }
 }

+ 4 - 0
src/Composer/Util/GitHub.php

@@ -22,9 +22,13 @@ use Composer\Downloader\TransportException;
  */
  */
 class GitHub
 class GitHub
 {
 {
+    /** @var IOInterface */
     protected $io;
     protected $io;
+    /** @var Config */
     protected $config;
     protected $config;
+    /** @var ProcessExecutor */
     protected $process;
     protected $process;
+    /** @var HttpDownloader */
     protected $httpDownloader;
     protected $httpDownloader;
 
 
     /**
     /**

Some files were not shown because too many files changed in this diff