소스 검색

Merge branch 'master' into 2.0

Jordi Boggiano 5 년 전
부모
커밋
bc2a1d762a
56개의 변경된 파일415개의 추가작업 그리고 121개의 파일을 삭제
  1. 14 0
      CHANGELOG.md
  2. 36 36
      composer.lock
  3. 3 2
      doc/00-intro.md
  4. 5 0
      doc/06-config.md
  5. 3 0
      doc/articles/scripts.md
  6. 13 0
      doc/articles/troubleshooting.md
  7. 8 2
      res/composer-schema.json
  8. 3 0
      src/Composer/Autoload/AutoloadGenerator.php
  9. 1 1
      src/Composer/Autoload/ClassMapGenerator.php
  10. 2 0
      src/Composer/Command/AboutCommand.php
  11. 1 1
      src/Composer/Command/BaseDependencyCommand.php
  12. 2 0
      src/Composer/Command/ClearCacheCommand.php
  13. 53 19
      src/Composer/Command/ConfigCommand.php
  14. 1 1
      src/Composer/Command/DependsCommand.php
  15. 2 0
      src/Composer/Command/DumpAutoloadCommand.php
  16. 8 5
      src/Composer/Command/InitCommand.php
  17. 2 0
      src/Composer/Command/LicensesCommand.php
  18. 1 1
      src/Composer/Command/OutdatedCommand.php
  19. 1 1
      src/Composer/Command/ProhibitsCommand.php
  20. 24 2
      src/Composer/Command/RequireCommand.php
  21. 2 0
      src/Composer/Command/SearchCommand.php
  22. 2 0
      src/Composer/Command/SelfUpdateCommand.php
  23. 7 0
      src/Composer/Command/ShowCommand.php
  24. 1 1
      src/Composer/Command/StatusCommand.php
  25. 4 2
      src/Composer/Command/SuggestsCommand.php
  26. 3 0
      src/Composer/Config.php
  27. 5 5
      src/Composer/Console/Application.php
  28. 1 1
      src/Composer/DependencyResolver/Decisions.php
  29. 3 1
      src/Composer/Installer.php
  30. 23 2
      src/Composer/Repository/BaseRepository.php
  31. 12 2
      src/Composer/Repository/PathRepository.php
  32. 1 1
      src/Composer/Repository/Vcs/BitbucketDriver.php
  33. 1 1
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  34. 4 4
      src/Composer/Repository/Vcs/GitHubDriver.php
  35. 2 0
      src/Composer/Repository/Vcs/GitLabDriver.php
  36. 1 1
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  37. 1 1
      src/Composer/Repository/Vcs/HgDriver.php
  38. 24 1
      src/Composer/Util/AuthHelper.php
  39. 4 2
      src/Composer/Util/Filesystem.php
  40. 1 1
      src/Composer/Util/Git.php
  41. 4 4
      src/Composer/Util/Http/CurlDownloader.php
  42. 2 2
      src/Composer/Util/RemoteFilesystem.php
  43. 0 1
      src/Composer/Util/Zip.php
  44. 12 0
      tests/Composer/Test/ApplicationTest.php
  45. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
  46. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
  47. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php
  48. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php
  49. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
  50. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
  51. 5 1
      tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php
  52. 25 0
      tests/Composer/Test/Fixtures/installer/install-without-lock.test
  53. 25 0
      tests/Composer/Test/Fixtures/installer/update-without-lock.test
  54. 15 8
      tests/Composer/Test/InstallerTest.php
  55. 16 0
      tests/Composer/Test/Repository/PathRepositoryTest.php
  56. 8 8
      tests/Composer/Test/Util/FilesystemTest.php

+ 14 - 0
CHANGELOG.md

@@ -1,3 +1,16 @@
+### [1.9.0] 2019-08-02
+
+  * Breaking: artifact repositories with URLs containing port numbers and requiring authentication now require you to configure http-basic auth for the `host:port` pair explicitly
+  * Added a `--no-cache` flag available on all commands to run with the cache disabled
+  * Added PHP_BINARY as env var pointing to the PHP process when executing Composer scripts as shell scripts
+  * Added a `use-github-api` config option which can set the `no-api` flag on all GitHub VCS repositories declared
+  * Added a static helper you can preprend to a script to avoid process timeouts, `"Composer\\Config::disableProcessTimeout"`
+  * Added Event::getOriginatingEvent to retrieve an event's original event when a script handler forwards to another one
+  * Added support for autoloading directly from a phar file
+  * Fixed loading order of plugins to always initialize them in order of dependencies
+  * Fixed various network-mount related issues
+  * Fixed --ignore-platform-reqs not ignoring conflict rules against platform packages
+
 ### [1.8.6] 2019-06-11
 ### [1.8.6] 2019-06-11
 
 
   * Fixed handling of backslash-escapes handling in composer.json when using the require command
   * Fixed handling of backslash-escapes handling in composer.json when using the require command
@@ -751,6 +764,7 @@
 
 
   * Initial release
   * Initial release
 
 
+[1.9.0]: https://github.com/composer/composer/compare/1.8.6...1.9.0
 [1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6
 [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.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.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4

+ 36 - 36
composer.lock

@@ -8,25 +8,25 @@
     "packages": [
     "packages": [
         {
         {
             "name": "composer/ca-bundle",
             "name": "composer/ca-bundle",
-            "version": "1.1.4",
+            "version": "1.2.4",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/composer/ca-bundle.git",
                 "url": "https://github.com/composer/ca-bundle.git",
-                "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d"
+                "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
-                "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
+                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
+                "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
                 "ext-openssl": "*",
                 "ext-openssl": "*",
                 "ext-pcre": "*",
                 "ext-pcre": "*",
-                "php": "^5.3.2 || ^7.0"
+                "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             },
             "require-dev": {
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
                 "psr/log": "^1.0",
                 "psr/log": "^1.0",
                 "symfony/process": "^2.5 || ^3.0 || ^4.0"
                 "symfony/process": "^2.5 || ^3.0 || ^4.0"
             },
             },
@@ -60,7 +60,7 @@
                 "ssl",
                 "ssl",
                 "tls"
                 "tls"
             ],
             ],
-            "time": "2019-01-28T09:30:10+00:00"
+            "time": "2019-08-30T08:44:50+00:00"
         },
         },
         {
         {
             "name": "composer/semver",
             "name": "composer/semver",
@@ -126,16 +126,16 @@
         },
         },
         {
         {
             "name": "composer/spdx-licenses",
             "name": "composer/spdx-licenses",
-            "version": "1.5.1",
+            "version": "1.5.2",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/composer/spdx-licenses.git",
                 "url": "https://github.com/composer/spdx-licenses.git",
-                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d"
+                "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
-                "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
+                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5",
+                "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -182,7 +182,7 @@
                 "spdx",
                 "spdx",
                 "validator"
                 "validator"
             ],
             ],
-            "time": "2019-03-26T10:23:26+00:00"
+            "time": "2019-07-29T10:31:59+00:00"
         },
         },
         {
         {
             "name": "composer/xdebug-handler",
             "name": "composer/xdebug-handler",
@@ -697,16 +697,16 @@
         },
         },
         {
         {
             "name": "symfony/polyfill-ctype",
             "name": "symfony/polyfill-ctype",
-            "version": "v1.11.0",
+            "version": "v1.12.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "82ebae02209c21113908c229e9883c419720738a"
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
-                "reference": "82ebae02209c21113908c229e9883c419720738a",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -718,7 +718,7 @@
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
                 "branch-alias": {
                 "branch-alias": {
-                    "dev-master": "1.11-dev"
+                    "dev-master": "1.12-dev"
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
@@ -734,13 +734,13 @@
                 "MIT"
                 "MIT"
             ],
             ],
             "authors": [
             "authors": [
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                },
                 {
                 {
                     "name": "Gert de Pagter",
                     "name": "Gert de Pagter",
                     "email": "BackEndTea@gmail.com"
                     "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
                 }
             ],
             ],
             "description": "Symfony polyfill for ctype functions",
             "description": "Symfony polyfill for ctype functions",
@@ -751,20 +751,20 @@
                 "polyfill",
                 "polyfill",
                 "portable"
                 "portable"
             ],
             ],
