Browse Source

The update allow list is now generated while building the pool

This reduces code complexity while making partial updates more
predictable. This also allows composer require to successfully run a
partial update for a new package with transitive dependency updates.
Nils Adermann 5 years ago
parent
commit
01fe92905a

+ 10 - 3
src/Composer/Command/UpdateCommand.php

@@ -13,6 +13,7 @@
 namespace Composer\Command;
 
 use Composer\Composer;
+use Composer\DependencyResolver\Request;
 use Composer\Installer;
 use Composer\IO\IOInterface;
 use Composer\Plugin\CommandEvent;
@@ -145,6 +146,13 @@ EOT
         $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
         $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');
 
+        $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
+        if ($input->getOption('with-all-dependencies')) {
+            $updateAllowTransitiveDependencies = Request::UPDATE_TRANSITIVE_ROOT_DEPENDENCIES;
+        } elseif ($input->getOption('with-dependencies')) {
+            $updateAllowTransitiveDependencies = Request::UPDATE_TRANSITIVE_DEPENDENCIES;
+        }
+
         $install
             ->setDryRun($input->getOption('dry-run'))
             ->setVerbose($input->getOption('verbose'))
@@ -158,9 +166,8 @@ EOT
             ->setApcuAutoloader($apcu)
             ->setUpdate(true)
             ->setUpdateMirrors($updateMirrors)
-            ->setUpdateWhitelist($packages)
-            ->setWhitelistTransitiveDependencies($input->getOption('with-dependencies'))
-            ->setWhitelistAllDependencies($input->getOption('with-all-dependencies'))
+            ->setUpdateAllowList($packages)
+            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
             ->setPreferStable($input->getOption('prefer-stable'))
             ->setPreferLowest($input->getOption('prefer-lowest'))

+ 69 - 105
src/Composer/DependencyResolver/PoolBuilder.php

@@ -12,6 +12,7 @@
 
 namespace Composer\DependencyResolver;
 
+use Composer\IO\IOInterface;
 use Composer\Package\AliasPackage;
 use Composer\Package\BasePackage;
 use Composer\Package\Package;
@@ -36,32 +37,41 @@ class PoolBuilder
     private $rootAliases;
     private $rootReferences;
     private $eventDispatcher;
+    private $io;
 
     private $aliasMap = array();
     private $nameConstraints = array();
     private $loadedNames = array();
     private $packages = array();
     private $unacceptableFixedPackages = array();
+    private $updateAllowList = array();
+    private $skippedLoad = array();
+    private $updateAllowWarned = array();
 
-    private $unfixList = array();
-
-    public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, EventDispatcher $eventDispatcher = null)
+    public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, EventDispatcher $eventDispatcher = null, IOInterface $io = null)
     {
         $this->acceptableStabilities = $acceptableStabilities;
         $this->stabilityFlags = $stabilityFlags;
         $this->rootAliases = $rootAliases;
         $this->rootReferences = $rootReferences;
         $this->eventDispatcher = $eventDispatcher;
+        $this->io = $io;
     }
 
     public function buildPool(array $repositories, Request $request)
     {
         if ($request->getUpdateAllowList()) {
-            $this->unfixList = $request->getUpdateAllowList();
+            $this->updateAllowList = $request->getUpdateAllowList();
+            $this->warnAboutNonMatchingUpdateAllowList($request);
 
             foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
-                if (!$this->isUpdateable($lockedPackage)) {
+                if (!$this->isUpdateAllowed($lockedPackage)) {
                     $request->fixPackage($lockedPackage);
+                    // remember which packages we skipped loading remote content for in this partial update
+                    $this->skippedLoad[$lockedPackage->getName()] = true;
+                    foreach ($lockedPackage->getReplaces() as $link) {
+                        $this->skippedLoad[$link->getTarget()] = true;
+                    }
                 }
             }
         }
@@ -85,7 +95,7 @@ class PoolBuilder
                 || $package->getRepository() instanceof PlatformRepository
                 || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability())
             ) {
-                $loadNames += $this->loadPackage($request, $package);
+                $loadNames += $this->loadPackage($request, $package, false);
             } else {
                 $this->unacceptableFixedPackages[] = $package;
             }
@@ -94,6 +104,7 @@ class PoolBuilder
         foreach ($request->getRequires() as $packageName => $constraint) {
             // fixed packages have already been added, so if a root require needs one of them, no need to do anything
             if (isset($this->loadedNames[$packageName])) {
+                $this->rootRequireNotUpdated[$packageName] = true;
                 continue;
             }
 
@@ -120,7 +131,6 @@ class PoolBuilder
                 if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) {
                     continue;
                 }
-
                 $result = $repository->loadPackages($loadNames, $this->acceptableStabilities, $this->stabilityFlags);
 
                 foreach ($result['namesFound'] as $name) {
@@ -189,7 +199,7 @@ class PoolBuilder
         return $pool;
     }
 
-    private function loadPackage(Request $request, PackageInterface $package)
+    private function loadPackage(Request $request, PackageInterface $package, $propagateUpdate = true)
     {
         $index = count($this->packages);
         $this->packages[] = $package;
@@ -205,6 +215,7 @@ class PoolBuilder
         // apply to
         if (isset($this->rootReferences[$name])) {
             // do not modify the references on already locked packages
+            // TODO what about unfix on allow update?
             if (!$request->isFixedPackage($package)) {
                 $package->setSourceDistReferences($this->rootReferences[$name]);
             }
@@ -229,9 +240,16 @@ class PoolBuilder
             $require = $link->getTarget();
             if (!isset($this->loadedNames[$require])) {
                 $loadNames[$require] = null;
-            }
-            if (isset($request->getUpdateAllowList()[$package->getName()])) {
-
+            // if this is a partial update with transitive dependencies we need to unfix the package we now know is a
+            // dependency of another package which we are trying to update, and then attempt to load it again
+            } elseif ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies() && isset($this->skippedLoad[$require])) {
+                if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $require)) {
+                    $this->unfixPackage($request, $require);
+                    $loadNames[$require] = null;
+                } elseif (!$request->getUpdateAllowTransitiveRootDependencies() && $this->isRootRequire($request, $require) && !isset($this->updateAllowWarned[$require]) && $this->io) {
+                    $this->updateAllowWarned[$require] = true;
+                    $this->io->writeError('<warning>Dependency "'.$require.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.</warning>');
+                }
             }
 
             $linkConstraint = $link->getConstraint();
@@ -252,121 +270,67 @@ class PoolBuilder
     }
 
     /**
-     * Adds all dependencies of the update whitelist to the whitelist, too.
-     *
-     * Packages which are listed as requirements in the root package will be
-     * skipped including their dependencies, unless they are listed in the
-     * update whitelist themselves or $whitelistAllDependencies is true.
+     * Checks if a particular name is required directly in the request
      *
-     * @param RepositoryInterface $lockRepo        Use the locked repo
-     *                                             As we want the most accurate package list to work with, and installed
-     *                                             repo might be empty but locked repo will always be current.
-     * @param array               $rootRequires    An array of links to packages in require of the root package
-     * @param array               $rootDevRequires An array of links to packages in require-dev of the root package
+     * @return bool
      */
-    private function whitelistUpdateDependencies($lockRepo, array $rootRequires, array $rootDevRequires)
+    private function isRootRequire(Request $request, $name)
     {
-        $rootRequires = array_merge($rootRequires, $rootDevRequires);
+        $rootRequires = $request->getRequires();
+        return isset($rootRequires[$name]);
+    }
 
-        $skipPackages = array();
-        if (!$this->whitelistAllDependencies) {
-            foreach ($rootRequires as $require) {
-                $skipPackages[$require->getTarget()] = true;
+    /**
+     * Checks whether the update allow list allows this package in the lock file to be updated
+     * @return bool
+     */
+    private function isUpdateAllowed(PackageInterface $package)
+    {
+        foreach ($this->updateAllowList as $pattern => $void) {
+            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
+            if (preg_match($patternRegexp, $package->getName())) {
+                return true;
             }
         }
 
-        $installedRepo = new InstalledRepository(array($lockRepo));
-
-        $seen = array();
-
-        $rootRequiredPackageNames = array_keys($rootRequires);
-
-        foreach ($this->updateWhitelist as $packageName => $void) {
-            $packageQueue = new \SplQueue;
-            $nameMatchesRequiredPackage = false;
-
-            $depPackages = $installedRepo->findPackagesWithReplacersAndProviders($packageName);
-            $matchesByPattern = array();
-
-            // check if the name is a glob pattern that did not match directly
-            if (empty($depPackages)) {
-                // add any installed package matching the whitelisted name/pattern
-                $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$');
-                foreach ($lockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) {
-                    $matchesByPattern[] = $installedRepo->findPackages($installedPackage['name']);
-                }
+        return false;
+    }
 
-                // add root requirements which match the whitelisted name/pattern
-                $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName);
-                foreach ($rootRequiredPackageNames as $rootRequiredPackageName) {
-                    if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) {
-                        $nameMatchesRequiredPackage = true;
-                        break;
+    private function warnAboutNonMatchingUpdateAllowList(Request $request)
+    {
+        if ($this->io) {
+            foreach ($this->updateAllowList as $pattern => $void) {
+                foreach ($request->getLockedRepository()->getPackages() as $package) {
+                    $patternRegexp = BasePackage::packageNameToRegexp($pattern);
+                    if (preg_match($patternRegexp, $package->getName())) {
+                        continue 2;
                     }
                 }
-            }
-
-            if (!empty($matchesByPattern)) {
-                $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern));
-            }
-
-            if (count($depPackages) == 0 && !$nameMatchesRequiredPackage) {
-                $this->io->writeError('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
-            }
-
-            foreach ($depPackages as $depPackage) {
-                $packageQueue->enqueue($depPackage);
-            }
-
-            while (!$packageQueue->isEmpty()) {
-                $package = $packageQueue->dequeue();
-                if (isset($seen[spl_object_hash($package)])) {
-                    continue;
-                }
-
-                $seen[spl_object_hash($package)] = true;
-                $this->updateWhitelist[$package->getName()] = true;
-
-                if (!$this->whitelistTransitiveDependencies && !$this->whitelistAllDependencies) {
-                    continue;
-                }
-
-                $requires = $package->getRequires();
-
-                foreach ($requires as $require) {
-                    $requirePackages = $installedRepo->findPackagesWithReplacersAndProviders($require->getTarget());
-
-                    foreach ($requirePackages as $requirePackage) {
-                        if (isset($this->updateWhitelist[$requirePackage->getName()])) {
-                            continue;
-                        }
-
-                        if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) {
-                            $this->io->writeError('<warning>Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>');
-                            continue;
-                        }
-
-                        $packageQueue->enqueue($requirePackage);
-                    }
+                if (strpos($pattern, '*') !== false) {
+                    $this->io->writeError('<warning>Pattern "' . $pattern . '" listed for update does not match any locked packages.</warning>');
+                } else {
+                    $this->io->writeError('<warning>Package "' . $pattern . '" listed for update is not locked.</warning>');
                 }
             }
         }
     }
 
     /**
-     * @param  PackageInterface $package
-     * @return bool
+     * Reverts the decision to use a fixed package from lock file if a partial update with transitive dependencies
+     * found that this package actually needs to be updated
      */
-    private function isUpdateable(PackageInterface $package)
+    private function unfixPackage(Request $request, $name)
     {
-        foreach ($this->unfixList as $pattern => $void) {
-            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
-            if (preg_match($patternRegexp, $package->getName())) {
-                return true;
+        // remove locked package by this name which was already initialized
+        foreach ($this->packages as $i => $loadedPackage) {
+            if ($loadedPackage->getName() === $name && $loadedPackage->getRepository() === $request->getLockedRepository()) {
+                $request->unfixPackage($loadedPackage);
+                unset($this->packages[$i]);
             }
         }
 
-        return false;
+        unset($this->skippedLoad[$name]);
+        unset($this->loadedNames[$name]);
     }
 }
 

+ 22 - 4
src/Composer/DependencyResolver/Request.php

@@ -23,13 +23,16 @@ use Composer\Semver\Constraint\ConstraintInterface;
  */
 class Request
 {
+    const UPDATE_ONLY_LISTED = 0;
+    const UPDATE_TRANSITIVE_DEPENDENCIES = 1;
+    const UPDATE_TRANSITIVE_ROOT_DEPENDENCIES = 2;
+
     protected $lockedRepository;
     protected $requires = array();
     protected $fixedPackages = array();
     protected $unlockables = array();
     protected $updateAllowList = array();
     protected $updateAllowTransitiveDependencies = false;
-    protected $updateAllowTransitiveRootDependencies = false;
 
     public function __construct(LockArrayRepository $lockedRepository = null)
     {
@@ -52,15 +55,20 @@ class Request
         $this->fixedPackages[spl_object_hash($package)] = $package;
 
         if (!$lockable) {
-            $this->unlockables[] = $package;
+            $this->unlockables[spl_object_hash($package)] = $package;
         }
     }
 
-    public function setUpdateAllowList($updateAllowList, $updateAllowTransitiveDependencies, $updateAllowTransitiveRootDependencies)
+    public function unfixPackage(PackageInterface $package)
+    {
+        unset($this->fixedPackages[spl_object_hash($package)]);
+        unset($this->unlockables[spl_object_hash($package)]);
+    }
+
+    public function setUpdateAllowList($updateAllowList, $updateAllowTransitiveDependencies)
     {
         $this->updateAllowList = $updateAllowList;
         $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;
-        $this->updateAllowTransitiveRootDependencies = $updateAllowTransitiveRootDependencies;
     }
 
     public function getUpdateAllowList()
@@ -68,6 +76,16 @@ class Request
         return $this->updateAllowList;
     }
 
+    public function getUpdateAllowTransitiveDependencies()
+    {
+        return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED;
+    }
+
+    public function getUpdateAllowTransitiveRootDependencies()
+    {
+        return $this->updateAllowTransitiveDependencies === self::UPDATE_TRANSITIVE_ROOT_DEPENDENCIES;
+    }
+
     public function getRequires()
     {
         return $this->requires;

+ 21 - 39
src/Composer/Installer.php

@@ -142,9 +142,8 @@ class Installer
      * @var array|null
      */
     protected $updateMirrors = false;
-    protected $updateWhitelist = null;
-    protected $whitelistTransitiveDependencies = false;
-    protected $whitelistAllDependencies = false;
+    protected $updateAllowList = null;
+    protected $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
 
     /**
      * @var SuggestedPackagesReporter
@@ -199,8 +198,8 @@ class Installer
         gc_collect_cycles();
         gc_disable();
 
-        if ($this->updateWhitelist && $this->updateMirrors) {
-            throw new \RuntimeException("The installer options updateMirrors and updateWhitelist are mutually exclusive.");
+        if ($this->updateAllowList && $this->updateMirrors) {
+            throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive.");
         }
 
         // Force update if there is no lock file present
@@ -352,16 +351,11 @@ class Installer
             $lockedRepository = $this->locker->getLockedRepository(true);
         }
 
-        if ($this->updateWhitelist) {
+        if ($this->updateAllowList) {
             if (!$lockedRepository) {
                 $this->io->writeError('<error>Cannot update only a partial set of packages without a lock file present.</error>', true, IOInterface::QUIET);
                 return 1;
-            }/*
-            $this->whitelistUpdateDependencies(
-                $lockedRepository,
-                $this->package->getRequires(),
-                $this->package->getDevRequires()
-            );*/
+            }
         }
 
         $this->io->writeError('<info>Loading composer repositories with package information</info>');
@@ -395,11 +389,11 @@ class Installer
         }
 
         // pass the allow list into the request, so the pool builder can apply it
-        if ($this->updateWhitelist) {
-            $request->setUpdateAllowList($this->updateWhitelist, $this->whitelistTransitiveDependencies, $this->whitelistAllDependencies);
+        if ($this->updateAllowList) {
+            $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies);
         }
 
-        $pool = $repositorySet->createPool($request, $this->eventDispatcher);
+        $pool = $repositorySet->createPool($request, $this->eventDispatcher, $this->io);
 
         // solve dependencies
         $solver = new Solver($policy, $pool, $this->io, $repositorySet);
@@ -618,7 +612,7 @@ class Installer
                 $request->requireName($link->getTarget(), $link->getConstraint());
             }
 
-            $pool = $repositorySet->createPool($request, $this->eventDispatcher);
+            $pool = $repositorySet->createPool($request, $this->eventDispatcher, $this->io);
 
             // solve dependencies
             $solver = new Solver($policy, $pool, $this->io, $repositorySet);
@@ -1138,41 +1132,29 @@ class Installer
      * @param  array     $packages
      * @return Installer
      */
-    public function setUpdateWhitelist(array $packages)
+    public function setUpdateAllowList(array $packages)
     {
-        $this->updateWhitelist = array_flip(array_map('strtolower', $packages));
+        $this->updateAllowList = array_flip(array_map('strtolower', $packages));
 
         return $this;
     }
 
     /**
-     * Should dependencies of whitelisted packages (but not direct dependencies) be updated?
+     * Should dependencies of packages marked for update be updated?
      *
-     * This will NOT whitelist any dependencies that are also directly defined
-     * in the root package.
+     * Depending on the chosen constant this will either only update the directly named packages, all transitive
+     * dependencies which are not root requirement or all transitive dependencies including root requirements
      *
-     * @param  bool      $updateTransitiveDependencies
+     * @param  int      $updateAllowTransitiveDependencies One of the UPDATE_ constants on the Request class
      * @return Installer
      */
-    public function setWhitelistTransitiveDependencies($updateTransitiveDependencies = true)
+    public function setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
     {
-        $this->whitelistTransitiveDependencies = (bool) $updateTransitiveDependencies;
-
-        return $this;
-    }
+        if (!in_array($updateAllowTransitiveDependencies, array(Request::UPDATE_ONLY_LISTED, Request::UPDATE_TRANSITIVE_DEPENDENCIES, Request::UPDATE_TRANSITIVE_ROOT_DEPENDENCIES), true)) {
+            throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied");
+        }
 
-    /**
-     * Should all dependencies of whitelisted packages be updated recursively?
-     *
-     * This will whitelist any dependencies of the whitelisted packages, including
-     * those defined in the root package.
-     *
-     * @param  bool      $updateAllDependencies
-     * @return Installer
-     */
-    public function setWhitelistAllDependencies($updateAllDependencies = true)
-    {
-        $this->whitelistAllDependencies = (bool) $updateAllDependencies;
+        $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;
 
         return $this;
     }

+ 3 - 2
src/Composer/Repository/RepositorySet.php

@@ -16,6 +16,7 @@ use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\PoolBuilder;
 use Composer\DependencyResolver\Request;
 use Composer\EventDispatcher\EventDispatcher;
+use Composer\IO\IOInterface;
 use Composer\Package\BasePackage;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\CompositeRepository;
@@ -185,9 +186,9 @@ class RepositorySet
      *
      * @return Pool
      */
-    public function createPool(Request $request, EventDispatcher $eventDispatcher = null)
+    public function createPool(Request $request, EventDispatcher $eventDispatcher = null, IOInterface $io = null)
     {
-        $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $eventDispatcher);
+        $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $eventDispatcher, $io);
 
         foreach ($this->repositories as $repo) {
             if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {

+ 2 - 2
tests/Composer/Test/Fixtures/installer/github-issues-4795.test

@@ -14,7 +14,7 @@ dependency of one the requirements that is whitelisted for update.
                 { "name": "a/a", "version": "1.0.0" },
                 { "name": "a/a", "version": "1.1.0" },
                 { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
-                { "name": "b/b", "version": "1.1.0", "require": { "a/b": "~1.1" } }
+                { "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } }
             ]
         }
     ],
@@ -49,9 +49,9 @@ dependency of one the requirements that is whitelisted for update.
 update b/b --with-dependencies
 
 --EXPECT-OUTPUT--
-<warning>Dependency "a/a" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>
 Loading composer repositories with package information
 Updating dependencies
+<warning>Dependency "a/a" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.</warning>
 Nothing to modify in lock file
 Writing lock file
 Installing dependencies from lock file (including require-dev)

+ 1 - 1
tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test

@@ -70,7 +70,7 @@ Update with a package whitelist only updates those packages and their dependenci
     "platform-dev": []
 }
 --RUN--
-update whitelisted/pkg-* --with-dependencies
+update whitelisted/pkg-* foobar --with-dependencies
 --EXPECT--
 Upgrading dependency/pkg (1.0.0 => 1.1.0)
 Upgrading whitelisted/pkg-component2 (1.0.0 => 1.1.0)

+ 58 - 0
tests/Composer/Test/Fixtures/installer/update-whitelist-warns-non-existing-patterns.test

@@ -0,0 +1,58 @@
+--TEST--
+Verify that partial updates warn about using patterns in the argument which have no matches
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "a/a", "version": "1.0.0" },
+                { "name": "b/b", "version": "1.0.0" },
+                { "name": "b/b", "version": "1.1.0" }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "~1.0",
+        "b/b": "~1.0"
+    }
+}
+
+--INSTALLED--
+[
+    { "name": "a/a", "version": "1.0.0" },
+    { "name": "b/b", "version": "1.0.0" }
+]
+
+--LOCK--
+{
+    "packages": [
+        { "name": "a/a", "version": "1.0.0" },
+        { "name": "b/b", "version": "1.0.0" }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--RUN--
+update b/b foo/bar baz/* --with-dependencies
+
+--EXPECT-OUTPUT--
+Loading composer repositories with package information
+Updating dependencies
+<warning>Package "foo/bar" listed for update is not locked.</warning>
+<warning>Pattern "baz/*" listed for update does not match any locked packages.</warning>
+Lock file operations: 0 installs, 1 update, 0 removals
+  - Upgrading b/b (1.0.0 => 1.1.0)
+Writing lock file
+Installing dependencies from lock file (including require-dev)
+Package operations: 0 installs, 1 update, 0 removals
+Generating autoload files
+
+--EXPECT--
+Upgrading b/b (1.0.0 => 1.1.0)

+ 10 - 3
tests/Composer/Test/InstallerTest.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Test;
 
+use Composer\DependencyResolver\Request;
 use Composer\Installer;
 use Composer\Console\Application;
 use Composer\IO\BufferIO;
@@ -279,14 +280,20 @@ class InstallerTest extends TestCase
             $updateMirrors = $input->getOption('lock') || count($filteredPackages) != count($packages);
             $packages = $filteredPackages;
 
+            $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
+            if ($input->getOption('with-all-dependencies')) {
+                $updateAllowTransitiveDependencies = Request::UPDATE_TRANSITIVE_ROOT_DEPENDENCIES;
+            } elseif ($input->getOption('with-dependencies')) {
+                $updateAllowTransitiveDependencies = Request::UPDATE_TRANSITIVE_DEPENDENCIES;
+            }
+
             $installer
                 ->setDevMode(!$input->getOption('no-dev'))
                 ->setUpdate(true)
                 ->setDryRun($input->getOption('dry-run'))
                 ->setUpdateMirrors($updateMirrors)
-                ->setUpdateWhitelist($packages)
-                ->setWhitelistTransitiveDependencies($input->getOption('with-dependencies'))
-                ->setWhitelistAllDependencies($input->getOption('with-all-dependencies'))
+                ->setUpdateAllowList($packages)
+                ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
                 ->setPreferStable($input->getOption('prefer-stable'))
                 ->setPreferLowest($input->getOption('prefer-lowest'))
                 ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'));