Browse Source

Fix handling of reference updates and root references

Nils Adermann 5 years ago
parent
commit
f1e4ccbe1d

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

@@ -62,9 +62,10 @@ class LocalRepoTransaction
                 // do we need to update?
                 if ($package->getVersion() != $localPackageMap[$package->getName()]->getVersion()) {
                     $operations[] = new Operation\UpdateOperation($source, $package);
-                } else {
-                    // TODO do we need to update metadata? force update based on reference?
+                } elseif ($package->isDev() && $package->getSourceReference() !== $localPackageMap[$package->getName()]->getSourceReference()) {
+                    $operations[] = new Operation\UpdateOperation($source, $package);
                 }
+                unset($removeMap[$package->getName()]);
             } else {
                 $operations[] = new Operation\InstallOperation($package);
                 unset($removeMap[$package->getName()]);

+ 2 - 9
src/Composer/DependencyResolver/LockTransaction.php

@@ -108,7 +108,7 @@ class LockTransaction
     }
 
     // TODO additionalFixedRepository needs to be looked at here as well?
-    public function getNewLockNonDevPackages(array $rootForceReferences)
+    public function getNewLockNonDevPackages()
     {
         $packages = array();
         foreach ($this->decisions as $i => $decision) {
@@ -117,14 +117,6 @@ class LockTransaction
             if ($literal > 0) {
                 $package = $this->pool->literalToPackage($literal);
                 if (!isset($this->unlockableMap[$package->id]) && !($package instanceof AliasPackage) && !($package instanceof RootAliasPackage)) {
-
-                    echo "rootRef? ".$package->getName()."\n";
-                    // TODO can we really just do this for all of them here? What if we're doing a partial update, should this change anyway?
-                    if (isset($rootForceReferences[$package->getName()])) {
-                        echo "rootRef! ".$package->getName()."\n";
-                        $package->setSourceReference($rootForceReferences[$package->getName()]);
-                    }
-
                     $packages[] = $package;
                 }
             }
@@ -135,6 +127,7 @@ class LockTransaction
 
     public function getNewLockDevPackages()
     {
+        // TODO this is empty?
         $packages = array();
         return $packages;
     }

+ 33 - 7
src/Composer/DependencyResolver/PoolBuilder.php

@@ -14,11 +14,11 @@ namespace Composer\DependencyResolver;
 
 use Composer\Package\AliasPackage;
 use Composer\Package\BasePackage;
+use Composer\Package\Package;
 use Composer\Package\PackageInterface;
 use Composer\Repository\AsyncRepositoryInterface;
 use Composer\Repository\ComposerRepository;
 use Composer\Repository\InstalledRepositoryInterface;
-use Composer\Repository\LockArrayRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Semver\Constraint\MultiConstraint;
@@ -31,6 +31,7 @@ class PoolBuilder
     private $isPackageAcceptableCallable;
     private $filterRequires;
     private $rootAliases;
+    private $rootReferences;
 
     private $aliasMap = array();
     private $nameConstraints = array();
@@ -46,16 +47,17 @@ class PoolBuilder
         $this->filterRequires = $filterRequires;
     }
 
-    public function buildPool(array $repositories, array $rootAliases, Request $request)
+    public function buildPool(array $repositories, array $rootAliases, array $rootReferences, Request $request)
     {
         $pool = new Pool($this->filterRequires);
         $this->rootAliases = $rootAliases;
+        $this->rootReferences = $rootReferences;
 
         // TODO do we really want the request here? kind of want a root requirements thingy instead
         $loadNames = array();
         foreach ($request->getFixedPackages() as $package) {
             // TODO can actually use very specific constraint
-            $loadNames[$package->getname()] = null;
+            $loadNames[$package->getName()] = null;
         }
         foreach ($request->getJobs() as $job) {
             switch ($job['cmd']) {
@@ -94,7 +96,7 @@ class PoolBuilder
 
                 foreach ($packages as $package) {
                     if (call_user_func($this->isPackageAcceptableCallable, $package->getNames(), $package->getStability())) {
-                        $newLoadNames += $this->loadPackage($package, $key);
+                        $newLoadNames += $this->loadPackage($request, $package, $key);
                     }
                 }
             }
@@ -132,7 +134,7 @@ class PoolBuilder
             if ($repository instanceof PlatformRepository ||
                 $repository instanceof InstalledRepositoryInterface) {
                 foreach ($repository->getPackages() as $package) {
-                    $this->loadPackage($package, $key);
+                    $this->loadPackage($request, $package, $key);
                 }
             }
         }
@@ -146,7 +148,7 @@ class PoolBuilder
         return $pool;
     }
 
-    private function loadPackage(PackageInterface $package, $repoIndex)
+    private function loadPackage(Request $request, PackageInterface $package, $repoIndex)
     {
         $index = count($this->packages);
         $this->packages[] = $package;
@@ -156,8 +158,18 @@ class PoolBuilder
             $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package;
         }
 
-        // handle root package aliases
         $name = $package->getName();
+
+        // we're simply setting the root references on all versions for a name here and rely on the solver to pick the
+        // right version. It'd be more work to figure out which versions and which aliases of those versions this may
+        // apply to
+        if (isset($this->rootReferences[$name])) {
+            // do not modify the references on already locked packages
+            if (!$request->isFixedPackage($package)) {
+                $this->setReferences($package, $this->rootReferences[$name]);
+            }
+        }
+
         if (isset($this->rootAliases[$name][$package->getVersion()])) {
             $alias = $this->rootAliases[$name][$package->getVersion()];
             if ($package instanceof AliasPackage) {
@@ -194,5 +206,19 @@ class PoolBuilder
 
         return $loadNames;
     }
+
+    private function setReferences(Package $package, $reference)
+    {
+        $package->setSourceReference($reference);
+
+        // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL
+        // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this?
+        if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) {
+            $package->setDistReference($reference);
+            $package->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl()));
+        } elseif ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it
+            $package->setDistReference($reference);
+        }
+    }
 }
 