-            "time": "2019-02-06T07:57:58+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         },
         {
         {
             "name": "symfony/polyfill-mbstring",
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.11.0",
+            "version": "v1.12.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
-                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -776,7 +776,7 @@
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
                 "branch-alias": {
                 "branch-alias": {
-                    "dev-master": "1.11-dev"
+                    "dev-master": "1.12-dev"
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
@@ -810,7 +810,7 @@
                 "portable",
                 "portable",
                 "shim"
                 "shim"
             ],
             ],
-            "time": "2019-02-06T07:57:58+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         },
         {
         {
             "name": "symfony/process",
             "name": "symfony/process",
@@ -968,22 +968,22 @@
         },
         },
         {
         {
             "name": "phpspec/prophecy",
             "name": "phpspec/prophecy",
-            "version": "1.8.0",
+            "version": "1.9.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
+                "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
-                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203",
+                "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
                 "doctrine/instantiator": "^1.0.2",
                 "doctrine/instantiator": "^1.0.2",
                 "php": "^5.3|^7.0",
                 "php": "^5.3|^7.0",
-                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
                 "sebastian/comparator": "^1.1|^2.0|^3.0",
                 "sebastian/comparator": "^1.1|^2.0|^3.0",
                 "sebastian/recursion-context": "^1.0|^2.0|^3.0"
                 "sebastian/recursion-context": "^1.0|^2.0|^3.0"
             },
             },
@@ -998,8 +998,8 @@
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
-                "psr-0": {
-                    "Prophecy\\": "src/"
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
                 }
                 }
             },
             },
             "notification-url": "https://packagist.org/downloads/",
             "notification-url": "https://packagist.org/downloads/",
@@ -1027,7 +1027,7 @@
                 "spy",
                 "spy",
                 "stub"
                 "stub"
             ],
             ],
-            "time": "2018-08-05T17:53:17+00:00"
+            "time": "2019-10-03T11:07:50+00:00"
         },
         },
         {
         {
             "name": "phpunit/php-code-coverage",
             "name": "phpunit/php-code-coverage",

+ 3 - 2
doc/00-intro.md

@@ -106,7 +106,8 @@ Linux distributions.
 > `mkdir -p /usr/local/bin`.
 > `mkdir -p /usr/local/bin`.
 
 
 > **Note:** For information on changing your PATH, please read the
 > **Note:** For information on changing your PATH, please read the
-> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google.
+> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use
+> your search engine of choice.
 
 
 Now run `composer` in order to run Composer instead of `php composer.phar`.
 Now run `composer` in order to run Composer instead of `php composer.phar`.
 
 
@@ -139,7 +140,7 @@ C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
 Add the directory to your PATH environment variable if it isn't already.
 Add the directory to your PATH environment variable if it isn't already.
 For information on changing your PATH variable, please see
 For information on changing your PATH variable, please see
 [this article](https://www.computerhope.com/issues/ch000549.htm) and/or
 [this article](https://www.computerhope.com/issues/ch000549.htm) and/or
-use Google.
+use your search engine of choice.
 
 
 Close your current terminal. Test usage with a new terminal:
 Close your current terminal. Test usage with a new terminal:
 
 

+ 5 - 0
doc/06-config.md

@@ -296,4 +296,9 @@ Example:
 Defaults to `true`. If set to `false`, Composer will not create `.htaccess` files
 Defaults to `true`. If set to `false`, Composer will not create `.htaccess` files
 in the composer home, cache, and data directories.
 in the composer home, cache, and data directories.
 
 
+## lock
+
+Defaults to `true`. If set to `false`, Composer will not create a `composer.lock` 
+file.
+
 ← [Repositories](05-repositories.md)  |  [Community](07-community.md) →
 ← [Repositories](05-repositories.md)  |  [Community](07-community.md) →

+ 3 - 0
doc/articles/scripts.md

@@ -339,6 +339,9 @@ One limitation of this is that you can not call multiple commands in
 a row like `@php install && @php foo`. You must split them up in a
 a row like `@php install && @php foo`. You must split them up in a
 JSON array of commands.
 JSON array of commands.
 
 
+You can also call a shell/bash script, which will have the path to
+the PHP executable available in it as a `PHP_BINARY` env var.
+
 ## Custom descriptions.
 ## Custom descriptions.
 
 
 You can set custom script descriptions with the following in your `composer.json`:
 You can set custom script descriptions with the following in your `composer.json`:

+ 13 - 0
doc/articles/troubleshooting.md

@@ -211,6 +211,19 @@ To enable the swap you can use for example:
 ```
 ```
 You can make a permanent swap file following this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04).
 You can make a permanent swap file following this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04).
 
 
+## proc_open(): failed to open stream errors (Windows)
+
+If composer shows proc_open(NUL) errors on Windows:
+
+`proc_open(NUL): failed to open stream: No such file or directory`
+
+This could be happening because you are working in a _OneDrive_ directory and
+using a version of PHP that does not support the file system semantics of this
+service. The issue was fixed in PHP 7.2.23 and 7.3.10.
+
+Alternatively it could be because the Windows Null Service is not enabled. For
+more information, see this [issue](https://github.com/composer/composer/issues/7186#issuecomment-373134916).
+
 ## Degraded Mode
 ## Degraded Mode
 
 
 Due to some intermittent issues on Travis and other systems, we introduced a
 Due to some intermittent issues on Travis and other systems, we introduced a

+ 8 - 2
res/composer-schema.json

@@ -11,7 +11,8 @@
         },
         },
         "type": {
         "type": {
             "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
             "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
-            "type": "string"
+            "type": "string",
+            "pattern": "^[a-z0-9-]+$"
         },
         },
         "target-dir": {
         "target-dir": {
             "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
             "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
@@ -39,7 +40,8 @@
         },
         },
         "version": {
         "version": {
             "type": "string",
             "type": "string",
-            "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes."
+            "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.",
+            "pattern": "^v?\\d+(((\\.\\d+)?\\.\\d+)?\\.\\d+)?"
         },
         },
         "time": {
         "time": {
             "type": "string",
             "type": "string",
@@ -290,6 +292,10 @@
                 "sort-packages": {
                 "sort-packages": {
                     "type": "boolean",
                     "type": "boolean",
                     "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
                     "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
+                },
+                "lock": {
+                    "type": "boolean",
+                    "description": "Defaults to true. If set to false, Composer will not create a composer.lock file."
                 }
                 }
             }
             }
         },
         },

+ 3 - 0
src/Composer/Autoload/AutoloadGenerator.php

@@ -592,6 +592,9 @@ class ComposerAutoloaderInit$suffix
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::\$loader) {
         if (null !== self::\$loader) {

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

@@ -173,7 +173,7 @@ class ClassMapGenerator
             }
             }
         }
         }
         // strip non-php blocks in the file
         // strip non-php blocks in the file
-        $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
+        $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?><?', $contents);
         // strip trailing non-php code if needed
         // strip trailing non-php code if needed
         $pos = strrpos($contents, '?>');
         $pos = strrpos($contents, '?>');
         if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
         if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {

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

@@ -42,5 +42,7 @@ EOT
 See https://getcomposer.org/ for more information.</comment>
 See https://getcomposer.org/ for more information.</comment>
 EOT
 EOT
         );
         );
+
+        return 0;
     }
     }
 }
 }

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

@@ -62,7 +62,7 @@ class BaseDependencyCommand extends BaseCommand
      * @param  InputInterface  $input
      * @param  InputInterface  $input
      * @param  OutputInterface $output
      * @param  OutputInterface $output
      * @param  bool            $inverted Whether to invert matching process (why-not vs why behaviour)
      * @param  bool            $inverted Whether to invert matching process (why-not vs why behaviour)
-     * @return int|null        Exit code of the operation.
+     * @return int             Exit code of the operation.
      */
      */
     protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
     protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
     {
     {

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

@@ -70,5 +70,7 @@ EOT
         }
         }
 
 
         $io->writeError('<info>All caches cleared.</info>');
         $io->writeError('<info>All caches cleared.</info>');
+
+        return 0;
     }
     }
 }
 }

+ 53 - 19
src/Composer/Command/ConfigCommand.php

@@ -412,6 +412,7 @@ EOT
             ),
             ),
             'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
             'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
             'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
             'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
+            'lock' => array($booleanValidator, $booleanNormalizer),
         );
         );
         $multiConfigValues = array(
         $multiConfigValues = array(
             'github-protocols' => array(
             'github-protocols' => array(
@@ -463,13 +464,19 @@ EOT
                 $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
                 $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
             }
             }
 
 
-            return $this->configSource->removeConfigSetting($settingKey);
+            $this->configSource->removeConfigSetting($settingKey);
+
+            return 0;
         }
         }
         if (isset($uniqueConfigValues[$settingKey])) {
         if (isset($uniqueConfigValues[$settingKey])) {
-            return $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
+            $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
+
+            return 0;
         }
         }
         if (isset($multiConfigValues[$settingKey])) {
         if (isset($multiConfigValues[$settingKey])) {
-            return $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
+            $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
+
+            return 0;
         }
         }
 
 
         // handle properties
         // handle properties
@@ -530,38 +537,51 @@ EOT
             throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
             throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
         }
         }
         if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
         if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
-            return $this->configSource->removeProperty($settingKey);
+            $this->configSource->removeProperty($settingKey);
+
+            return 0;
         }
         }
         if (isset($uniqueProps[$settingKey])) {
         if (isset($uniqueProps[$settingKey])) {
-            return $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
+            $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
+
+            return 0;
         }
         }
         if (isset($multiProps[$settingKey])) {
         if (isset($multiProps[$settingKey])) {
-            return $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
+            $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
+
+            return 0;
         }
         }
 
 
         // handle repositories
         // handle repositories
         if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
         if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {
             if ($input->getOption('unset')) {
-                return $this->configSource->removeRepository($matches[1]);
+                $this->configSource->removeRepository($matches[1]);
+
+                return 0;
             }
             }
 
 
             if (2 === count($values)) {
             if (2 === count($values)) {
-                return $this->configSource->addRepository($matches[1], array(
+                $this->configSource->addRepository($matches[1], array(
                     'type' => $values[0],
                     'type' => $values[0],
                     'url' => $values[1],
                     'url' => $values[1],
                 ));
                 ));
+
+                return 0;
             }
             }
 
 
             if (1 === count($values)) {
             if (1 === count($values)) {
                 $value = strtolower($values[0]);
                 $value = strtolower($values[0]);
                 if (true === $booleanValidator($value)) {
                 if (true === $booleanValidator($value)) {
                     if (false === $booleanNormalizer($value)) {
                     if (false === $booleanNormalizer($value)) {
-                        return $this->configSource->addRepository($matches[1], false);
+                        $this->configSource->addRepository($matches[1], false);
+
+                        return 0;
                     }
                     }
                 } else {
                 } else {
                     $value = JsonFile::parseJson($values[0]);
                     $value = JsonFile::parseJson($values[0]);
+                    $this->configSource->addRepository($matches[1], $value);
 
 
-                    return $this->configSource->addRepository($matches[1], $value);
+                    return 0;
                 }
                 }
             }
             }
 
 
@@ -571,22 +591,32 @@ EOT
         // handle extra
         // handle extra
         if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
         if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {
             if ($input->getOption('unset')) {
-                return $this->configSource->removeProperty($settingKey);
+                $this->configSource->removeProperty($settingKey);
+
+                return 0;
             }
             }
 
 
-            return $this->configSource->addProperty($settingKey, $values[0]);
+            $this->configSource->addProperty($settingKey, $values[0]);
+
+            return 0;
         }
         }
 
 
         // handle platform
         // handle platform
         if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
         if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {
             if ($input->getOption('unset')) {
-                return $this->configSource->removeConfigSetting($settingKey);
+                $this->configSource->removeConfigSetting($settingKey);
+
+                return 0;
             }
             }
 
 
-            return $this->configSource->addConfigSetting($settingKey, $values[0]);
+            $this->configSource->addConfigSetting($settingKey, $values[0]);
+
+            return 0;
         }
         }
         if ($settingKey === 'platform' && $input->getOption('unset')) {
         if ($settingKey === 'platform' && $input->getOption('unset')) {
-            return $this->configSource->removeConfigSetting($settingKey);
+            $this->configSource->removeConfigSetting($settingKey);
+
+            return 0;
         }
         }
 
 
         // handle auth
         // handle auth
@@ -595,7 +625,7 @@ EOT
                 $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                 $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                 $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                 $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
 
 
-                return;
+                return 0;
             }
             }
 
 
             if ($matches[1] === 'bitbucket-oauth') {
             if ($matches[1] === 'bitbucket-oauth') {
@@ -618,16 +648,20 @@ EOT
                 $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
                 $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
             }
             }
 
 
-            return;
+            return 0;
         }
         }
 
 
         // handle script
         // handle script
         if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
         if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {
             if ($input->getOption('unset')) {
-                return $this->configSource->removeProperty($settingKey);
+                $this->configSource->removeProperty($settingKey);
+
+                return 0;
             }
             }
 
 
-            return $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);
+            $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);
+
+            return 0;
         }
         }
 
 
         throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
         throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');

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

@@ -48,7 +48,7 @@ EOT
      *
      *
      * @param  InputInterface  $input
      * @param  InputInterface  $input
      * @param  OutputInterface $output
      * @param  OutputInterface $output
-     * @return int|null
+     * @return int
      */
      */
     protected function execute(InputInterface $input, OutputInterface $output)
     protected function execute(InputInterface $input, OutputInterface $output)
     {
     {

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

@@ -84,5 +84,7 @@ EOT
         } else {
         } else {
             $this->getIO()->overwriteError('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
             $this->getIO()->overwriteError('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
         }
         }
+
+        return 0;
     }
     }
 }
 }

+ 8 - 5
src/Composer/Command/InitCommand.php

@@ -152,6 +152,8 @@ EOT
         if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
         if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
             $this->installDependencies($output);
             $this->installDependencies($output);
         }
         }
+
+        return 0;
     }
     }
 
 
     /**
     /**
@@ -400,7 +402,7 @@ EOT
         return $this->repos;
         return $this->repos;
     }
     }
 
 
-    protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true)
+    final protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true, $fixed = false)
     {
     {
         if ($requires) {
         if ($requires) {
             $requires = $this->normalizeRequirements($requires);
             $requires = $this->normalizeRequirements($requires);
@@ -410,7 +412,7 @@ EOT
             foreach ($requires as $requirement) {
             foreach ($requires as $requirement) {
                 if (!isset($requirement['version'])) {
                 if (!isset($requirement['version'])) {
                     // determine the best version automatically
                     // determine the best version automatically
-                    list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability);
+                    list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, null, null, $fixed);
                     $requirement['version'] = $version;
                     $requirement['version'] = $version;
 
 
                     // replace package name from packagist.org
                     // replace package name from packagist.org
@@ -423,7 +425,7 @@ EOT
                     ));
                     ));
                 } else {
                 } else {
                     // check that the specified version/constraint exists before we proceed
                     // check that the specified version/constraint exists before we proceed
-                    list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev');
+                    list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev', $fixed);
 
 
                     // replace package name from packagist.org
                     // replace package name from packagist.org
                     $requirement['name'] = $name;
                     $requirement['name'] = $name;
@@ -700,10 +702,11 @@ EOT
      * @param  string                    $preferredStability
      * @param  string                    $preferredStability
      * @param  string|null               $requiredVersion
      * @param  string|null               $requiredVersion
      * @param  string                    $minimumStability
      * @param  string                    $minimumStability
+     * @param  bool                      $fixed
      * @throws \InvalidArgumentException
      * @throws \InvalidArgumentException
      * @return array                     name version
      * @return array                     name version
      */
      */
-    private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null)
+    private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = null)
     {
     {
         // find the latest version allowed in this repo set
         // find the latest version allowed in this repo set
         $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
         $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
@@ -777,7 +780,7 @@ EOT
 
 
         return array(
         return array(
             $package->getPrettyName(),
             $package->getPrettyName(),
-            $versionSelector->findRecommendedRequireVersion($package),
+            $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
         );
         );
     }
     }
 
 

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

@@ -110,6 +110,8 @@ EOT
             default:
             default:
                 throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
                 throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
         }
         }
+
+        return 0;
     }
     }
 
 
     /**
     /**

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

@@ -59,7 +59,7 @@ EOT
     protected function execute(InputInterface $input, OutputInterface $output)
     protected function execute(InputInterface $input, OutputInterface $output)
     {
     {
         $args = array(
         $args = array(
-            'show',
+            'command' => 'show',
             '--latest' => true,
             '--latest' => true,
         );
         );
         if (!$input->getOption('all')) {
         if (!$input->getOption('all')) {

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

@@ -48,7 +48,7 @@ EOT
      *
      *
      * @param  InputInterface  $input
      * @param  InputInterface  $input
      * @param  OutputInterface $output
      * @param  OutputInterface $output
-     * @return int|null
+     * @return int
      */
      */
     protected function execute(InputInterface $input, OutputInterface $output)
     protected function execute(InputInterface $input, OutputInterface $output)
     {
     {

+ 24 - 2
src/Composer/Command/RequireCommand.php

@@ -49,6 +49,7 @@ class RequireCommand extends InitCommand
                 new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                 new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                 new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
+                new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                 new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                 new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
                 new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
@@ -99,7 +100,9 @@ EOT
 
 
             return 1;
             return 1;
         }
         }
-        if (!is_readable($this->file)) {
+        // check for readability by reading the file as is_readable can not be trusted on network-mounts
+        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
+        if (!is_readable($this->file) && false === Silencer::call('file_get_contents', $this->file)) {
             $io->writeError('<error>'.$this->file.' is not readable.</error>');
             $io->writeError('<error>'.$this->file.' is not readable.</error>');
 
 
             return 1;
             return 1;
@@ -120,6 +123,25 @@ EOT
             return 1;
             return 1;
         }
         }
 
 
+        if ($input->getOption('fixed') === true) {
+            $config = $this->json->read();
+
+            $packageType = empty($config['type']) ? 'library' : $config['type'];
+
+            /**
+             * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
+             */
+            if ($packageType !== 'project') {
+                $io->writeError('<error>"--fixed" option is allowed for "project" package types only to prevent possible misuses.</error>');
+
+                if (empty($config['type'])) {
+                    $io->writeError('<error>If your package is not library, you should explicitly specify "type" parameter in composer.json.</error>');
+                }
+
+                return 1;
+            }
+        }
+
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $repos = $composer->getRepositoryManager()->getRepositories();
         $repos = $composer->getRepositoryManager()->getRepositories();
 
 
@@ -137,7 +159,7 @@ EOT
         }
         }
 
 
         $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
         $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
-        $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'));
+        $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'), $input->getOption('fixed'));
 
 
         $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
         $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
         $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
         $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';

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

@@ -79,5 +79,7 @@ EOT
         foreach ($results as $result) {
         foreach ($results as $result) {
             $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
             $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
         }
         }
+
+        return 0;
     }
     }
 }
 }

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

@@ -254,6 +254,8 @@ TAGSPUBKEY
         } else {
         } else {
             $io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
             $io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
         }
         }
+
+        return 0;
     }
     }
 
 
     protected function fetchKeys(IOInterface $io, Config $config)
     protected function fetchKeys(IOInterface $io, Config $config)

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

@@ -129,6 +129,12 @@ EOT
             return 1;
             return 1;
         }
         }
 
 
+        if ($input->getOption('tree') && $input->getOption('path')) {
+            $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)');
+
+            return 1;
+        }
+
         $format = $input->getOption('format');
         $format = $input->getOption('format');
         if (!in_array($format, array('text', 'json'))) {
         if (!in_array($format, array('text', 'json'))) {
             $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
             $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
@@ -586,6 +592,7 @@ EOT
         }
         }
         $io->write('<info>type</info>     : ' . $package->getType());
         $io->write('<info>type</info>     : ' . $package->getType());
         $this->printLicenses($package);
         $this->printLicenses($package);
+        $io->write('<info>homepage</info> : ' . $package->getHomepage());
         $io->write('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
         $io->write('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
         $io->write('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
         $io->write('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
         if ($installedRepo->hasPackage($package)) {
         if ($installedRepo->hasPackage($package)) {

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

@@ -61,7 +61,7 @@ EOT
     /**
     /**
      * @param  InputInterface  $input
      * @param  InputInterface  $input
      * @param  OutputInterface $output
      * @param  OutputInterface $output
-     * @return int|null
+     * @return int
      */
      */
     protected function execute(InputInterface $input, OutputInterface $output)
     protected function execute(InputInterface $input, OutputInterface $output)
     {
     {

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

@@ -93,7 +93,7 @@ EOT
                 continue;
                 continue;
             }
             }
             foreach ($package['suggest'] as $suggestion => $reason) {
             foreach ($package['suggest'] as $suggestion => $reason) {
-                if (false === strpos('/', $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
+                if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
                     continue;
                     continue;
                 }
                 }
                 if (!isset($installed[$suggestion])) {
                 if (!isset($installed[$suggestion])) {
@@ -121,7 +121,7 @@ EOT
                 $io->write(sprintf('<info>%s</info>', $suggestion));
                 $io->write(sprintf('<info>%s</info>', $suggestion));
             }
             }
 
 
-            return null;
+            return 0;
         }
         }
 
 
         // Grouped by package
         // Grouped by package
@@ -151,5 +151,7 @@ EOT
                 $io->write('');
                 $io->write('');
             }
             }
         }
         }
+
+        return 0;
     }
     }
 }
 }

+ 3 - 0
src/Composer/Config.php

@@ -63,6 +63,7 @@ class Config
         'archive-dir' => '.',
         'archive-dir' => '.',
         'htaccess-protect' => true,
         'htaccess-protect' => true,
         'use-github-api' => true,
         'use-github-api' => true,
+        'lock' => true,
         // valid keys without defaults (auth config stuff):
         // valid keys without defaults (auth config stuff):
         // bitbucket-oauth
         // bitbucket-oauth
         // github-oauth
         // github-oauth
@@ -329,6 +330,8 @@ class Config
                 return $this->config[$key] !== 'false' && (bool) $this->config[$key];
                 return $this->config[$key] !== 'false' && (bool) $this->config[$key];
             case 'use-github-api':
             case 'use-github-api':
                 return $this->config[$key] !== 'false' && (bool) $this->config[$key];
                 return $this->config[$key] !== 'false' && (bool) $this->config[$key];
+            case 'lock':
+                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
             default:
             default:
                 if (!isset($this->config[$key])) {
                 if (!isset($this->config[$key])) {
                     return null;
                     return null;

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

@@ -113,6 +113,10 @@ class Application extends BaseApplication
     {
     {
         $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
         $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
 
 
+        if (getenv('COMPOSER_NO_INTERACTION')) {
+            $input->setInteractive(false);
+        }
+
         $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
         $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
             new QuestionHelper(),
             new QuestionHelper(),
         )));
         )));
@@ -208,11 +212,7 @@ class Application extends BaseApplication
                 $io->writeError(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
                 $io->writeError(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
             }
             }
 
 
-            if (getenv('COMPOSER_NO_INTERACTION')) {
-                $input->setInteractive(false);
-            }
-
-            if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER')) {
+            if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER') && !file_exists('/.dockerenv')) {
                 if (function_exists('posix_getuid') && posix_getuid() === 0) {
                 if (function_exists('posix_getuid') && posix_getuid() === 0) {
                     if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
                     if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
                         $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');
                         $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');

+ 1 - 1
src/Composer/DependencyResolver/Decisions.php

@@ -183,7 +183,7 @@ class Decisions implements \Iterator, \Countable
 
 
         $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
         $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
         if ($previousDecision != 0) {
         if ($previousDecision != 0) {
-            $literalString = $this->pool->literalToString($literal);
+            $literalString = $this->pool->literalToPrettyString($literal, array());
             $package = $this->pool->literalToPackage($literal);
             $package = $this->pool->literalToPackage($literal);
             throw new SolverBugException(
             throw new SolverBugException(
                 "Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."
                 "Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."

+ 3 - 1
src/Composer/Installer.php

@@ -119,7 +119,7 @@ class Installer
     protected $preferStable = false;
     protected $preferStable = false;
     protected $preferLowest = false;
     protected $preferLowest = false;
     protected $skipSuggest = false;
     protected $skipSuggest = false;
-    protected $writeLock = true;
+    protected $writeLock;
     protected $executeOperations = true;
     protected $executeOperations = true;
 
 
     /**
     /**
@@ -165,6 +165,8 @@ class Installer
         $this->installationManager = $installationManager;
         $this->installationManager = $installationManager;
         $this->eventDispatcher = $eventDispatcher;
         $this->eventDispatcher = $eventDispatcher;
         $this->autoloadGenerator = $autoloadGenerator;
         $this->autoloadGenerator = $autoloadGenerator;
+
+        $this->writeLock = $config->get('lock');
     }
     }
 
 
     /**
     /**

+ 23 - 2
src/Composer/Repository/BaseRepository.php

@@ -98,6 +98,27 @@ abstract class BaseRepository implements RepositoryInterface
             // Replacements are considered valid reasons for a package to be installed during forward resolution
             // Replacements are considered valid reasons for a package to be installed during forward resolution
             if (!$invert) {
             if (!$invert) {
                 $links += $package->getReplaces();
                 $links += $package->getReplaces();
+
+                // On forward search, check if any replaced package was required and add the replaced
+                // packages to the list of needles. Contrary to the cross-reference link check below,
+                // replaced packages are the target of links.
+                foreach ($package->getReplaces() as $link) {
+                    foreach ($needles as $needle) {
+                        if ($link->getSource() === $needle) {
+                            if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
+                                // already displayed this node's dependencies, cutting short
+                                if (in_array($link->getTarget(), $packagesInTree)) {
+                                    $results[] = array($package, $link, false);
+                                    continue;
+                                }
+                                $packagesInTree[] = $link->getTarget();
+                                $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : array();
+                                $results[] = array($package, $link, $dependents);
+                                $needles[] = $link->getTarget();
+                            }
+                        }
+                    }
+                }
             }
             }
 
 
             // Require-dev is only relevant for the root package
             // Require-dev is only relevant for the root package
@@ -112,12 +133,12 @@ abstract class BaseRepository implements RepositoryInterface
                         if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                         if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                             // already displayed this node's dependencies, cutting short
                             // already displayed this node's dependencies, cutting short
                             if (in_array($link->getSource(), $packagesInTree)) {
                             if (in_array($link->getSource(), $packagesInTree)) {
-                                $results[$link->getSource()] = array($package, $link, false);
+                                $results[] = array($package, $link, false);
                                 continue;
                                 continue;
                             }
                             }
                             $packagesInTree[] = $link->getSource();
                             $packagesInTree[] = $link->getSource();
                             $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
                             $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
-                            $results[$link->getSource()] = array($package, $link, $dependents);
+                            $results[] = array($package, $link, $dependents);
                         }
                         }
                     }
                     }
                 }
                 }

+ 12 - 2
src/Composer/Repository/PathRepository.php

@@ -125,7 +125,13 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
     {
     {
         parent::initialize();
         parent::initialize();
 
 
-        foreach ($this->getUrlMatches() as $url) {
+        $urlMatches = $this->getUrlMatches();
+
+        if (empty($urlMatches)) {
+            throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist');
+        }
+
+        foreach ($urlMatches as $url) {
             $path = realpath($url) . DIRECTORY_SEPARATOR;
             $path = realpath($url) . DIRECTORY_SEPARATOR;
             $composerFilePath = $path.'composer.json';
             $composerFilePath = $path.'composer.json';
 
 
@@ -155,7 +161,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
 
 
             if (!isset($package['version'])) {
             if (!isset($package['version'])) {
                 $versionData = $this->versionGuesser->guessVersion($package, $path);
                 $versionData = $this->versionGuesser->guessVersion($package, $path);
-                $package['version'] = $versionData['pretty_version'] ?: 'dev-master';
+                if (is_array($versionData) && $versionData['pretty_version']) {
+                    $package['version'] = $versionData['pretty_version'];
+                } else {
+                    $package['version'] = 'dev-master';
+                }
             }
             }
 
 
             $output = '';
             $output = '';

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

@@ -47,7 +47,7 @@ abstract class BitbucketDriver extends VcsDriver
      */
      */
     public function initialize()
     public function initialize()
     {
     {
-        preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#', $this->url, $match);
+        preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#i', $this->url, $match);
         $this->owner = $match[1];
         $this->owner = $match[1];
         $this->repository = $match[2];
         $this->repository = $match[2];
         $this->originUrl = 'bitbucket.org';
         $this->originUrl = 'bitbucket.org';

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

@@ -53,7 +53,7 @@ class GitBitbucketDriver extends BitbucketDriver
      */
      */
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
     {
-        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#i', $url)) {
             return false;
             return false;
         }
         }
 
 

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

@@ -48,10 +48,10 @@ class GitHubDriver extends VcsDriver
      */
      */
     public function initialize()
     public function initialize()
     {
     {
-        preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
+        preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
         $this->owner = $match[3];
         $this->owner = $match[3];
         $this->repository = $match[4];
         $this->repository = $match[4];
-        $this->originUrl = !empty($match[1]) ? $match[1] : $match[2];
+        $this->originUrl = strtolower(!empty($match[1]) ? $match[1] : $match[2]);
         if ($this->originUrl === 'www.github.com') {
         if ($this->originUrl === 'www.github.com') {
             $this->originUrl = 'github.com';
             $this->originUrl = 'github.com';
         }
         }
@@ -270,12 +270,12 @@ class GitHubDriver extends VcsDriver
      */
      */
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
     {
-        if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
+        if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
             return false;
             return false;
         }
         }
 
 
         $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3];
         $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3];
-        if (!in_array(preg_replace('{^www\.}i', '', $originUrl), $config->get('github-domains'))) {
+        if (!in_array(strtolower(preg_replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) {
             return false;
             return false;
         }
         }
 
 

+ 2 - 0
src/Composer/Repository/Vcs/GitLabDriver.php

@@ -497,6 +497,8 @@ class GitLabDriver extends VcsDriver
      */
      */
     private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber)
     private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber)
     {
     {
+        $guessedDomain = strtolower($guessedDomain);
+
         if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
         if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
             if ($portNumber) {
             if ($portNumber) {
                 return $guessedDomain.':'.$portNumber;
                 return $guessedDomain.':'.$portNumber;

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

@@ -53,7 +53,7 @@ class HgBitbucketDriver extends BitbucketDriver
      */
      */
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
     {
-        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#i', $url)) {
             return false;
             return false;
         }
         }
 
 

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

@@ -71,7 +71,7 @@ class HgDriver extends VcsDriver
                     return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
                     return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
                 };
                 };
 
 
-                $hgUtils->runCommand($command, $this->url, $this->repoDir);
+                $hgUtils->runCommand($command, $this->url, null);
             }
             }
         }
         }
 
 

+ 24 - 1
src/Composer/Util/AuthHelper.php

@@ -23,6 +23,7 @@ class AuthHelper
 {
 {
     protected $io;
     protected $io;
     protected $config;
     protected $config;
+    private $displayedOriginAuthentications = array();
 
 
     public function __construct(IOInterface $io, Config $config)
     public function __construct(IOInterface $io, Config $config)
     {
     {
@@ -172,7 +173,7 @@ class AuthHelper
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
             }
             }
 
 
-            $this->io->writeError('    Authentication required (<info>'.parse_url($url, PHP_URL_HOST).'</info>):');
+            $this->io->writeError('    Authentication required (<info>'.$origin.'</info>):');
             $username = $this->io->ask('      Username: ');
             $username = $this->io->ask('      Username: ');
             $password = $this->io->askAndHideAnswer('      Password: ');
             $password = $this->io->askAndHideAnswer('      Password: ');
             $this->io->setAuthentication($origin, $username, $password);
             $this->io->setAuthentication($origin, $username, $password);
@@ -193,14 +194,18 @@ class AuthHelper
     public function addAuthenticationHeader(array $headers, $origin, $url)
     public function addAuthenticationHeader(array $headers, $origin, $url)
     {
     {
         if ($this->io->hasAuthentication($origin)) {
         if ($this->io->hasAuthentication($origin)) {
+            $authenticationDisplayMessage = null;
             $auth = $this->io->getAuthentication($origin);
             $auth = $this->io->getAuthentication($origin);
             if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
             if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
                 $headers[] = 'Authorization: token '.$auth['username'];
                 $headers[] = 'Authorization: token '.$auth['username'];
+                $authenticationDisplayMessage = 'Using GitHub token authentication';
             } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
             } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
                 if ($auth['password'] === 'oauth2') {
                 if ($auth['password'] === 'oauth2') {
                     $headers[] = 'Authorization: Bearer '.$auth['username'];
                     $headers[] = 'Authorization: Bearer '.$auth['username'];
+                    $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
                 } elseif ($auth['password'] === 'private-token') {
                 } elseif ($auth['password'] === 'private-token') {
                     $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
                     $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
+                    $authenticationDisplayMessage = 'Using GitLab private token authentication';
                 }
                 }
             } elseif (
             } elseif (
                 'bitbucket.org' === $origin
                 'bitbucket.org' === $origin
@@ -209,10 +214,17 @@ class AuthHelper
             ) {
             ) {
                 if (!$this->isPublicBitBucketDownload($url)) {
                 if (!$this->isPublicBitBucketDownload($url)) {
                     $headers[] = 'Authorization: Bearer ' . $auth['password'];
                     $headers[] = 'Authorization: Bearer ' . $auth['password'];
+                    $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
                 }
                 }
             } else {
             } else {
                 $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                 $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                 $headers[] = 'Authorization: Basic '.$authStr;
                 $headers[] = 'Authorization: Basic '.$authStr;
+                $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"';
+            }
+
+            if ($authenticationDisplayMessage && !in_array($origin, $this->displayedOriginAuthentications, true)) {
+                $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG);
+                $this->displayedOriginAuthentications[] = $origin;
             }
             }
         }
         }
 
 
@@ -243,4 +255,15 @@ class AuthHelper
 
 
         return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
         return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
     }
     }
+
+    /**
+     * @param string $url
+     * @return string
+     */
+    public function stripCredentialsFromUrl($url)
+    {
+        // GitHub repository rename result in redirect locations containing the access_token as GET parameter
+        // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
+        return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
+    }
 }
 }

+ 4 - 2
src/Composer/Util/Filesystem.php

@@ -687,12 +687,14 @@ class Filesystem
         if (!Platform::isWindows()) {
         if (!Platform::isWindows()) {
             return false;
             return false;
         }
         }
+
+        // Important to clear all caches first
+        clearstatcache(true, $junction);
+
         if (!is_dir($junction) || is_link($junction)) {
         if (!is_dir($junction) || is_link($junction)) {
             return false;
             return false;
         }
         }
 
 
-        // Important to clear all caches first
-        clearstatcache(true, $junction);
         $stat = lstat($junction);
         $stat = lstat($junction);
 
 
         // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
         // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)

+ 1 - 1
src/Composer/Util/Git.php

@@ -193,7 +193,7 @@ class Git
                         }
                         }
                     }
                     }
 
 
-                    $this->io->writeError('    Authentication required (<info>' . parse_url($url, PHP_URL_HOST) . '</info>):');
+                    $this->io->writeError('    Authentication required (<info>' . $match[2] . '</info>):');
                     $auth = array(
                     $auth = array(
                         'username' => $this->io->ask('      Username: ', $defaultUsername),
                         'username' => $this->io->ask('      Username: ', $defaultUsername),
                         'password' => $this->io->askAndHideAnswer('      Password: '),
                         'password' => $this->io->askAndHideAnswer('      Password: '),

+ 4 - 4
src/Composer/Util/Http/CurlDownloader.php

@@ -195,7 +195,7 @@ class CurlDownloader
         $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
         $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
         $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
         $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
         if ($attributes['redirects'] === 0) {
         if ($attributes['redirects'] === 0) {
-            $this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG);
+            $this->io->writeError('Downloading ' . $this->authHelper->stripCredentialsFromUrl($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
         }
         }
 
 
         $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
         $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
@@ -254,12 +254,12 @@ class CurlDownloader
                         $contents = stream_get_contents($job['bodyHandle']);
                         $contents = stream_get_contents($job['bodyHandle']);
                     }
                     }
                     $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
                     $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
-                    $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
+                    $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
                 } else {
                 } else {
                     rewind($job['bodyHandle']);
                     rewind($job['bodyHandle']);
                     $contents = stream_get_contents($job['bodyHandle']);
                     $contents = stream_get_contents($job['bodyHandle']);
                     $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
                     $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
-                    $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
+                    $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
                 }
                 }
                 fclose($job['bodyHandle']);
                 fclose($job['bodyHandle']);
 
 
@@ -362,7 +362,7 @@ class CurlDownloader
         }
         }
 
 
         if (!empty($targetUrl)) {
         if (!empty($targetUrl)) {
-            $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG);
+            $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
 
 
             return $targetUrl;
             return $targetUrl;
         }
         }

+ 2 - 2
src/Composer/Util/RemoteFilesystem.php

@@ -246,7 +246,7 @@ class RemoteFilesystem
 
 
         $actualContextOptions = stream_context_get_options($ctx);
         $actualContextOptions = stream_context_get_options($ctx);
         $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
         $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
-        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG);
+        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $this->authHelper->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
         unset($origFileUrl, $actualContextOptions);
         unset($origFileUrl, $actualContextOptions);
 
 
         // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
         // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
@@ -704,7 +704,7 @@ class RemoteFilesystem
             $this->redirects++;
             $this->redirects++;
 
 
             $this->io->writeError('', true, IOInterface::DEBUG);
             $this->io->writeError('', true, IOInterface::DEBUG);
-            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl), true, IOInterface::DEBUG);
+            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
 
 
             $additionalOptions['redirects'] = $this->redirects;
             $additionalOptions['redirects'] = $this->redirects;
 
 

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

@@ -21,7 +21,6 @@ class Zip
      * Gets content of the root composer.json inside a ZIP archive.
      * Gets content of the root composer.json inside a ZIP archive.
      *
      *
      * @param string $pathToZip
      * @param string $pathToZip
-     * @param string $filename
      *
      *
      * @return string|null
      * @return string|null
      */
      */

+ 12 - 0
tests/Composer/Test/ApplicationTest.php

@@ -25,12 +25,18 @@ class ApplicationTest extends TestCase
         $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
         $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
         $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
         $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
 
 
+        putenv('COMPOSER_NO_INTERACTION=1');
+
         $index = 0;
         $index = 0;
         $inputMock->expects($this->at($index++))
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-plugins'))
             ->with($this->equalTo('--no-plugins'))
             ->will($this->returnValue(true));
             ->will($this->returnValue(true));
 
 
+        $inputMock->expects($this->at($index++))
+            ->method('setInteractive')
+            ->with($this->equalTo(false));
+
         $inputMock->expects($this->at($index++))
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-cache'))
             ->with($this->equalTo('--no-cache'))
@@ -83,12 +89,18 @@ class ApplicationTest extends TestCase
         $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
         $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
         $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
         $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
 
 
+        putenv('COMPOSER_NO_INTERACTION=1');
+
         $index = 0;
         $index = 0;
         $inputMock->expects($this->at($index++))
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-plugins'))
             ->with($this->equalTo('--no-plugins'))
             ->will($this->returnValue(true));
             ->will($this->returnValue(true));
 
 
+        $inputMock->expects($this->at($index++))
+            ->method('setInteractive')
+            ->with($this->equalTo(false));
+
         $inputMock->expects($this->at($index++))
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-cache'))
             ->with($this->equalTo('--no-cache'))

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php

@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoloadOrder
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::$loader) {
         if (null !== self::$loader) {

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php

@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::$loader) {
         if (null !== self::$loader) {

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php

@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::$loader) {
         if (null !== self::$loader) {

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php

@@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::$loader) {
         if (null !== self::$loader) {

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php

@@ -13,6 +13,9 @@ class ComposerAutoloaderInitIncludePath
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::$loader) {
         if (null !== self::$loader) {

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php

@@ -13,6 +13,9 @@ class ComposerAutoloaderInitTargetDir
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::$loader) {
         if (null !== self::$loader) {

+ 5 - 1
tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php

@@ -1385,6 +1385,10 @@ namespace Foo;
     <?php
     <?php
 class LargeGap
 class LargeGap
 {
 {
-    public function a1380() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null)); }
+    public function test_double_gap() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null));
+        ?>
+    public function a1381() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null)); }
+        <?php
+    }
 }
 }
 
 

+ 25 - 0
tests/Composer/Test/Fixtures/installer/install-without-lock.test

@@ -0,0 +1,25 @@
+--TEST--
+Installs from composer.json without writing a lock file
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "a/a", "version": "1.0.0" }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "1.0.0"
+    },
+    "config": {
+        "lock": "false"
+    }
+}
+--RUN--
+install
+--EXPECT--
+Installing a/a (1.0.0)
+--EXPECT-LOCK--
+false

+ 25 - 0
tests/Composer/Test/Fixtures/installer/update-without-lock.test

@@ -0,0 +1,25 @@
+--TEST--
+Updates when no lock file is present without writing a lock file
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "a/a", "version": "1.0.0" }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "1.0.0"
+    },
+    "config": {
+        "lock": false
+    }
+}
+--RUN--
+update
+--EXPECT--
+Installing a/a (1.0.0)
+--EXPECT-LOCK--
+false

+ 15 - 8
tests/Composer/Test/InstallerTest.php

@@ -199,6 +199,9 @@ class InstallerTest extends TestCase
                     // so store value temporarily in reference for later assetion
                     // so store value temporarily in reference for later assetion
                     $actualLock = $hash;
                     $actualLock = $hash;
                 }));
                 }));
+        } elseif ($expectLock === false) {
+            $lockJsonMock->expects($this->never())
+                ->method('write');
         }
         }
 
 
         $contents = json_encode($composerConfig);
         $contents = json_encode($composerConfig);
@@ -282,15 +285,15 @@ class InstallerTest extends TestCase
                 continue;
                 continue;
             }
             }
 
 
-            $testData = $this->readTestFile($file, $fixturesDir);
+            try {
+                $testData = $this->readTestFile($file, $fixturesDir);
 
 
-            $installed = array();
-            $installedDev = array();
-            $lock = array();
-            $expectLock = array();
-            $expectResult = 0;
+                $installed = array();
+                $installedDev = array();
+                $lock = array();
+                $expectLock = array();
+                $expectResult = 0;
 
 
-            try {
                 $message = $testData['TEST'];
                 $message = $testData['TEST'];
                 $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
                 $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
                 $composer = JsonFile::parseJson($testData['COMPOSER']);
                 $composer = JsonFile::parseJson($testData['COMPOSER']);
@@ -321,7 +324,11 @@ class InstallerTest extends TestCase
                 }
                 }
                 $run = $testData['RUN'];
                 $run = $testData['RUN'];
                 if (!empty($testData['EXPECT-LOCK'])) {
                 if (!empty($testData['EXPECT-LOCK'])) {
-                    $expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
+                    if ($testData['EXPECT-LOCK'] === 'false') {
+                        $expectLock = false;
+                    } else {
+                        $expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
+                    }
                 }
                 }
                 $expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
                 $expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
                 $expect = $testData['EXPECT'];
                 $expect = $testData['EXPECT'];

+ 16 - 0
tests/Composer/Test/Repository/PathRepositoryTest.php

@@ -19,6 +19,22 @@ use Composer\Package\Version\VersionParser;
 
 
 class PathRepositoryTest extends TestCase
 class PathRepositoryTest extends TestCase
 {
 {
+
+    /**
+     * @expectedException RuntimeException
+     */
+    public function testLoadPackageFromFileSystemWithIncorrectPath()
+    {
+        $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
+            ->getMock();
+
+        $config = new \Composer\Config();
+
+        $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'missing'));
+        $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
+        $repository->getPackages();
+    }
+
     public function testLoadPackageFromFileSystemWithVersion()
     public function testLoadPackageFromFileSystemWithVersion()
     {
     {
         $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
         $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')

+ 8 - 8
tests/Composer/Test/Util/FilesystemTest.php

@@ -300,16 +300,16 @@ class FilesystemTest extends TestCase
 
 
         // Create and detect junction
         // Create and detect junction
         $fs->junction($target, $junction);
         $fs->junction($target, $junction);
-        $this->assertTrue($fs->isJunction($junction));
-        $this->assertFalse($fs->isJunction($target));
-        $this->assertTrue($fs->isJunction($target . '/../../junction'));
-        $this->assertFalse($fs->isJunction($junction . '/../real'));
-        $this->assertTrue($fs->isJunction($junction . '/../junction'));
+        $this->assertTrue($fs->isJunction($junction), $junction . ': is a junction');
+        $this->assertFalse($fs->isJunction($target), $target . ': is not a junction');
+        $this->assertTrue($fs->isJunction($target . '/../../junction'), $target . '/../../junction: is a junction');
+        $this->assertFalse($fs->isJunction($junction . '/../real'), $junction . '/../real: is not a junction');
+        $this->assertTrue($fs->isJunction($junction . '/../junction'), $junction . '/../junction: is a junction');
 
 
         // Remove junction
         // Remove junction
-        $this->assertTrue(is_dir($junction));
-        $this->assertTrue($fs->removeJunction($junction));
-        $this->assertFalse(is_dir($junction));
+        $this->assertTrue(is_dir($junction), $junction . ' is a directory');
+        $this->assertTrue($fs->removeJunction($junction), $junction . ' has been removed');
+        $this->assertFalse(is_dir($junction), $junction . ' is not a directory');
     }
     }
 
 
     public function testCopy()
     public function testCopy()