ソースを参照

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 年 前
コミット
9053d74282
100 ファイル変更833 行追加443 行削除
  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
 /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.2
     - php: 7.3
+      env: PHPSTAN=1
     - php: 7.3
-      env: deps=high
+      env:
+        - deps=high
     - php: nightly
   fast_finish: true
   allow_failures:
@@ -58,6 +60,11 @@ before_script:
 script:
   # 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);'
+  # 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:
   - 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
 
   * Fixed regression when executing partial updates
@@ -729,6 +751,12 @@
 
   * 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.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 - 1
appveyor.yml

@@ -3,7 +3,7 @@ clone_depth: 5
 
 environment:
   # 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_PHP: C:\tools\phpci\php
   PHPCI_COMPOSER: C:\tools\phpci\composer

+ 5 - 0
bin/composer

@@ -18,6 +18,11 @@ $xdebug = new XdebugHandler('Composer', '--ansi');
 $xdebug->check();
 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')) {
     @ini_set('display_errors', 1);
 

+ 2 - 2
composer.json

@@ -1,7 +1,7 @@
 {
     "name": "composer/composer",
     "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": [
         "package",
         "dependency",
@@ -56,7 +56,7 @@
     },
     "extra": {
         "branch-alias": {
-            "dev-master": "1.9-dev"
+            "dev-master": "2.0-dev"
         }
     },
     "autoload": {

+ 42 - 43
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "b078b12b2912d599e0c6904f64def484",
+    "content-hash": "280f5d5184039085b5f22236d267ae82",
     "packages": [
         {
             "name": "composer/ca-bundle",
@@ -64,16 +64,16 @@
         },
         {
             "name": "composer/semver",
-            "version": "1.4.2",
+            "version": "1.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/semver.git",
-                "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
+                "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e"
             },
             "dist": {
                 "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": ""
             },
             "require": {
@@ -122,28 +122,27 @@
                 "validation",
                 "versioning"
             ],
-            "time": "2016-08-30T16:08:34+00:00"
+            "time": "2019-03-19T17:25:45+00:00"
         },
         {
             "name": "composer/spdx-licenses",
-            "version": "1.5.0",
+            "version": "1.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/spdx-licenses.git",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2"
+                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2",
+                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
+                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.2 || ^7.0"
+                "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
-                "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7"
             },
             "type": "library",
             "extra": {
@@ -183,20 +182,20 @@
                 "spdx",
                 "validator"
             ],
-            "time": "2018-11-01T09:45:54+00:00"
+            "time": "2019-03-26T10:23:26+00:00"
         },
         {
             "name": "composer/xdebug-handler",
-            "version": "1.3.2",
+            "version": "1.3.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "d17708133b6c276d6e42ef887a877866b909d892"
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892",
-                "reference": "d17708133b6c276d6e42ef887a877866b909d892",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f",
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f",
                 "shasum": ""
             },
             "require": {
@@ -227,27 +226,27 @@
                 "Xdebug",
                 "performance"
             ],
-            "time": "2019-01-28T20:25:53+00:00"
+            "time": "2019-05-27T17:52:04+00:00"
         },
         {
             "name": "justinrainbow/json-schema",
-            "version": "5.2.7",
+            "version": "5.2.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/justinrainbow/json-schema.git",
-                "reference": "8560d4314577199ba51bf2032f02cd1315587c23"
+                "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4"
             },
             "dist": {
                 "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": ""
             },
             "require": {
                 "php": ">=5.3.3"
             },
             "require-dev": {
-                "friendsofphp/php-cs-fixer": "^2.1",
+                "friendsofphp/php-cs-fixer": "~2.2.20",
                 "json-schema/json-schema-test-suite": "1.2.0",
                 "phpunit/phpunit": "^4.8.35"
             },
@@ -293,7 +292,7 @@
                 "json",
                 "schema"
             ],
