Browse Source

Merge remote-tracking branch 'github-composer/2.0' into solve-without-installed

* github-composer/2.0: (63 commits)
  Fix PSR warnings for optimized autoloader, refs #8397, refs #8403
  Prepare 1.9.1 changelog
  Output a hint that maybe you are not in the right directory, fixes #8404
  Fix PSR warnings for optimized autoloader, refs #8397, refs #8403
  Fix tests for PSR-fix in optimized autoloader, refs #8397
  Fix tests for PSR-fix in optimized autoloader, refs #8397
  Change PSR-fix for optimized autoloader to only warn for now, refs #8397
  Fix output of dump-autoload command to avoid interfering with warnings, refs #8397
  Remove credentials from git remotes in cache and vendor dirs
  Avoid overwriting credentials with existing ones from git repos, refs #8293
  Fix github auth to try https with pwd also, fixes #8356
  Fix gitlab support for basic-auth fallback from ssh URLs
  Avoid clearing the error output during removeDirectory execution, losing git error output, fixes #8351
  Move test file parsing into try/catch block to avoid phpunit swallowing errors
  make optimized autoloader respect PSR standards
  Validate composer show with --tree and --path options set (#8390)
  Don't show root warning for docker containers
  Added phpdoc for ComposerAutoloaderInit$SHA1::getLoader() (#8393)
  Validate schema name, type and version
  Fix require command to allow working on network mounts, fixes #8231
  ...
Nils Adermann 5 years ago
parent
commit
97ec2d7b61
59 changed files with 649 additions and 175 deletions
  1. 27 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. 10 8
      src/Composer/Autoload/AutoloadGenerator.php
  9. 81 7
      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. 8 6
      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. 32 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. 8 6
      src/Composer/Downloader/GitDownloader.php
  30. 3 1
      src/Composer/Installer.php
  31. 23 2
      src/Composer/Repository/BaseRepository.php
  32. 12 2
      src/Composer/Repository/PathRepository.php
  33. 1 1
      src/Composer/Repository/Vcs/BitbucketDriver.php
  34. 1 1
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  35. 4 4
      src/Composer/Repository/Vcs/GitHubDriver.php
  36. 2 0
      src/Composer/Repository/Vcs/GitLabDriver.php
  37. 1 1
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  38. 1 1
      src/Composer/Repository/Vcs/HgDriver.php
  39. 26 3
      src/Composer/Util/AuthHelper.php
  40. 4 2
      src/Composer/Util/Filesystem.php
  41. 39 12
      src/Composer/Util/Git.php
  42. 4 4
      src/Composer/Util/Http/CurlDownloader.php
  43. 2 2
      src/Composer/Util/RemoteFilesystem.php
  44. 0 1
      src/Composer/Util/Zip.php
  45. 12 0
      tests/Composer/Test/ApplicationTest.php
  46. 42 0
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  47. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
  48. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
  49. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_include_paths.php
  50. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php
  51. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
  52. 3 0
      tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
  53. 5 1
      tests/Composer/Test/Autoload/Fixtures/classmap/LargeGap.php
  54. 30 15
      tests/Composer/Test/Downloader/GitDownloaderTest.php
  55. 25 0
      tests/Composer/Test/Fixtures/installer/install-without-lock.test
  56. 25 0
      tests/Composer/Test/Fixtures/installer/update-without-lock.test
  57. 15 8
      tests/Composer/Test/InstallerTest.php
  58. 16 0
      tests/Composer/Test/Repository/PathRepositoryTest.php
  59. 8 8
      tests/Composer/Test/Util/FilesystemTest.php

+ 27 - 0
CHANGELOG.md

@@ -1,3 +1,28 @@
+### [1.9.1] 2019-11-01
+
+  * Fixed various credential handling issues with gitlab and github
+  * Fixed credentials being present in git remotes in Composer cache and vendor directory when not using SSH keys
+  * Fixed `composer why` not listing replacers as a reason something is present
+  * Fixed various PHP 7.4 compatibility issues
+  * Fixed root warnings always present in Docker containers, setting COMPOSER_ALLOW_SUPERUSER is not necessary anymore
+  * Fixed GitHub access tokens leaking into debug-verbosity output
+  * Fixed several edge case issues detecting GitHub, Bitbucket and GitLab repository types
+  * Fixed Composer asking if you want to use a composer.json in a parent directory when ran in non-interactive mode
+  * Fixed classmap autoloading issue finding classes located within a few non-PHP context blocks (?>...<?php)
+
+### [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 +776,8 @@
 
 
   * Initial release
   * Initial release
 
 
+[1.9.1]: https://github.com/composer/composer/compare/1.9.0...1.9.1
+[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.
+
 &larr; [Repositories](05-repositories.md)  |  [Community](07-community.md) &rarr;
 &larr; [Repositories](05-repositories.md)  |  [Community](07-community.md) &rarr;

+ 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."
                 }
                 }
             }
             }
         },
         },

+ 10 - 8
src/Composer/Autoload/AutoloadGenerator.php

@@ -256,15 +256,14 @@ EOF;
                             continue;
                             continue;
                         }
                         }
 
 
-                        $namespaceFilter = $namespace === '' ? null : $namespace;
-                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
+                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap);
                     }
                     }
                 }
                 }
             }
             }
         }
         }
 
 
         foreach ($autoloads['classmap'] as $dir) {
         foreach ($autoloads['classmap'] as $dir) {
-            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap);
+            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
         }
         }
 
 
         ksort($classMap);
         ksort($classMap);
@@ -317,9 +316,9 @@ EOF;
         return count($classMap);
         return count($classMap);
     }
     }
 
 
-    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array())
+    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap = array())
     {
     {
-        foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) {
+        foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) {
             $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
             $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
             if (!isset($classMap[$class])) {
             if (!isset($classMap[$class])) {
                 $classMap[$class] = $pathCode;
                 $classMap[$class] = $pathCode;
@@ -334,9 +333,9 @@ EOF;
         return $classMap;
         return $classMap;
     }
     }
 
 
-    private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true)
+    private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, $showAmbiguousWarning = true)
     {
     {
-        return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter);
+        return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType);
     }
     }
 
 
     public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
     public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@@ -447,7 +446,7 @@ EOF;
 
 
             foreach ($autoloads['classmap'] as $dir) {
             foreach ($autoloads['classmap'] as $dir) {
                 try {
                 try {
-                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false));
+                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false));
                 } catch (\RuntimeException $e) {
                 } catch (\RuntimeException $e) {
                     $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                     $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                 }
                 }
@@ -592,6 +591,9 @@ class ComposerAutoloaderInit$suffix
         }
         }
     }
     }
 
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     public static function getLoader()
     {
     {
         if (null !== self::\$loader) {
         if (null !== self::\$loader) {

+ 81 - 7
src/Composer/Autoload/ClassMapGenerator.php

@@ -50,17 +50,19 @@ class ClassMapGenerator
     /**
     /**
      * Iterate over all files in the given directory searching for classes
      * Iterate over all files in the given directory searching for classes
      *
      *
-     * @param \Iterator|string $path      The path to search in or an iterator
-     * @param string           $blacklist Regex that matches against the file path that exclude from the classmap.
-     * @param IOInterface      $io        IO object
-     * @param string           $namespace Optional namespace prefix to filter by
+     * @param \Iterator|string $path         The path to search in or an iterator
+     * @param string           $blacklist    Regex that matches against the file path that exclude from the classmap.
+     * @param IOInterface      $io           IO object
+     * @param string           $namespace    Optional namespace prefix to filter by
+     * @param string           $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
      *
      *
      * @throws \RuntimeException When the path is neither an existing file nor directory
      * @throws \RuntimeException When the path is neither an existing file nor directory
      * @return array             A class map array
      * @return array             A class map array
      */
      */
-    public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null)
+    public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null)
     {
     {
         if (is_string($path)) {
         if (is_string($path)) {
+            $basePath = $path;
             if (is_file($path)) {
             if (is_file($path)) {
                 $path = array(new \SplFileInfo($path));
                 $path = array(new \SplFileInfo($path));
             } elseif (is_dir($path)) {
             } elseif (is_dir($path)) {
@@ -71,6 +73,8 @@ class ClassMapGenerator
                     '" which does not appear to be a file nor a folder'
                     '" which does not appear to be a file nor a folder'
                 );
                 );
             }
             }
+        } elseif (null !== $autoloadType) {
+            throw new \RuntimeException('Path must be a string when specifying an autoload type');
         }
         }
 
 
         $map = array();
         $map = array();
@@ -100,10 +104,14 @@ class ClassMapGenerator
             }
             }
 
 
             $classes = self::findClasses($filePath);
             $classes = self::findClasses($filePath);
+            if (null !== $autoloadType) {
+                $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
+            }
 
 
             foreach ($classes as $class) {
             foreach ($classes as $class) {
                 // skip classes not within the given namespace prefix
                 // skip classes not within the given namespace prefix
-                if (null !== $namespace && 0 !== strpos($class, $namespace)) {
+                // TODO enable in Composer v1.11 or 2.0 whichever comes first
+                if (/* null === $autoloadType && */ null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) {
                     continue;
                     continue;
                 }
                 }
 
 
@@ -121,6 +129,72 @@ class ClassMapGenerator
         return $map;
         return $map;
     }
     }
 
 
+    /**
+     * Remove classes which could not have been loaded by namespace autoloaders
+     *
+     * @param array       $classes       found classes in given file
+     * @param string      $filePath      current file
+     * @param string      $baseNamespace prefix of given autoload mapping
+     * @param string      $namespaceType psr-0|psr-4
+     * @param string      $basePath      root directory of given autoload mapping
+     * @param IOInterface $io            IO object
+     * @return array      valid classes
+     */
+    private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io)
+    {
+        $validClasses = array();
+        $rejectedClasses = array();
+
+        $realSubPath = substr($filePath, strlen($basePath) + 1);
+        $realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.'));
+
+        foreach ($classes as $class) {
+            // silently skip if ns doesn't have common root
+            if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
+                continue;
+            }
+            // transform class name to file path and validate
+            if ('psr-0' === $namespaceType) {
+                $namespaceLength = strrpos($class, '\\');
+                if (false !== $namespaceLength) {
+                    $namespace = substr($class, 0, $namespaceLength + 1);
+                    $className = substr($class, $namespaceLength + 1);
+                    $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
+                        . str_replace('_', DIRECTORY_SEPARATOR, $className);
+                }
+                else {
+                    $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
+                }
+            } elseif ('psr-4' === $namespaceType) {
+                $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
+                $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
+            } else {
+                throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
+            }
+            if ($subPath === $realSubPath) {
+                $validClasses[] = $class;
+            } else {
+                $rejectedClasses[] = $class;
+            }
+        }
+        // warn only if no valid classes, else silently skip invalid
+        if (empty($validClasses)) {
+            foreach ($rejectedClasses as $class) {
+                trigger_error(
+                    "Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. It will not autoload anymore in Composer v1.11+.",
+                    E_USER_DEPRECATED
+                );
+            }
+
+            // TODO enable in Composer v1.11 or 2.0 whichever comes first
+            //return array();
+        }
+
+        // TODO enable in Composer v1.11 or 2.0 whichever comes first & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses
+        //return $validClasses;
+        return $classes;
+    }
+
     /**
     /**
      * Extract the classes in the given file
      * Extract the classes in the given file
      *
      *
@@ -173,7 +247,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)
     {
     {

+ 8 - 6
src/Composer/Command/DumpAutoloadCommand.php

@@ -63,11 +63,11 @@ EOT
         $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');
         $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');
 
 
         if ($authoritative) {
         if ($authoritative) {
-            $this->getIO()->writeError('<info>Generating optimized autoload files (authoritative)</info>', false);
+            $this->getIO()->write('<info>Generating optimized autoload files (authoritative)</info>');
         } elseif ($optimize) {
         } elseif ($optimize) {
-            $this->getIO()->writeError('<info>Generating optimized autoload files</info>', false);
+            $this->getIO()->write('<info>Generating optimized autoload files</info>');
         } else {
         } else {
-            $this->getIO()->writeError('<info>Generating autoload files</info>', false);
+            $this->getIO()->write('<info>Generating autoload files</info>');
         }
         }
 
 
         $generator = $composer->getAutoloadGenerator();
         $generator = $composer->getAutoloadGenerator();
@@ -78,11 +78,13 @@ EOT
         $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
         $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
 
 
         if ($authoritative) {
         if ($authoritative) {
-            $this->getIO()->overwriteError('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
+            $this->getIO()->write('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
         } elseif ($optimize) {
         } elseif ($optimize) {
-            $this->getIO()->overwriteError('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
+            $this->getIO()->write('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
         } else {
         } else {
-            $this->getIO()->overwriteError('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
+            $this->getIO()->write('<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)
     {
     {

+ 32 - 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,15 @@ 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'));
+        try {
+            $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'), $input->getOption('fixed'));
+        } catch (\Exception $e) {
+            if ($this->newlyCreated) {
+                throw new \RuntimeException('No composer.json present in the current directory, this may be the cause of the following exception.', 0, $e);
+            }
+
+            throw $e;
+        }
 
 
         $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
         $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."."

+ 8 - 6
src/Composer/Downloader/GitDownloader.php

@@ -74,10 +74,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
             $command =
             $command =
                 'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
                 'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
                 . '&& cd '.$flag.'%path% '
                 . '&& cd '.$flag.'%path% '
-                . '&& git remote set-url origin %url% && git remote add composer %url%';
+                . '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
         } else {
         } else {
             $msg = "Cloning ".$this->getShortHash($ref);
             $msg = "Cloning ".$this->getShortHash($ref);
-            $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer';
+            $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer && git remote set-url origin %sanitizedUrl% && git remote set-url composer %sanitizedUrl%';
             if (getenv('COMPOSER_DISABLE_NETWORK')) {
             if (getenv('COMPOSER_DISABLE_NETWORK')) {
                 throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
                 throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
             }
             }
@@ -87,11 +87,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
 
 
         $commandCallable = function ($url) use ($path, $command, $cachePath) {
         $commandCallable = function ($url) use ($path, $command, $cachePath) {
             return str_replace(
             return str_replace(
-                array('%url%', '%path%', '%cachePath%'),
+                array('%url%', '%path%', '%cachePath%', '%sanitizedUrl%'),
                 array(
                 array(
                     ProcessExecutor::escape($url),
                     ProcessExecutor::escape($url),
                     ProcessExecutor::escape($path),
                     ProcessExecutor::escape($path),
                     ProcessExecutor::escape($cachePath),
                     ProcessExecutor::escape($cachePath),
+                    ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
                 ),
                 ),
                 $command
                 $command
             );
             );
@@ -129,10 +130,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
 
 
         if (!empty($this->cachedPackages[$target->getId()][$ref])) {
         if (!empty($this->cachedPackages[$target->getId()][$ref])) {
             $msg = "Checking out ".$this->getShortHash($ref).' from cache';
             $msg = "Checking out ".$this->getShortHash($ref).' from cache';
-            $command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %url%';
+            $command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
         } else {
         } else {
             $msg = "Checking out ".$this->getShortHash($ref);
             $msg = "Checking out ".$this->getShortHash($ref);
-            $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)';
+            $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
             if (getenv('COMPOSER_DISABLE_NETWORK')) {
             if (getenv('COMPOSER_DISABLE_NETWORK')) {
                 throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
                 throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
             }
             }
@@ -142,11 +143,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
 
 
         $commandCallable = function ($url) use ($ref, $command, $cachePath) {
         $commandCallable = function ($url) use ($ref, $command, $cachePath) {
             return str_replace(
             return str_replace(
-                array('%url%', '%ref%', '%cachePath%'),
+                array('%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'),
                 array(
                 array(
                     ProcessExecutor::escape($url),
                     ProcessExecutor::escape($url),
                     ProcessExecutor::escape($ref.'^{commit}'),
                     ProcessExecutor::escape($ref.'^{commit}'),
                     ProcessExecutor::escape($cachePath),
                     ProcessExecutor::escape($cachePath),
+                    ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
                 ),
                 ),
                 $command
                 $command
             );
             );

+ 3 - 1
src/Composer/Installer.php

@@ -130,7 +130,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;
 
 
     /**
     /**
@@ -177,6 +177,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);
             }
             }
         }
         }
 
 

+ 26 - 3
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)
     {
     {
@@ -116,7 +117,7 @@ class AuthHelper
             $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
             $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
             $gitLabUtil = new GitLab($this->io, $this->config, null);
             $gitLabUtil = new GitLab($this->io, $this->config, null);
 
 
-            if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && $auth['password'] === 'private-token') {
+            if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token'), true)) {
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
             }
             }
 
 
@@ -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'];
-                } elseif ($auth['password'] === 'private-token') {
+                    $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
+                } elseif ($auth['password'] === 'private-token' || $auth['password'] === 'gitlab-ci-token') {
                     $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
                     $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)

+ 39 - 12
src/Composer/Util/Git.php

@@ -54,9 +54,9 @@ class Git
         }
         }
 
 
         if (!$initialClone) {
         if (!$initialClone) {
-            // capture username/password from URL if there is one
+            // capture username/password from URL if there is one and we have no auth configured yet
             $this->process->execute('git remote -v', $output, $cwd);
             $this->process->execute('git remote -v', $output, $cwd);
-            if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) {
+            if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
                 $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
                 $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
             }
             }
         }
         }
@@ -95,8 +95,10 @@ class Git
 
 
         $auth = null;
         $auth = null;
         if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
         if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
-            // private github repository without git access, try https with auth
-            if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)) {
+            // private github repository without ssh key access, try https with auth
+            if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
+                || preg_match('{^(https?)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)
+            ) {
                 if (!$this->io->hasAuthentication($match[1])) {
                 if (!$this->io->hasAuthentication($match[1])) {
                     $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                     $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                     $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
                     $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
@@ -153,7 +155,14 @@ class Git
                         return;
                         return;
                     }
                     }
                 }
                 }
-            } elseif (preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
+            } elseif (
+                preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
+                || preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)
+            ) {
+                if ($match[1] === 'git') {
+                    $match[1] = 'https';
+                }
+
                 if (!$this->io->hasAuthentication($match[2])) {
                 if (!$this->io->hasAuthentication($match[2])) {
                     $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
                     $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
                     $message = 'Cloning failed, enter your GitLab credentials to access private repos';
                     $message = 'Cloning failed, enter your GitLab credentials to access private repos';
@@ -165,17 +174,18 @@ class Git
 
 
                 if ($this->io->hasAuthentication($match[2])) {
                 if ($this->io->hasAuthentication($match[2])) {
                     $auth = $this->io->getAuthentication($match[2]);
                     $auth = $this->io->getAuthentication($match[2]);
-                    if($auth['password'] === 'private-token' || $auth['password'] === 'oauth2') {
+                    if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') {
                         $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
                         $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
                     } else {
                     } else {
                         $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
                         $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
                     }
                     }
+
                     $command = call_user_func($commandCallable, $authUrl);
                     $command = call_user_func($commandCallable, $authUrl);
                     if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                     if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                         return;
                         return;
                     }
                     }
                 }
                 }
-            } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate
+            } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate
                 if (strpos($match[2], '@')) {
                 if (strpos($match[2], '@')) {
                     list($authParts, $match[2]) = explode('@', $match[2], 2);
                     list($authParts, $match[2]) = explode('@', $match[2], 2);
                 }
                 }
@@ -193,7 +203,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: '),
@@ -215,10 +225,12 @@ class Git
                 }
                 }
             }
             }
 
 
+            $errorMsg = $this->process->getErrorOutput();
             if ($initialClone) {
             if ($initialClone) {
                 $this->filesystem->removeDirectory($origCwd);
                 $this->filesystem->removeDirectory($origCwd);
             }
             }
-            $this->throwException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(), $url);
+
+            $this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
         }
         }
     }
     }
 
 
@@ -232,7 +244,9 @@ class Git
         if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
         if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
             try {
             try {
                 $commandCallable = function ($url) {
                 $commandCallable = function ($url) {
-                    return sprintf('git remote set-url origin %s && git remote update --prune origin', ProcessExecutor::escape($url));
+                    $sanitizedUrl = preg_replace('{://([^@]+?):(.+?)@}', '://', $url);
+
+                    return sprintf('git remote set-url origin %s && git remote update --prune origin && git remote set-url origin %s', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
                 };
                 };
                 $this->runCommand($commandCallable, $url, $dir);
                 $this->runCommand($commandCallable, $url, $dir);
             } catch (\Exception $e) {
             } catch (\Exception $e) {
@@ -255,16 +269,29 @@ class Git
     }
     }
 
 
     public function fetchRefOrSyncMirror($url, $dir, $ref)
     public function fetchRefOrSyncMirror($url, $dir, $ref)
+    {
+        if ($this->checkRefIsInMirror($url, $dir, $ref)) {
+            return true;
+        }
+
+        if ($this->syncMirror($url, $dir)) {
+            return $this->checkRefIsInMirror($url, $dir, $ref);
+        }
+
+        return false;
+    }
+
+    private function checkRefIsInMirror($url, $dir, $ref)
     {
     {
         if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
         if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
             $escapedRef = ProcessExecutor::escape($ref.'^{commit}');
             $escapedRef = ProcessExecutor::escape($ref.'^{commit}');
-            $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $output, $dir);
+            $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir);
             if ($exitCode === 0) {
             if ($exitCode === 0) {
                 return true;
                 return true;
             }
             }
         }
         }
 
 
-        return $this->syncMirror($url, $dir);
+        return false;
     }
     }
 
 
     private function isAuthenticationFailure($url, &$match)
     private function isAuthenticationFailure($url, &$match)

+ 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'))

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

@@ -548,6 +548,48 @@ class AutoloadGeneratorTest extends TestCase
         );
         );
     }
     }
 
 
+    public function testPSRToClassMapIgnoresNonPSRClasses()
+    {
+        $package = new Package('a', '1.0', '1.0');
+
+        $this->markTestSkipped('Skipped until ClassMapGenerator ignoring of invalid PSR-x classes is enabled');
+
+        $package->setAutoload(array(
+            'psr-0' => array('psr0_' => 'psr0/'),
+            'psr-4' => array('psr4\\' => 'psr4/'),
+        ));
+
+        $this->repository->expects($this->once())
+            ->method('getCanonicalPackages')
+            ->will($this->returnValue(array()));
+
+        $this->fs->ensureDirectoryExists($this->workingDir.'/psr0/psr0');
+        $this->fs->ensureDirectoryExists($this->workingDir.'/psr4');
+        file_put_contents($this->workingDir.'/psr0/psr0/match.php', '<?php class psr0_match {}');
+        file_put_contents($this->workingDir.'/psr0/psr0/badfile.php', '<?php class psr0_badclass {}');
+        file_put_contents($this->workingDir.'/psr4/match.php', '<?php namespace psr4; class match {}');
+        file_put_contents($this->workingDir.'/psr4/badfile.php', '<?php namespace psr4; class badclass {}');
+
+        $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1');
+        $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated.");
+
+        $expectedClassmap = <<<EOF
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+\$vendorDir = dirname(dirname(__FILE__));
+\$baseDir = dirname(\$vendorDir);
+
+return array(
+    'psr0_match' => \$baseDir . '/psr0/psr0/match.php',
+    'psr4\\\\match' => \$baseDir . '/psr4/match.php',
+);
+
+EOF;
+        $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap);
+    }
+
     public function testVendorsClassMapAutoloading()
     public function testVendorsClassMapAutoloading()
     {
     {
         $package = new Package('a', '1.0', '1.0');
         $package = new Package('a', '1.0', '1.0');

+ 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
+    }
 }
 }
 
 

+ 30 - 15
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -112,7 +112,7 @@ class GitDownloaderTest extends TestCase
                 return 0;
                 return 0;
             }));
             }));
 
 
-        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer");
+        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin 'https://example.com/composer/composer' && git remote set-url composer 'https://example.com/composer/composer'");
         $processExecutor->expects($this->at(1))
         $processExecutor->expects($this->at(1))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->with($this->equalTo($expectedGitCommand))
@@ -170,6 +170,9 @@ class GitDownloaderTest extends TestCase
         $this->setupConfig($config);
         $this->setupConfig($config);
         $cachePath = $config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', 'https://example.com/composer/composer').'/';
         $cachePath = $config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', 'https://example.com/composer/composer').'/';
 
 
+        $filesystem = new \Composer\Util\Filesystem;
+        $filesystem->removeDirectory($cachePath);
+
         $expectedGitCommand = $this->winCompat(sprintf("git clone --mirror 'https://example.com/composer/composer' '%s'", $cachePath));
         $expectedGitCommand = $this->winCompat(sprintf("git clone --mirror 'https://example.com/composer/composer' '%s'", $cachePath));
         $processExecutor->expects($this->at(1))
         $processExecutor->expects($this->at(1))
             ->method('execute')
             ->method('execute')
@@ -179,24 +182,36 @@ class GitDownloaderTest extends TestCase
 
 
                 return 0;
                 return 0;
             }));
             }));
+        $processExecutor->expects($this->at(2))
+            ->method('execute')
+            ->with($this->equalTo('git rev-parse --git-dir'), $this->anything(), $this->equalTo($this->winCompat($cachePath)))
+            ->will($this->returnCallback(function ($command, &$output = null) {
+                $output = '.';
+
+                return 0;
+            }));
+        $processExecutor->expects($this->at(3))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat('git rev-parse --quiet --verify \'1234567890123456789012345678901234567890^{commit}\'')), $this->equalTo(null), $this->equalTo($this->winCompat($cachePath)))
+            ->will($this->returnValue(0));
 
 
         $expectedGitCommand = $this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin 'https://example.com/composer/composer' && git remote add composer 'https://example.com/composer/composer'", $cachePath));
         $expectedGitCommand = $this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin 'https://example.com/composer/composer' && git remote add composer 'https://example.com/composer/composer'", $cachePath));
-        $processExecutor->expects($this->at(2))
+        $processExecutor->expects($this->at(4))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->with($this->equalTo($expectedGitCommand))
             ->will($this->returnValue(0));
             ->will($this->returnValue(0));
 
 
-        $processExecutor->expects($this->at(3))
+        $processExecutor->expects($this->at(5))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git branch -r")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->with($this->equalTo($this->winCompat("git branch -r")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->will($this->returnValue(0));
             ->will($this->returnValue(0));
 
 
-        $processExecutor->expects($this->at(4))
+        $processExecutor->expects($this->at(6))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git checkout 'master' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->with($this->equalTo($this->winCompat("git checkout 'master' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->will($this->returnValue(0));
             ->will($this->returnValue(0));
 
 
-        $processExecutor->expects($this->at(5))
+        $processExecutor->expects($this->at(7))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->with($this->equalTo($this->winCompat("git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->will($this->returnValue(0));
             ->will($this->returnValue(0));
@@ -235,7 +250,7 @@ class GitDownloaderTest extends TestCase
                 return 0;
                 return 0;
             }));
             }));
 
 
-        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://github.com/mirrors/composer' && git fetch composer");
+        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://github.com/mirrors/composer' && git fetch composer && git remote set-url origin 'https://github.com/mirrors/composer' && git remote set-url composer 'https://github.com/mirrors/composer'");
         $processExecutor->expects($this->at(1))
         $processExecutor->expects($this->at(1))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->with($this->equalTo($expectedGitCommand))
@@ -246,7 +261,7 @@ class GitDownloaderTest extends TestCase
             ->with()
             ->with()
             ->will($this->returnValue('Error1'));
             ->will($this->returnValue('Error1'));
 
 
-        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'git@github.com:mirrors/composer' && git fetch composer");
+        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'git@github.com:mirrors/composer' && git fetch composer && git remote set-url origin 'git@github.com:mirrors/composer' && git remote set-url composer 'git@github.com:mirrors/composer'");
         $processExecutor->expects($this->at(3))
         $processExecutor->expects($this->at(3))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->with($this->equalTo($expectedGitCommand))
@@ -322,7 +337,7 @@ class GitDownloaderTest extends TestCase
                 return 0;
                 return 0;
             }));
             }));
 
 
-        $expectedGitCommand = $this->winCompat("git clone --no-checkout '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer '{$url}' && git fetch composer");
+        $expectedGitCommand = $this->winCompat("git clone --no-checkout '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer '{$url}' && git fetch composer && git remote set-url origin '{$url}' && git remote set-url composer '{$url}'");
         $processExecutor->expects($this->at(1))
         $processExecutor->expects($this->at(1))
             ->method('execute')
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->with($this->equalTo($expectedGitCommand))
@@ -350,7 +365,7 @@ class GitDownloaderTest extends TestCase
 
 
     public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
     public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
     {
     {
-        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer");
+        $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin 'https://example.com/composer/composer' && git remote set-url composer 'https://example.com/composer/composer'");
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())
             ->method('getSourceReference')
             ->method('getSourceReference')
@@ -408,7 +423,7 @@ class GitDownloaderTest extends TestCase
 
 
     public function testUpdate()
     public function testUpdate()
     {
     {
-        $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+        $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
 
 
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())
@@ -440,7 +455,7 @@ class GitDownloaderTest extends TestCase
 
 
     public function testUpdateWithNewRepoUrl()
     public function testUpdateWithNewRepoUrl()
     {
     {
-        $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+        $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
 
 
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())
@@ -519,8 +534,8 @@ composer https://github.com/old/url (push)
      */
      */
     public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     {
     {
-        $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
-        $expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+        $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
+        $expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'git@github.com:composer/composer'");
 
 
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())
@@ -563,8 +578,8 @@ composer https://github.com/old/url (push)
 
 
     public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
     public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
     {
     {
-        $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
-        $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
+        $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer '/'");
+        $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
 
 
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())

+ 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

@@ -243,6 +243,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);
@@ -334,15 +337,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']);
@@ -373,7 +376,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()