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
 
   * Fixed handling of backslash-escapes handling in composer.json when using the require command
@@ -751,6 +776,8 @@
 
   * 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.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

+ 36 - 36
composer.lock

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

+ 3 - 2
doc/00-intro.md

@@ -106,7 +106,8 @@ Linux distributions.
 > `mkdir -p /usr/local/bin`.
 
 > **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`.
 
@@ -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.
 For information on changing your PATH variable, please see
 [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:
 

+ 5 - 0
doc/06-config.md

@@ -296,4 +296,9 @@ Example:
 Defaults to `true`. If set to `false`, Composer will not create `.htaccess` files
 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;

+ 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
 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.
 
 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).
 
+## 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
 
 Due to some intermittent issues on Travis and other systems, we introduced a

+ 8 - 2
res/composer-schema.json

@@ -11,7 +11,8 @@
         },
         "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.",
-            "type": "string"
+            "type": "string",
+            "pattern": "^[a-z0-9-]+$"
         },
         "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.",
@@ -39,7 +40,8 @@
         },
         "version": {
             "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": {
             "type": "string",
@@ -290,6 +292,10 @@
                 "sort-packages": {
                     "type": "boolean",
                     "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;
                         }
 
-                        $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) {
-            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap);
+            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
         }
 
         ksort($classMap);
@@ -317,9 +316,9 @@ EOF;
         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";
             if (!isset($classMap[$class])) {
                 $classMap[$class] = $pathCode;
@@ -334,9 +333,9 @@ EOF;
         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)
@@ -447,7 +446,7 @@ EOF;
 
             foreach ($autoloads['classmap'] as $dir) {
                 try {
-                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false));
+                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false));
                 } catch (\RuntimeException $e) {
                     $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                 }
@@ -592,6 +591,9 @@ class ComposerAutoloaderInit$suffix
         }
     }
 
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
     public static function getLoader()
     {
         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
      *
-     * @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
      * @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)) {
+            $basePath = $path;
             if (is_file($path)) {
                 $path = array(new \SplFileInfo($path));
             } elseif (is_dir($path)) {
@@ -71,6 +73,8 @@ class ClassMapGenerator
                     '" 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();
@@ -100,10 +104,14 @@ class ClassMapGenerator
             }
 
             $classes = self::findClasses($filePath);
+            if (null !== $autoloadType) {
+                $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
+            }
 
             foreach ($classes as $class) {
                 // 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;
                 }
 
@@ -121,6 +129,72 @@ class ClassMapGenerator
         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
      *
@@ -173,7 +247,7 @@ class ClassMapGenerator
             }
         }
         // strip non-php blocks in the file
-        $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
+        $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?><?', $contents);
         // strip trailing non-php code if needed
         $pos = strrpos($contents, '?>');
         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>
 EOT
         );
+
+        return 0;
     }
 }

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

@@ -62,7 +62,7 @@ class BaseDependencyCommand extends BaseCommand
      * @param  InputInterface  $input
      * @param  OutputInterface $output
      * @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)
     {

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

@@ -70,5 +70,7 @@ EOT
         }
 
         $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),
             'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
+            'lock' => array($booleanValidator, $booleanNormalizer),
         );
         $multiConfigValues = array(
             'github-protocols' => array(
@@ -463,13 +464,19 @@ EOT
                 $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])) {
-            return $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
+            $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
+
+            return 0;
         }
         if (isset($multiConfigValues[$settingKey])) {
-            return $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
+            $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
+
+            return 0;
         }
 
         // 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');
         }
         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])) {
-            return $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
+            $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
+
+            return 0;
         }
         if (isset($multiProps[$settingKey])) {
-            return $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
+            $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
+
+            return 0;
         }
 
         // handle repositories
         if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {
-                return $this->configSource->removeRepository($matches[1]);
+                $this->configSource->removeRepository($matches[1]);
+
+                return 0;
             }
 
             if (2 === count($values)) {
-                return $this->configSource->addRepository($matches[1], array(
+                $this->configSource->addRepository($matches[1], array(
                     'type' => $values[0],
                     'url' => $values[1],
                 ));
+
+                return 0;
             }
 
             if (1 === count($values)) {
                 $value = strtolower($values[0]);
                 if (true === $booleanValidator($value)) {
                     if (false === $booleanNormalizer($value)) {
-                        return $this->configSource->addRepository($matches[1], false);
+                        $this->configSource->addRepository($matches[1], false);
+
+                        return 0;
                     }
                 } else {
                     $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
         if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
             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
         if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
             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')) {
-            return $this->configSource->removeConfigSetting($settingKey);
+            $this->configSource->removeConfigSetting($settingKey);
+
+            return 0;
         }
 
         // handle auth
@@ -595,7 +625,7 @@ EOT
                 $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                 $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
 
-                return;
+                return 0;
             }
 
             if ($matches[1] === 'bitbucket-oauth') {
@@ -618,16 +648,20 @@ EOT
                 $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
             }
 
-            return;
+            return 0;
         }
 
         // handle script
         if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
             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');

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

@@ -48,7 +48,7 @@ EOT
      *
      * @param  InputInterface  $input
      * @param  OutputInterface $output
-     * @return int|null
+     * @return int
      */
     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');
 
         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) {
-            $this->getIO()->writeError('<info>Generating optimized autoload files</info>', false);
+            $this->getIO()->write('<info>Generating optimized autoload files</info>');
         } else {
-            $this->getIO()->writeError('<info>Generating autoload files</info>', false);
+            $this->getIO()->write('<info>Generating autoload files</info>');
         }
 
         $generator = $composer->getAutoloadGenerator();
@@ -78,11 +78,13 @@ EOT
         $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
 
         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) {
-            $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 {
-            $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)) {
             $this->installDependencies($output);
         }
+
+        return 0;
     }
 
     /**
@@ -400,7 +402,7 @@ EOT
         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) {
             $requires = $this->normalizeRequirements($requires);
@@ -410,7 +412,7 @@ EOT
             foreach ($requires as $requirement) {
                 if (!isset($requirement['version'])) {
                     // 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;
 
                     // replace package name from packagist.org
@@ -423,7 +425,7 @@ EOT
                     ));
                 } else {
                     // 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
                     $requirement['name'] = $name;
@@ -700,10 +702,11 @@ EOT
      * @param  string                    $preferredStability
      * @param  string|null               $requiredVersion
      * @param  string                    $minimumStability
+     * @param  bool                      $fixed
      * @throws \InvalidArgumentException
      * @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
         $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
@@ -777,7 +780,7 @@ EOT
 
         return array(
             $package->getPrettyName(),
-            $versionSelector->findRecommendedRequireVersion($package),
+            $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
         );
     }
 

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

@@ -110,6 +110,8 @@ EOT
             default:
                 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)
     {
         $args = array(
-            'show',
+            'command' => 'show',
             '--latest' => true,
         );
         if (!$input->getOption('all')) {

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

@@ -48,7 +48,7 @@ EOT
      *
      * @param  InputInterface  $input
      * @param  OutputInterface $output
-     * @return int|null
+     * @return int
      */
     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('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('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-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.'),
@@ -99,7 +100,9 @@ EOT
 
             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>');
 
             return 1;
@@ -120,6 +123,25 @@ EOT
             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'));
         $repos = $composer->getRepositoryManager()->getRepositories();
 
@@ -137,7 +159,15 @@ EOT
         }
 
         $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';
         $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';

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

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

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

@@ -254,6 +254,8 @@ TAGSPUBKEY
         } else {
             $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)

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

@@ -129,6 +129,12 @@ EOT
             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');
         if (!in_array($format, array('text', 'json'))) {
             $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
@@ -586,6 +592,7 @@ EOT
         }
         $io->write('<info>type</info>     : ' . $package->getType());
         $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>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
         if ($installedRepo->hasPackage($package)) {

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

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

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

@@ -93,7 +93,7 @@ EOT
                 continue;
             }
             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;
                 }
                 if (!isset($installed[$suggestion])) {
@@ -121,7 +121,7 @@ EOT
                 $io->write(sprintf('<info>%s</info>', $suggestion));
             }
 
-            return null;
+            return 0;
         }
 
         // Grouped by package
@@ -151,5 +151,7 @@ EOT
                 $io->write('');
             }
         }
+
+        return 0;
     }
 }

+ 3 - 0
src/Composer/Config.php

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

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

@@ -113,6 +113,10 @@ class Application extends BaseApplication
     {
         $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
 
+        if (getenv('COMPOSER_NO_INTERACTION')) {
+            $input->setInteractive(false);
+        }
+
         $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
             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']));
             }
 
-            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 ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
                         $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;
         if ($previousDecision != 0) {
-            $literalString = $this->pool->literalToString($literal);
+            $literalString = $this->pool->literalToPrettyString($literal, array());
             $package = $this->pool->literalToPackage($literal);
             throw new SolverBugException(
                 "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 =
                 'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
                 . '&& 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 {
             $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')) {
                 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) {
             return str_replace(
-                array('%url%', '%path%', '%cachePath%'),
+                array('%url%', '%path%', '%cachePath%', '%sanitizedUrl%'),
                 array(
                     ProcessExecutor::escape($url),
                     ProcessExecutor::escape($path),
                     ProcessExecutor::escape($cachePath),
+                    ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
                 ),
                 $command
             );
@@ -129,10 +130,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
 
         if (!empty($this->cachedPackages[$target->getId()][$ref])) {
             $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 {
             $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')) {
                 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) {
             return str_replace(
-                array('%url%', '%ref%', '%cachePath%'),
+                array('%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'),
                 array(
                     ProcessExecutor::escape($url),
                     ProcessExecutor::escape($ref.'^{commit}'),
                     ProcessExecutor::escape($cachePath),
+                    ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
                 ),
                 $command
             );

+ 3 - 1
src/Composer/Installer.php

@@ -130,7 +130,7 @@ class Installer
     protected $preferStable = false;
     protected $preferLowest = false;
     protected $skipSuggest = false;
-    protected $writeLock = true;
+    protected $writeLock;
     protected $executeOperations = true;
 
     /**
@@ -177,6 +177,8 @@ class Installer
         $this->installationManager = $installationManager;
         $this->eventDispatcher = $eventDispatcher;
         $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
             if (!$invert) {
                 $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
@@ -112,12 +133,12 @@ abstract class BaseRepository implements RepositoryInterface
                         if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                             // already displayed this node's dependencies, cutting short
                             if (in_array($link->getSource(), $packagesInTree)) {
-                                $results[$link->getSource()] = array($package, $link, false);
+                                $results[] = array($package, $link, false);
                                 continue;
                             }
                             $packagesInTree[] = $link->getSource();
                             $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();
 
-        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;
             $composerFilePath = $path.'composer.json';
 
@@ -155,7 +161,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
 
             if (!isset($package['version'])) {
                 $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 = '';

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

@@ -47,7 +47,7 @@ abstract class BitbucketDriver extends VcsDriver
      */
     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->repository = $match[2];
         $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)
     {
-        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#i', $url)) {
             return false;
         }
 

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

@@ -48,10 +48,10 @@ class GitHubDriver extends VcsDriver
      */
     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->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') {
             $this->originUrl = 'github.com';
         }
@@ -270,12 +270,12 @@ class GitHubDriver extends VcsDriver
      */
     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;
         }
 
         $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;
         }
 

+ 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)
     {
+        $guessedDomain = strtolower($guessedDomain);
+
         if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
             if ($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)
     {
-        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#i', $url)) {
             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));
                 };
 
-                $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 $config;
+    private $displayedOriginAuthentications = array();
 
     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');
             $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);
             }
 
@@ -172,7 +173,7 @@ class AuthHelper
                 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: ');
             $password = $this->io->askAndHideAnswer('      Password: ');
             $this->io->setAuthentication($origin, $username, $password);
@@ -193,14 +194,18 @@ class AuthHelper
     public function addAuthenticationHeader(array $headers, $origin, $url)
     {
         if ($this->io->hasAuthentication($origin)) {
+            $authenticationDisplayMessage = null;
             $auth = $this->io->getAuthentication($origin);
             if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
                 $headers[] = 'Authorization: token '.$auth['username'];
+                $authenticationDisplayMessage = 'Using GitHub token authentication';
             } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
                 if ($auth['password'] === 'oauth2') {
                     $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'];
+                    $authenticationDisplayMessage = 'Using GitLab private token authentication';
                 }
             } elseif (
                 'bitbucket.org' === $origin
@@ -209,10 +214,17 @@ class AuthHelper
             ) {
                 if (!$this->isPublicBitBucketDownload($url)) {
                     $headers[] = 'Authorization: Bearer ' . $auth['password'];
+                    $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
                 }
             } else {
                 $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                 $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';
     }
+
+    /**
+     * @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()) {
             return false;
         }
+
+        // Important to clear all caches first
+        clearstatcache(true, $junction);
+
         if (!is_dir($junction) || is_link($junction)) {
             return false;
         }
 
-        // Important to clear all caches first
-        clearstatcache(true, $junction);
         $stat = lstat($junction);
 
         // 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) {
-            // 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);
-            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]));
             }
         }
@@ -95,8 +95,10 @@ class Git
 
         $auth = null;
         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])) {
                     $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';
@@ -153,7 +155,14 @@ class Git
                         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])) {
                     $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
                     $message = 'Cloning failed, enter your GitLab credentials to access private repos';
@@ -165,17 +174,18 @@ class Git
 
                 if ($this->io->hasAuthentication($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
                     } else {
                         $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
                     }
+
                     $command = call_user_func($commandCallable, $authUrl);
                     if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                         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], '@')) {
                     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(
                         'username' => $this->io->ask('      Username: ', $defaultUsername),
                         'password' => $this->io->askAndHideAnswer('      Password: '),
@@ -215,10 +225,12 @@ class Git
                 }
             }
 
+            $errorMsg = $this->process->getErrorOutput();
             if ($initialClone) {
                 $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) === '.') {
             try {
                 $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);
             } catch (\Exception $e) {
@@ -255,16 +269,29 @@ class Git
     }
 
     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) === '.') {
             $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) {
                 return true;
             }
         }
 
-        return $this->syncMirror($url, $dir);
+        return false;
     }
 
     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'] : '';
         $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
         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));
@@ -254,12 +254,12 @@ class CurlDownloader
                         $contents = stream_get_contents($job['bodyHandle']);
                     }
                     $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 {
                     rewind($job['bodyHandle']);
                     $contents = stream_get_contents($job['bodyHandle']);
                     $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']);
 
@@ -362,7 +362,7 @@ class CurlDownloader
         }
 
         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;
         }

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

@@ -246,7 +246,7 @@ class RemoteFilesystem
 
         $actualContextOptions = stream_context_get_options($ctx);
         $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);
 
         // 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->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;
 

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

@@ -21,7 +21,6 @@ class Zip
      * Gets content of the root composer.json inside a ZIP archive.
      *
      * @param string $pathToZip
-     * @param string $filename
      *
      * @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();
         $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
 
+        putenv('COMPOSER_NO_INTERACTION=1');
+
         $index = 0;
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-plugins'))
             ->will($this->returnValue(true));
 
+        $inputMock->expects($this->at($index++))
+            ->method('setInteractive')
+            ->with($this->equalTo(false));
+
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-cache'))
@@ -83,12 +89,18 @@ class ApplicationTest extends TestCase
         $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
         $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
 
+        putenv('COMPOSER_NO_INTERACTION=1');
+
         $index = 0;
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->with($this->equalTo('--no-plugins'))
             ->will($this->returnValue(true));
 
+        $inputMock->expects($this->at($index++))
+            ->method('setInteractive')
+            ->with($this->equalTo(false));
+
         $inputMock->expects($this->at($index++))
             ->method('hasParameterOption')
             ->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()
     {
         $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()
     {
         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()
     {
         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()
     {
         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()
     {
         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()
     {
         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()
     {
         if (null !== self::$loader) {

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

@@ -1385,6 +1385,10 @@ namespace Foo;
     <?php
 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;
             }));
 
-        $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))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -170,6 +170,9 @@ class GitDownloaderTest extends TestCase
         $this->setupConfig($config);
         $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));
         $processExecutor->expects($this->at(1))
             ->method('execute')
@@ -179,24 +182,36 @@ class GitDownloaderTest extends TestCase
 
                 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));
-        $processExecutor->expects($this->at(2))
+        $processExecutor->expects($this->at(4))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->will($this->returnValue(0));
 
-        $processExecutor->expects($this->at(3))
+        $processExecutor->expects($this->at(5))
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git branch -r")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->will($this->returnValue(0));
 
-        $processExecutor->expects($this->at(4))
+        $processExecutor->expects($this->at(6))
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git checkout 'master' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->will($this->returnValue(0));
 
-        $processExecutor->expects($this->at(5))
+        $processExecutor->expects($this->at(7))
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
             ->will($this->returnValue(0));
@@ -235,7 +250,7 @@ class GitDownloaderTest extends TestCase
                 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))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -246,7 +261,7 @@ class GitDownloaderTest extends TestCase
             ->with()
             ->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))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -322,7 +337,7 @@ class GitDownloaderTest extends TestCase
                 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))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -350,7 +365,7 @@ class GitDownloaderTest extends TestCase
 
     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->expects($this->any())
             ->method('getSourceReference')
@@ -408,7 +423,7 @@ class GitDownloaderTest extends TestCase
 
     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->expects($this->any())
@@ -440,7 +455,7 @@ class GitDownloaderTest extends TestCase
 
     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->expects($this->any())
@@ -519,8 +534,8 @@ composer https://github.com/old/url (push)
      */
     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->expects($this->any())
@@ -563,8 +578,8 @@ composer https://github.com/old/url (push)
 
     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->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
                     $actualLock = $hash;
                 }));
+        } elseif ($expectLock === false) {
+            $lockJsonMock->expects($this->never())
+                ->method('write');
         }
 
         $contents = json_encode($composerConfig);
@@ -334,15 +337,15 @@ class InstallerTest extends TestCase
                 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'];
                 $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
                 $composer = JsonFile::parseJson($testData['COMPOSER']);
@@ -373,7 +376,11 @@ class InstallerTest extends TestCase
                 }
                 $run = $testData['RUN'];
                 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;
                 $expect = $testData['EXPECT'];

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

@@ -19,6 +19,22 @@ use Composer\Package\Version\VersionParser;
 
 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()
     {
         $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
         $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
-        $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()