-            "time": "2018-02-14T22:26:30+00:00"
+            "time": "2019-01-14T23:55:14+00:00"
         },
         {
             "name": "psr/log",
@@ -481,7 +480,7 @@
         },
         {
             "name": "symfony/console",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
@@ -542,7 +541,7 @@
         },
         {
             "name": "symfony/debug",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
@@ -599,7 +598,7 @@
         },
         {
             "name": "symfony/filesystem",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
@@ -649,7 +648,7 @@
         },
         {
             "name": "symfony/finder",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
@@ -698,16 +697,16 @@
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.10.0",
+            "version": "v1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+                "reference": "82ebae02209c21113908c229e9883c419720738a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
+                "reference": "82ebae02209c21113908c229e9883c419720738a",
                 "shasum": ""
             },
             "require": {
@@ -719,7 +718,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.11-dev"
                 }
             },
             "autoload": {
@@ -752,20 +751,20 @@
                 "polyfill",
                 "portable"
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2019-02-06T07:57:58+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.10.0",
+            "version": "v1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
+                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
                 "shasum": ""
             },
             "require": {
@@ -777,7 +776,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.11-dev"
                 }
             },
             "autoload": {
@@ -811,11 +810,11 @@
                 "portable",
                 "shim"
             ],
-            "time": "2018-09-21T13:07:52+00:00"
+            "time": "2019-02-06T07:57:58+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
@@ -1780,7 +1779,7 @@
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.8.48",
+            "version": "v2.8.50",
             "source": {
                 "type": "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
 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
-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.
 
 This idea is not new and Composer is strongly inspired by node's
@@ -47,7 +47,7 @@ Linux and macOS.
 ### Downloading the Composer Executable
 
 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)
 if you wish to know more about the inner workings of the installer. The source
 is plain PHP.