+ 6 - 1
src/Composer/DependencyResolver/Request.php

@@ -52,7 +52,7 @@ class Request
             $package = $package->getAliasOf();
         }
 
-        $this->fixedPackages[] = $package;
+        $this->fixedPackages[spl_object_hash($package)] = $package;
 
         if (!$lockable) {
             $this->unlockables[] = $package;
@@ -80,6 +80,11 @@ class Request
         return $this->fixedPackages;
     }
 
+    public function isFixedPackage(PackageInterface $package)
+    {
+        return isset($this->fixedPackages[spl_object_hash($package)]);
+    }
+
     public function getPresentMap()
     {
         $presentMap = array();

+ 10 - 32
src/Composer/Installer.php

@@ -39,6 +39,7 @@ use Composer\Package\CompletePackage;
 use Composer\Package\Link;
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\Dumper\ArrayDumper;
+use Composer\Package\Package;
 use Composer\Repository\RepositorySet;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Package\Locker;
@@ -375,25 +376,21 @@ class Installer
         // to the version specified in the lock
         if ($this->updateWhitelist) {
             foreach ($lockedRepository->getPackages() as $lockedPackage) {
+                // TODO should this really be checking acceptability here?
                 if (!$this->isUpdateable($lockedPackage) && $repositorySet->isPackageAcceptable($lockedPackage->getNames(), $lockedPackage->getStability())) {
-                    // need to actually allow for metadata updates at all times, so we want to fix the most recent prefered package in the repo set instead
-                    $packages = $repositorySet->findPackages($lockedPackage->getName(), new Constraint('=', $lockedPackage->getVersion()));
-                    $lockedPackage = isset($packages[0]) ? $packages[0] : $lockedPackage;
-
-                    // in how far do we need to reset requirements here, theoretically it's the same version so nothing should have changed, but for a dev version it could have?
-
-
                     // TODO add reason for fix?
                     $request->fixPackage($lockedPackage);
                 }
             }
         }
 
+        // TODO reenable events
         //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request);
 
         $pool = $repositorySet->createPool($request);
 
         // TODO ensure that the solver always picks most recent reference for dev packages, so they get updated even when just a new commit is pushed but version is unchanged
+        // should already be solved by using the remote package in all cases in the pool
 
         // solve dependencies
         $solver = new Solver($policy, $pool, $this->io);
@@ -426,7 +423,7 @@ class Installer
         $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());
 
         $updatedLock = $this->locker->setLockData(
-            $lockTransaction->getNewLockNonDevPackages($this->package->getReferences()),
+            $lockTransaction->getNewLockNonDevPackages(),
             $lockTransaction->getNewLockDevPackages(),
             $platformReqs,
             $platformDevReqs,
@@ -504,6 +501,8 @@ class Installer
             }
         }
 
+        $this->io->write('foo');
+
         if ($doInstall) {
             // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false
             return $this->doInstall($localRepo, true);
@@ -703,7 +702,7 @@ class Installer
         $this->fixedRootPackage->setRequires(array());
         $this->fixedRootPackage->setDevRequires(array());
 
-        $repositorySet = new RepositorySet($rootAliases, $minimumStability, $stabilityFlags, $rootConstraints);
+        $repositorySet = new RepositorySet($rootAliases, $this->package->getReferences(), $minimumStability, $stabilityFlags, $rootConstraints);
         $repositorySet->addRepository(new InstalledArrayRepository(array($this->fixedRootPackage)));
         $repositorySet->addRepository($platformRepo);
         if ($this->additionalFixedRepository) {
@@ -793,28 +792,7 @@ class Installer
         return $normalizedAliases;
     }
 
-    private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl)
-    {
-        $oldSourceRef = $package->getSourceReference();
-
-        if ($package->getSourceUrl() !== $sourceUrl) {
-            $package->setSourceType($sourceType);
-            $package->setSourceUrl($sourceUrl);
-            $package->setSourceReference($sourceReference);
-        }
-
-        // only update dist url for github/bitbucket/gitlab dists as they use a combination of dist url + dist reference to install
-        // but for other urls this is ambiguous and could result in bad outcomes
-        if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $distUrl)) {
-            $package->setDistUrl($distUrl);
-            $this->updateInstallReferences($package, $sourceReference);
-        }
-
-        if ($this->updateWhitelist && !$this->isUpdateable($package)) {
-            $this->updateInstallReferences($package, $oldSourceRef);
-        }
-    }
-
+    // TODO do we still need this function?
     private function updateInstallReferences(PackageInterface $package, $reference)
     {
         if (!$reference) {
@@ -910,7 +888,7 @@ class Installer
             }
         }
 
-        $repositorySet = new RepositorySet(array(), 'dev');
+        $repositorySet = new RepositorySet(array(), array(), 'dev');
         $repositorySet->addRepository($lockRepo);
 
         $seen = array();

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

@@ -29,6 +29,8 @@ class RepositorySet
 {
     /** @var array */
     private $rootAliases;
+    /** @var array */
+    private $rootReferences;
 
     /** @var RepositoryInterface[] */
     private $repositories = array();
@@ -40,9 +42,10 @@ class RepositorySet
     /** @var Pool */
     private $pool;
 
-    public function __construct(array $rootAliases = array(), $minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
+    public function __construct(array $rootAliases = array(), array $rootReferences = array(), $minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
     {
         $this->rootAliases = $rootAliases;
+        $this->rootReferences = $rootReferences;
 
         $this->acceptableStabilities = array();
         foreach (BasePackage::$stabilities as $stability => $value) {
@@ -151,7 +154,7 @@ class RepositorySet
     {
         $poolBuilder = new PoolBuilder(array($this, 'isPackageAcceptable'), $this->filterRequires);
 
-        return $this->pool = $poolBuilder->buildPool($this->repositories, $this->rootAliases, $request);
+        return $this->pool = $poolBuilder->buildPool($this->repositories, $this->rootAliases, $this->rootReferences, $request);
     }
 
     // TODO unify this with above in some simpler version without "request"?

+ 1 - 1
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -930,7 +930,7 @@ class SolverTest extends TestCase
 
     protected function createSolver()
     {
-        $this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), $this->repoInstalled, new NullIO());
+        $this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), new NullIO());
     }
 
     protected function checkSolverResult(array $expected)

+ 7 - 7
tests/Composer/Test/Fixtures/installer/update-changes-url.test

@@ -3,10 +3,10 @@ Update updates URLs for updated packages if they have changed
 
 a/a is dev and gets everything updated as it updates to a new ref
 b/b is a tag and gets everything updated by updating the package URL directly
-c/c is a tag and not whitelisted and gets the new URL but keeps its old ref
+c/c is a tag and not whitelisted and remains unchanged
 d/d is dev but with a #ref so it should get URL updated but not the reference
 e/e is dev and newly installed with a #ref so it should get the correct URL but with the #111 ref
-e/e is dev but not whitelisted and gets the new URL but keeps its old ref
+f/f is dev but not whitelisted and remains unchanged
 g/g is dev and installed in a different ref than the #ref, so it gets updated and gets the new URL but not the new ref
 --COMPOSER--
 {
@@ -161,8 +161,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
         },
         {
             "name": "c/c", "version": "1.0.0",
-            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/newc", "type": "git" },
-            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" },
             "type": "library"
         },
         {
@@ -179,8 +179,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
         },
         {
             "name": "f/f", "version": "dev-master",
-            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" },
-            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" },
             "type": "library"
         },
         {
@@ -208,6 +208,6 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
 --RUN--
 update a/a b/b d/d g/g
 --EXPECT--
-Installing e/e (dev-master 1111111)
 Updating a/a (dev-master 1111111) to a/a (dev-master 2222222)
+Installing e/e (dev-master 1111111)
 Updating g/g (dev-master 0000000) to g/g (dev-master 1111111)

+ 213 - 0
tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test

@@ -0,0 +1,213 @@
+--TEST--
+Update mirrors updates URLs for all packages if they have changed without updating versions
+
+a/a is dev and gets everything updated as it updates to a new ref
+b/b is a tag and gets everything updated by updating the package URL directly
+c/c is a tag and not whitelisted and gets the new URL but keeps its old ref
+d/d is dev but with a #ref so it should get URL updated but not the reference
+e/e is dev and newly installed with a #ref so it should get the correct URL but with the #111 ref
+e/e is dev but not whitelisted and gets the new URL but keeps its old ref
+g/g is dev and installed in a different ref than the #ref, so it gets updated and gets the new URL but not the new ref
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                {
+                    "name": "a/a", "version": "dev-master",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                },
+                {
+                    "name": "b/b", "version": "2.0.3",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                },
+                {
+                    "name": "c/c", "version": "1.0.0",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/c/newc", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                },
+                {
+                    "name": "d/d", "version": "dev-master",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/d/newd", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                },
+                {
+                    "name": "e/e", "version": "dev-master",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/e/newe", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                },
+                {
+                    "name": "f/f", "version": "dev-master",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/f/newf", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                },
+                {
+                    "name": "g/g", "version": "dev-master",
+                    "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/g/newg", "type": "git" },
+                    "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/zipball/2222222222222222222222222222222222222222", "type": "zip" }
+                }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "dev-master",
+        "b/b": "2.0.3",
+        "c/c": "1.0.0",
+        "d/d": "dev-master#1111111111111111111111111111111111111111",
+        "e/e": "dev-master#1111111111111111111111111111111111111111",
+        "f/f": "dev-master",
+        "g/g": "dev-master#1111111111111111111111111111111111111111"
+    }
+}
+--INSTALLED--
+[
+    {
+        "name": "a/a", "version": "dev-master",
+        "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" },
+        "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" }
+    },
+    {
+        "name": "b/b", "version": "2.0.3",
+        "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/b", "type": "git" },
+        "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip" }
+    },
+    {
+        "name": "c/c", "version": "1.0.0",
+        "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" },
+        "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" }
+    },
+    {
+        "name": "d/d", "version": "dev-master",
+        "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/d", "type": "git" },
+        "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip" }
+    },
+    {
+        "name": "f/f", "version": "dev-master",
+        "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" },
+        "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" }
+    },
+    {
+        "name": "g/g", "version": "dev-master",
+        "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" },
+        "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip" }
+    }
+]
+--LOCK--
+{
+    "packages": [
+        {
+            "name": "a/a", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "b/b", "version": "2.0.3",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/b", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "c/c", "version": "1.0.0",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "d/d", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/d", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "f/f", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "g/g", "version": "dev-master",
+            "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" },
+            "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip" },
+            "type": "library"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {},
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--EXPECT-LOCK--
+{
+    "packages": [
+        {
+            "name": "a/a", "version": "dev-master",
+            "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" },
+            "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "b/b", "version": "2.0.3",
+            "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" },
+            "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "c/c", "version": "1.0.0",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/newc", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "d/d", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/newd", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "e/e", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/e/newe", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "f/f", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        },
+        {
+            "name": "g/g", "version": "dev-master",
+            "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/g/newg", "type": "git" },
+            "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/zipball/1111111111111111111111111111111111111111", "type": "zip" },
+            "type": "library"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "a/a": 20,
+        "d/d": 20,
+        "e/e": 20,
+        "f/f": 20,
+        "g/g": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--RUN--
+update a/a b/b d/d g/g
+--EXPECT--
+Installing e/e (dev-master 1111111)
+Updating a/a (dev-master 1111111) to a/a (dev-master 2222222)
+Updating g/g (dev-master 0000000) to g/g (dev-master 1111111)