@@ -82,7 +82,7 @@ Now run `php bin/composer` in order to run Composer.
 #### Globally
 
 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`
 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
 from setting the request_fulluri option.
 
+### COMPOSER_SELF_UPDATE_TARGET
+
+If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem.
+
 ### no_proxy or NO_PROXY
 
 If you are behind a proxy and would like to disable it for certain domains, you

+ 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
 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
 {
     "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
 slow connection or huge vendors.
 
+To disable the process timeout on a custom command under `scripts`, a static
+helper is available:
+
+```json
+{
+    "scripts": {
+        "test": [
+            "Composer\\Config::disableProcessTimeout",
+            "phpunit"
+        ]
+    }
+}
+```
+
 ## use-include-path
 
 Defaults to `false`. If `true`, the Composer autoloader will also look for classes

+ 5 - 0
doc/articles/plugins.md

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

+ 51 - 1
doc/articles/scripts.md

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

+ 1 - 1
doc/articles/versions.md

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

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

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

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

+ 2 - 1
src/Composer/Cache.php

@@ -189,7 +189,8 @@ class Cache
     public function clear()
     {
         if ($this->enabled) {
-            return $this->filesystem->removeDirectory($this->root);
+            $this->filesystem->emptyDirectory($this->root);
+            return true;
         }
 
         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>
 
+Read more at https://getcomposer.org/doc/03-cli.md#archive
 EOT
             )
         ;

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

@@ -27,6 +27,8 @@ use Symfony\Component\Console\Command\Command;
 /**
  * Base class for Composer commands
  *
+ * @method Application getApplication()
+ *
  * @author Ryan Weaver <ryan@knplabs.com>
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
  */
@@ -46,7 +48,7 @@ abstract class BaseCommand extends Command
      * @param  bool              $required
      * @param  bool|null         $disablePlugins
      * @throws \RuntimeException
-     * @return Composer
+     * @return Composer|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'))) {
             $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);

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

@@ -32,6 +32,8 @@ class ClearCacheCommand extends BaseCommand
                 <<<EOT
 The <info>clear-cache</info> deletes all cached packages from composer's
 cache directory.
+
+Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-
 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.
 
     <comment>%command.full_name% --editor --global</comment>
+
+Read more at https://getcomposer.org/doc/03-cli.md#config
 EOT
             )
         ;
@@ -226,7 +228,7 @@ EOT
         }
 
         $settingKey = $input->getArgument('setting-key');
-        if (!$settingKey) {
+        if (!$settingKey || !is_string($settingKey)) {
             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
 can pass the <info>'--repository=https://myrepository.org'</info> flag.
 
+Read more at https://getcomposer.org/doc/03-cli.md#create-project
 EOT
             )
         ;
@@ -183,7 +184,9 @@ EOT
                 ->setRunScripts(!$noScripts)
                 ->setIgnorePlatformRequirements($ignorePlatformReqs)
                 ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
-                ->setOptimizeAutoloader($config->get('optimize-autoloader'));
+                ->setOptimizeAutoloader($config->get('optimize-autoloader'))
+                ->setClassMapAuthoritative($config->get('classmap-authoritative'))
+                ->setApcuAutoloader($config->get('apcu-autoloader'));
 
             if ($disablePlugins) {
                 $installer->disablePlugins();

+ 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>
 
+Read more at https://getcomposer.org/doc/03-cli.md#depends-why-
 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.
 
+Read more at https://getcomposer.org/doc/03-cli.md#diagnose
 EOT
             )
         ;
@@ -602,20 +603,6 @@ EOT
                         $text .= "Install either of them or recompile php without --disable-iconv";
                         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':
                         $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
                         break;
@@ -713,7 +700,7 @@ EOT
     /**
      * Check if allow_url_fopen is ON
      *
-     * @return bool|string
+     * @return true|string
      */
     private function checkConnectivity()
     {

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

@@ -39,6 +39,8 @@ class DumpAutoloadCommand extends BaseCommand
             ->setHelp(
                 <<<EOT
 <info>php composer.phar dump-autoload</info>
+
+Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
 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'
                 ),
             ))
+            ->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
 composer.json or the environmental variable COMPOSER_BIN_DIR.
 
+Read more at https://getcomposer.org/doc/03-cli.md#global
 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 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
             );
     }

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

@@ -72,6 +72,7 @@ in the current directory.
 
 <info>php composer.phar init</info>
 
+Read more at https://getcomposer.org/doc/03-cli.md#init
 EOT
             )
         ;
@@ -694,15 +695,22 @@ EOT
     {
         // find the latest version allowed in this repo set
         $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;
-            $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);
         }
 
+        $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);
+
         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
             if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) {
                 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>
 
+Read more at https://getcomposer.org/doc/03-cli.md#install-i
 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 installed dependencies.
 
+Read more at https://getcomposer.org/doc/03-cli.md#licenses
 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.
 - <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
             )
         ;

+ 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>
 
+Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not-
 EOT
             )
         ;

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

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

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

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

+ 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:
 
 <info>php composer.phar run-script post-update-cmd</info>
+
+Read more at https://getcomposer.org/doc/03-cli.md#run-script
 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:
 
 <info>php composer.phar run-script post-update-cmd</info>
+
+Read more at https://getcomposer.org/doc/03-cli.md#run-script
 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
 <info>php composer.phar search symfony composer</info>
 
+Read more at https://getcomposer.org/doc/03-cli.md#search
 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>
 
+Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate-
 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
 lists all packages available.
 
+Read more at https://getcomposer.org/doc/03-cli.md#show
 EOT
             )
         ;
@@ -823,10 +824,10 @@ EOT
     /**
      * 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(
         $package,
@@ -835,7 +836,7 @@ EOT
         $level = 1
     ) {
         $previousTreeBar = str_replace('├', '│', $previousTreeBar);
-        if (isset($package['requires'])) {
+        if (is_array($package) && isset($package['requires'])) {
             $requires = $package['requires'];
             $treeBar = $previousTreeBar . '  ├';
             $i = 0;
@@ -968,7 +969,7 @@ EOT
      * @param string           $phpVersion
      * @param bool             $minorOnly
      *
-     * @return PackageInterface|null
+     * @return PackageInterface|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
 been modified locally.
 
+Read more at https://getcomposer.org/doc/03-cli.md#status
 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.
 
+Read more at https://getcomposer.org/doc/03-cli.md#suggests
 EOT
             )
         ;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     protected function execute(InputInterface $input, OutputInterface $output)
     {
         $lock = $this->getComposer()->getLocker()->getLockData();
@@ -117,7 +121,7 @@ EOT
                 $io->write(sprintf('<info>%s</info>', $suggestion));
             }
 
-            return;
+            return null;
         }
 
         // 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>.
 
+Read more at https://getcomposer.org/doc/03-cli.md#update-u
 EOT
             )
         ;

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

@@ -55,6 +55,7 @@ Exit codes in case of errors are:
 2 validation error(s)
 3 file unreadable or missing
 
+Read more at https://getcomposer.org/doc/03-cli.md#validate
 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_branch_alias_version@', $this->branchAliasVersion, $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);

+ 37 - 1
src/Composer/Composer.php

@@ -29,10 +29,46 @@ use Composer\Package\Archiver\ArchiveManager;
  */
 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 BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
     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

+ 17 - 0
src/Composer/Config.php

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

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

@@ -39,7 +39,7 @@ interface ConfigSourceInterface
      * Add a config setting
      *
      * @param string $name  Name
-     * @param string $value Value
+     * @param string|array $value 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  mixed $value
-     * @return array
+     * @return int
      */
     private function arrayUnshiftRef(&$array, &$value)
     {

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

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

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

@@ -23,10 +23,10 @@ class GenericRule extends Rule
     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)
     {

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

@@ -48,7 +48,7 @@ class PoolBuilder
 
     public function buildPool(array $repositories, array $rootAliases, Request $request)
     {
-        $this->pool = new Pool($this->filterRequires);
+        $pool = new Pool($this->filterRequires);
         $this->rootAliases = $rootAliases;
 
         // 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->loadedNames);
         unset($this->nameConstraints);
 
-        return $this->pool;
+        return $pool;
     }
 
     private function loadPackage(PackageInterface $package, $repoIndex)
@@ -180,12 +180,12 @@ class PoolBuilder
             if (!isset($this->loadedNames[$require])) {
                 $loadNames[$require] = null;
             }
-            if ($link->getConstraint()) {
+            if ($linkConstraint = $link->getConstraint()) {
                 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]) {
                     // 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 {
                 $this->nameConstraints[$require] = null;

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

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

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

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

+ 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));
             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));
                 if (0 === $this->process->execute($command, $output, $path)) {
-                    return;
+                    return null;
                 }
             }
         }
 
         $command = sprintf($template, ProcessExecutor::escape($gitRef));
         if (0 === $this->process->execute($command, $output, $path)) {
-            return;
+            return null;
         }
 
         // 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
 {
+    /** @var ProcessExecutor */
     protected $process;
 
     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);
         }
 
+        // 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();
         $this->filesystem->removeDirectory($path);
 
@@ -181,4 +187,25 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
             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)
     {
-        $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);
 
-        return;
+        return null;
     }
 
     /**

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

@@ -32,6 +32,7 @@ use RarArchive;
  */
 class RarDownloader extends ArchiveDownloader
 {
+    /** @var ProcessExecutor */
     protected $process;
 
     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
 {
+    /** @var ProcessExecutor */
     protected $process;
 
     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 $isWindows;
 
+    /** @var ProcessExecutor */
     protected $process;
+    /** @var ZipArchive|null */
     private $zipArchiveObject;
 
     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 $loader;
     protected $process;
-    protected $listeners;
+    protected $listeners = array();
     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');
                 }
+                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);
                 $return = false === call_user_func($callable, $event) ? 1 : 0;
             } elseif ($this->isComposerScript($callable)) {
@@ -197,6 +200,7 @@ class EventDispatcher
                     }
 
                     try {
+                        /** @var InstallerEvent $event */
                         $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags));
                     } catch (ScriptExecutionException $e) {
                         $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;
     }
 
+    /**
+     * @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
      *
@@ -481,7 +501,7 @@ class EventDispatcher
      *
      * @param  Event             $event
      * @throws \RuntimeException
-     * @return number
+     * @return int
      */
     protected function pushEvent(Event $event)
     {

+ 1 - 1
src/Composer/Factory.php

@@ -413,7 +413,7 @@ class Factory
     /**
      * @param  IOInterface $io             IO instance
      * @param  bool        $disablePlugins Whether plugins should not be loaded
-     * @return Composer
+     * @return Composer|null
      */
     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\Util\ProcessExecutor;
-use Psr\Log\LoggerInterface;
 use Psr\Log\LogLevel;
 
-abstract class BaseIO implements IOInterface, LoggerInterface
+abstract class BaseIO implements IOInterface
 {
     protected $authentications = array();
 

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

@@ -13,13 +13,14 @@
 namespace Composer\IO;
 
 use Composer\Config;
+use Psr\Log\LoggerInterface;
 
 /**
  * The Input/Output helper interface.
  *
  * @author François Pluchino <francois.pluchino@opendisplay.com>
  */
-interface IOInterface
+interface IOInterface extends LoggerInterface
 {
     const QUIET = 1;
     const NORMAL = 2;
@@ -107,7 +108,7 @@ interface IOInterface
      * @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
-     * @return string            The user answer
+     * @return string|null       The user answer
      */
     public function ask($question, $default = null);
 
@@ -145,7 +146,7 @@ interface IOInterface
      *
      * @param string $question The question to ask
      *
-     * @return string The answer
+     * @return string|null The answer
      */
     public function askAndHideAnswer($question);
 
@@ -160,7 +161,7 @@ interface IOInterface
      * @param bool        $multiselect  Select more than one value separated by comma
      *
      * @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);
 

+ 4 - 1
src/Composer/Installer.php

@@ -399,6 +399,8 @@ class Installer
         $solver = new Solver($policy, $pool, $this->io);
         try {
             $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
+            $ruleSetSize = $solver->getRuleSetSize();
+            $solver = null;
         } catch (SolverProblemsException $e) {
             $this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
             $this->io->writeError($e->getMessage());
@@ -413,7 +415,7 @@ class Installer
         //$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 ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE);
+        $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);
 
         if (!$lockTransaction->getOperations()) {
             $this->io->writeError('Nothing to modify in lock file');
@@ -559,6 +561,7 @@ class Installer
             $solver = new Solver($policy, $pool, $this->io);
             try {
                 $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
                 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 $prevPackage previous package instance in case of an update
-     * @return PromiseInterface
+     * @return PromiseInterface|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 Composer        $composer
-     * @param string          $type
+     * @param string|null     $type
      * @param Filesystem      $filesystem
      * @param BinaryInstaller $binaryInstaller
      */

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

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

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

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

+ 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
      *
      * @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
      */
     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)
     {
-        static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
-
         $packages = array();
         $linkCache = array();
 
         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;

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

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

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

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

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

@@ -76,7 +76,7 @@ interface PackageInterface
     /**
      * Returns the package targetDir property
      *
-     * @return string The package targetDir
+     * @return string|null The package targetDir
      */
     public function getTargetDir();
 
@@ -358,4 +358,32 @@ interface PackageInterface
      * @return array
      */
     public function getTransportOptions();
+
+    /**
+     * @param string $reference
+     *
+     * @return void
+     */
+    public function setSourceReference($reference);
+
+    /**
+     * @param string $url
+     *
+     * @return void
+     */
+    public function setDistUrl($url);
+
+    /**
+     * @param string $type
+     *
+     * @return void
+     */
+    public function setDistType($type);
+
+    /**
+     * @param string $reference
+     *
+     * @return void
+     */
+    public function setDistReference($reference);
 }

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

@@ -17,6 +17,7 @@ use Composer\Repository\Vcs\HgDriver;
 use Composer\IO\NullIO;
 use Composer\Semver\VersionParser as SemverVersionParser;
 use Composer\Util\Git as GitUtil;
+use Composer\Util\HttpDownloader;
 use Composer\Util\ProcessExecutor;
 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                $targetPhpVersion
      * @param  string                $preferredStability
-     * @return PackageInterface|bool
+     * @return PackageInterface|false
      */
     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\EventDispatcher\EventSubscriberInterface;
 use Composer\IO\IOInterface;
+use Composer\Package\CompletePackage;
 use Composer\Package\Package;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\RepositoryInterface;
-use Composer\Package\AliasPackage;
 use Composer\Package\PackageInterface;
 use Composer\Package\Link;
 use Composer\Repository\RepositorySet;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Plugin\Capability\Capability;
+use Composer\Util\PackageSorter;
 
 /**
  * Plugin manager
@@ -253,8 +254,10 @@ class PluginManager
      */
     private function loadRepository(RepositoryInterface $repo)
     {
-        foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */
-            if ($package instanceof AliasPackage) {
+        $packages = $repo->getPackages();
+        $sortedPackages = array_reverse(PackageSorter::sortPackages($packages));
+        foreach ($sortedPackages as $package) {
+            if (!($package instanceof CompletePackage)) {
                 continue;
             }
             if ('composer-plugin' === $package->getType()) {

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

@@ -32,9 +32,11 @@ abstract class BaseRepository implements RepositoryInterface
 
         $result = array();
         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;
                 if ($package instanceof AliasPackage && !isset($result[spl_object_hash($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\Cache;
 use Composer\Config;
+use Composer\Composer;
 use Composer\Factory;
 use Composer\IO\IOInterface;
 use Composer\Util\HttpDownloader;
@@ -103,7 +104,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
 
         $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
         $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->loader = new ArrayLoader($this->versionParser);
         $this->httpDownloader = $httpDownloader;
@@ -139,9 +140,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 return;
             }
 
-            $packages = $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) {
-                return true;
-            });
+            $packages = $this->loadAsyncPackages(array($name => $constraint));
 
             return reset($packages);
         }
@@ -181,9 +180,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 return array();
             }
 
-            return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) {
-                return true;
-            });
+            return $this->loadAsyncPackages(array($name => $constraint));
         }
 
         if ($hasProviders) {
@@ -241,7 +238,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                     $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.');
@@ -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) {
                     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;
                         }
                     }
@@ -511,11 +510,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 }
 
                 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);
     }
 
-    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();
 
@@ -579,16 +583,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
             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) {
             $name = strtolower($name);
 
+            $realName = preg_replace('{~dev$}', '', $name);
             // 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;
             }
 
             $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
-            $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
+            $cacheKey = 'provider-'.strtr($name, '/', '~').'.json';
 
             $lastModified = null;
             if ($contents = $this->cache->read($cacheKey)) {
@@ -597,16 +609,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
             }
 
             $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) {
                         $response = $contents;
                     }
 
-                    if (!isset($response['packages'][$name])) {
+                    if (!isset($response['packages'][$realName])) {
                         return;
                     }
 
-                    $versions = $response['packages'][$name];
+                    $versions = $response['packages'][$realName];
 
                     if (isset($response['minified']) && $response['minified'] === 'composer/2.0') {
                         // TODO extract in other method
@@ -635,37 +647,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                         unset($expanded, $expandedVersion, $versionData);
                     }
 
-                    static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
                     $versionsToLoad = array();
                     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');
                     foreach ($loadedPackages as $package) {
                         $package->setRepository($repo);
-
                         $packages[spl_object_hash($package)] = $package;
+
                         if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
+                            $package->getAliasOf()->setRepository($repo);
                             $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
      *
+     * @param string $name package name (must be lowercased already)
      * @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()
@@ -793,7 +803,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
     private function canonicalizeUrl($url)
     {
         if ('/' === $url[0]) {
-            return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url);
+            if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) {
+                return $matches[0] . $url;
+            }
+
+            return $this->url;
         }
 
         return $url;
@@ -952,12 +966,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 }
 
                 $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 ($storeLastModifiedTime) {
@@ -1031,12 +1040,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
                 }
 
                 $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');
                 $response->collect();
@@ -1101,12 +1105,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
             }
 
             $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');
             $response->collect();

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

@@ -51,6 +51,11 @@ class FilesystemRepository extends WritableArrayRepository
         try {
             $packages = $this->file->read();
 
+            // forward compatibility for composer v2 installed.json
+            if (isset($packages['packages'])) {
+                $packages = $packages['packages'];
+            }
+
             if (!is_array($packages)) {
                 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 $path   relative path to content
      * @throws \UnexpectedValueException
-     * @return \SimpleXMLElement
+     * @return string
      */
     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 $packageName
      * @param string $version
-     * @return DependencyInfo[]
+     * @return DependencyInfo
      */
     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  SemverVersionParser $versionParser
-     * @return CompletePackage
+     * @return CompletePackage[]
      */
     private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser)
     {

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

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

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

@@ -78,8 +78,9 @@ interface RepositoryInterface extends \Countable
      *
      * @param string $query search query
      * @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' => '...')
      */
-    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];
 
-        $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);
 
-            // 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->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->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;
@@ -217,6 +219,13 @@ abstract class BitbucketDriver extends VcsDriver
             return $this->fallbackDriver->getChangeDate($identifier);
         }
 
+        if (strpos($identifier, '/') !== false) {
+            $branches = $this->getBranches();
+            if (isset($branches[$identifier])) {
+                $identifier = $branches[$identifier];
+            }
+        }
+
         $resource = sprintf(
             'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
             $this->owner,

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

@@ -154,8 +154,8 @@ class GitHubDriver extends VcsDriver
             }
 
             $composer = $this->getBaseComposerInformation($identifier);
-            if ($composer) {
 
+            if ($composer) {
                 // specials for github
                 if (!isset($composer['support']['source'])) {
                     $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
                 $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);

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

@@ -38,7 +38,7 @@ interface VcsDriverInterface
      *
      * @param  string $file
      * @param  string $identifier
-     * @return string
+     * @return string|null
      */
     public function getFileContent($file, $identifier);
 
@@ -46,7 +46,7 @@ interface VcsDriverInterface
      * Get the changedate for $identifier.
      *
      * @param  string    $identifier
-     * @return \DateTime
+     * @return \DateTime|null
      */
     public function getChangeDate($identifier);
 
@@ -73,7 +73,7 @@ interface VcsDriverInterface
 
     /**
      * @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);
 

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

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

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

@@ -17,7 +17,7 @@ interface VersionCacheInterface
     /**
      * @param string $version
      * @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);
 }

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

@@ -71,12 +71,11 @@ class AuthHelper
      * @param string $origin
      * @param int $statusCode HTTP status code that triggered this call
      * @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
-     * @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
      */
-    public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $warning = null, $headers = array())
+    public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $headers = array())
     {
         $storeAuth = false;
         $retry = false;
@@ -173,10 +172,6 @@ class AuthHelper
                 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>):');
             $username = $this->io->ask('      Username: ');
             $password = $this->io->askAndHideAnswer('      Password: ');

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

@@ -22,11 +22,17 @@ use Composer\Downloader\TransportException;
  */
 class Bitbucket
 {
+    /** @var IOInterface */
     private $io;
+    /** @var Config */
     private $config;
+    /** @var ProcessExecutor */
     private $process;
+    /** @var HttpDownloader */
     private $httpDownloader;
+    /** @var array */
     private $token = array();
+    /** @var int|null */
     private $time;
 
     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
 {
+    /** @var ProcessExecutor */
     private $processExecutor;
 
     public function __construct(ProcessExecutor $executor = null)
@@ -199,9 +200,15 @@ class Filesystem
      */
     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
-            if (!Platform::isWindows() || (usleep(350000) && !@$this->unlinkImplementation($path))) {
+            if (Platform::isWindows()) {
+                usleep(350000);
+                $unlinked = @$this->unlinkImplementation($path);
+            }
+            
+            if (!$unlinked) {
                 $error = error_get_last();
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
                 if (Platform::isWindows()) {
@@ -224,9 +231,15 @@ class Filesystem
      */
     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
-            if (!Platform::isWindows() || (usleep(350000) && !@rmdir($path))) {
+            if (Platform::isWindows()) {
+                usleep(350000);
+                $deleted = @rmdir($path);
+            }
+            
+            if (!$deleted) {
                 $error = error_get_last();
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
                 if (Platform::isWindows()) {
@@ -279,6 +292,7 @@ class Filesystem
         $this->ensureDirectoryExists($target);
 
         $result = true;
+        /** @var RecursiveDirectoryIterator $ri */
         foreach ($ri as $file) {
             $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
             if ($file->isDir()) {
@@ -428,7 +442,7 @@ class Filesystem
      */
     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 ProcessExecutor
+     */
     protected function getProcess()
     {
         return $this->processExecutor;
@@ -648,6 +665,20 @@ class Filesystem
     /**
      * 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.
      * @return bool
      */
@@ -659,22 +690,13 @@ class Filesystem
         if (!is_dir($junction) || is_link($junction)) {
             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(false);
         $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)) {
             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
 {
+    /** @var IOInterface */
     protected $io;
+    /** @var Config */
     protected $config;
+    /** @var ProcessExecutor */
     protected $process;
+    /** @var HttpDownloader */
     protected $httpDownloader;
 
     /**

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません