浏览代码

Partial updates should not corrupt the lock if the installed repo is not up to date with it, fixes #3439, closes #3553

Jordi Boggiano 10 年之前
父节点
当前提交
4507805508

+ 58 - 21
src/Composer/Installer.php

@@ -358,9 +358,12 @@ class Installer
         $repositories = null;
 
         // initialize locker to create aliased packages
-        $installFromLock = false;
-        if (!$this->update && $this->locker->isLocked()) {
-            $installFromLock = true;
+        $installFromLock = !$this->update && $this->locker->isLocked();
+
+        // initialize locked repo if we are installing from lock or in a partial update
+        // and a lock file is present as we need to force install non-whitelisted lock file
+        // packages in that case
+        if ($installFromLock || (!empty($this->updateWhitelist) && $this->locker->isLocked())) {
             try {
                 $lockedRepository = $this->locker->getLockedRepository($withDevReqs);
             } catch (\RuntimeException $e) {
@@ -384,18 +387,20 @@ class Installer
 
         // creating repository pool
         $policy = $this->createPolicy();
-        $pool = $this->createPool($withDevReqs, $lockedRepository);
+        $pool = $this->createPool($withDevReqs, $installFromLock ? $lockedRepository : null);
         $pool->addRepository($installedRepo, $aliases);
-        if ($installFromLock) {
-            $pool->addRepository($lockedRepository, $aliases);
-        }
-
         if (!$installFromLock) {
             $repositories = $this->repositoryManager->getRepositories();
             foreach ($repositories as $repository) {
                 $pool->addRepository($repository, $aliases);
             }
         }
+        // Add the locked repository after the others in case we are doing a
+        // partial update so missing packages can be found there still.
+        // For installs from lock it's the only one added so it is first
+        if ($lockedRepository) {
+            $pool->addRepository($lockedRepository, $aliases);
+        }
 
         // creating requirements request
         $request = $this->createRequest($pool, $this->package, $platformRepo);
@@ -432,16 +437,7 @@ class Installer
             // if the updateWhitelist is enabled, packages not in it are also fixed
             // to the version specified in the lock, or their currently installed version
             if ($this->updateWhitelist) {
-                if ($this->locker->isLocked()) {
-                    try {
-                        $currentPackages = $this->locker->getLockedRepository($withDevReqs)->getPackages();
-                    } catch (\RuntimeException $e) {
-                        // fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
-                        $currentPackages = $this->locker->getLockedRepository()->getPackages();
-                    }
-                } else {
-                    $currentPackages = $installedRepo->getPackages();
-                }
+                $currentPackages = $this->getCurrentPackages($withDevReqs, $installedRepo);
 
                 // collect packages to fixate from root requirements as well as installed packages
                 $candidates = array();
@@ -500,7 +496,7 @@ class Installer
         }
 
         // force dev packages to have the latest links if we update or install from a (potentially new) lock
-        $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links');
+        $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-links');
 
         // solve dependencies
         $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request);
@@ -516,7 +512,7 @@ class Installer
         }
 
         // force dev packages to be updated if we update or install from a (potentially new) lock
-        $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-updates', $operations);
+        $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-updates', $operations);
 
         // execute operations
         if (!$operations) {
@@ -768,7 +764,7 @@ class Installer
         return $request;
     }
 
-    private function processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, $task, array $operations = null)
+    private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, $task, array $operations = null)
     {
         if ($task === 'force-updates' && null === $operations) {
             throw new \InvalidArgumentException('Missing operations argument');
@@ -777,6 +773,10 @@ class Installer
             $operations = array();
         }
 
+        if (!$installFromLock && $this->updateWhitelist) {
+            $currentPackages = $this->getCurrentPackages($withDevReqs, $installedRepo);
+        }
+
         foreach ($localRepo->getCanonicalPackages() as $package) {
             // skip non-dev packages
             if (!$package->isDev()) {
@@ -817,6 +817,26 @@ class Installer
                 if ($this->update) {
                     // skip package if the whitelist is enabled and it is not in it
                     if ($this->updateWhitelist && !$this->isUpdateable($package)) {
+                        // check if non-updateable packages are out of date compared to the lock file to ensure we don't corrupt it
+                        foreach ($currentPackages as $curPackage) {
+                            if ($curPackage->isDev() && $curPackage->getName() === $package->getName() && $curPackage->getVersion() === $package->getVersion()) {
+                                if ($task === 'force-links') {
+                                    $package->setRequires($curPackage->getRequires());
+                                    $package->setConflicts($curPackage->getConflicts());
+                                    $package->setProvides($curPackage->getProvides());
+                                    $package->setReplaces($curPackage->getReplaces());
+                                } elseif ($task === 'force-updates') {
+                                    if (($curPackage->getSourceReference() && $curPackage->getSourceReference() !== $package->getSourceReference())
+                                        || ($curPackage->getDistReference() && $curPackage->getDistReference() !== $package->getDistReference())
+                                    ) {
+                                        $operations[] = new UpdateOperation($package, $curPackage);
+                                    }
+                                }
+
+                                break;
+                            }
+                        }
+
                         continue;
                     }
 
@@ -874,6 +894,23 @@ class Installer
         return $operations;
     }
 
+    /**
+     * Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback
+     */
+    private function getCurrentPackages($withDevReqs, $installedRepo)
+    {
+        if ($this->locker->isLocked()) {
+            try {
+                return $this->locker->getLockedRepository($withDevReqs)->getPackages();
+            } catch (\RuntimeException $e) {
+                // fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
+                return $this->locker->getLockedRepository()->getPackages();
+            }
+        }
+
+        return $installedRepo->getPackages();
+    }
+
     private function getRootAliases()
     {
         if (!$this->update && $this->locker->isLocked()) {

+ 1 - 1
tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test

@@ -65,5 +65,5 @@ update c/uptodate
     "platform-dev": []
 }
 --EXPECT--
-Updating a/old (0.9.0) to a/old (1.0.0)
 Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0)
+Updating a/old (0.9.0) to a/old (1.0.0)

+ 97 - 0
tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test

@@ -0,0 +1,97 @@
+--TEST--
+Partial update forces updates dev reference from lock file for non whitelisted packages
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                {
+                    "name": "a/a", "version": "dev-master",
+                    "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+                    "source": { "reference": "newmaster-a2", "type": "git", "url": "" }
+                },
+                {
+                    "name": "b/b", "version": "dev-master",
+                    "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+                    "source": { "reference": "newmaster-b2", "type": "git", "url": "" }
+                }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "~2.1",
+        "b/b": "~2.1"
+    },
+    "minimum-stability": "dev"
+}
+--INSTALLED--
+[
+    {
+        "name": "a/a", "version": "dev-master", "version_normalized": "9999999-dev",
+        "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+        "source": { "reference": "oldmaster-a", "type": "git", "url": "" },
+        "type": "library"
+    },
+    {
+        "name": "b/b", "version": "dev-master", "version_normalized": "9999999-dev",
+        "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+        "source": { "reference": "oldmaster-b", "type": "git", "url": "" },
+        "type": "library"
+    }
+]
+--LOCK--
+{
+    "packages": [
+        {
+            "name": "a/a", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+            "source": { "reference": "newmaster-a", "type": "git", "url": "" },
+            "type": "library"
+        },
+        {
+            "name": "b/b", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+            "source": { "reference": "oldmaster-b", "type": "git", "url": "" },
+            "type": "library"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--RUN--
+update b/b
+--EXPECT-LOCK--
+{
+    "packages": [
+        {
+            "name": "a/a", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+            "source": { "reference": "newmaster-a", "type": "git", "url": "" },
+            "type": "library"
+        },
+        {
+            "name": "b/b", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+            "source": { "reference": "newmaster-b2", "type": "git", "url": "" },
+            "type": "library"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--EXPECT--
+Updating a/a (dev-master oldmaster-a) to a/a (dev-master newmaster-a)
+Updating b/b (dev-master oldmaster-b) to b/b (dev-master newmaster-b2)

+ 1 - 1
tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test

@@ -65,6 +65,6 @@ update b/unstable
     "platform-dev": []
 }
 --EXPECT--
+Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0)
 Updating a/old (0.9.0) to a/old (1.0.0)
 Updating c/uptodate (2.0.0) to c/uptodate (1.0.0)
-Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0)

+ 105 - 0
tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test

@@ -0,0 +1,105 @@
+--TEST--
+Partial update installs from lock even if package don't exist in public repo anymore
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                {
+                    "name": "a/a", "version": "dev-master",
+                    "extra": { "branch-alias": { "dev-master": "2.3.x-dev" } },
+                    "source": { "reference": "newmaster-a2", "type": "git", "url": "" }
+                },
+                {
+                    "name": "b/b", "version": "dev-master",
+                    "extra": { "branch-alias": { "dev-master": "2.3.x-dev" } },
+                    "source": { "reference": "newmaster-b2", "type": "git", "url": "" },
+                    "require": { "a/a": "dev-master" }
+                }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "~2.1",
+        "b/b": "~2.1"
+    },
+    "minimum-stability": "dev"
+}
+--INSTALLED--
+[
+    {
+        "name": "a/a", "version": "dev-master", "version_normalized": "9999999-dev",
+        "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+        "source": { "reference": "oldmaster-a", "type": "git", "url": "" },
+        "type": "library"
+    },
+    {
+        "name": "b/b", "version": "dev-master", "version_normalized": "9999999-dev",
+        "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+        "source": { "reference": "oldmaster-b", "type": "git", "url": "" },
+        "require": { "a/a": "dev-master" },
+        "type": "library"
+    }
+]
+--LOCK--
+{
+    "packages": [
+        {
+            "name": "a/a", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.2.x-dev" } },
+            "source": { "reference": "newmaster-a", "type": "git", "url": "" },
+            "type": "library"
+        },
+        {
+            "name": "b/b", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } },
+            "source": { "reference": "oldmaster-b", "type": "git", "url": "" },
+            "require": { "a/a": "dev-master" },
+            "type": "library"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--RUN--
+update b/b
+--EXPECT-LOCK--
+{
+    "packages": [
+        {
+            "name": "a/a", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.2.x-dev" } },
+            "source": { "reference": "newmaster-a", "type": "git", "url": "" },
+            "type": "library"
+        },
+        {
+            "name": "b/b", "version": "dev-master",
+            "extra": { "branch-alias": { "dev-master": "2.3.x-dev" } },
+            "source": { "reference": "newmaster-b2", "type": "git", "url": "" },
+            "require": { "a/a": "dev-master" },
+            "type": "library"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--EXPECT--
+Updating a/a (dev-master oldmaster-a) to a/a (dev-master newmaster-a)
+Updating b/b (dev-master oldmaster-b) to b/b (dev-master newmaster-b2)
+Marking a/a (2.2.x-dev newmaster-a) as installed, alias of a/a (dev-master newmaster-a)
+Marking b/b (2.3.x-dev newmaster-b2) as installed, alias of b/b (dev-master newmaster-b2)
+Marking b/b (2.1.x-dev oldmaster-b) as uninstalled, alias of b/b (dev-master oldmaster-b)
+Marking a/a (2.1.x-dev oldmaster-a) as uninstalled, alias of a/a (dev-master oldmaster-a)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/update-alias-lock.test

@@ -31,6 +31,7 @@ Update aliased package does not mess up the lock file
 }
 --LOCK--
 {
+    "_": "outdated lock file, should not have to be loaded in an update",
     "packages": [
         { "package": "a/a", "version": "dev-master", "source-reference": "1234" },
         { "package": "a/a", "version": "dev-master", "alias-pretty-version": "1.0.x-dev", "alias-version": "1.0.9999999.9999999-dev" }

+ 1 - 1
tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test

@@ -43,6 +43,6 @@ Limited update takes rules from lock if available, and not from the installed re
 --RUN--
 update toupdate/installed
 --EXPECT--
-Updating old/installed (0.9.0) to old/installed (1.0.0)
 Updating toupdate/installed (1.0.0) to toupdate/installed (1.1.0)
+Updating old/installed (0.9.0) to old/installed (1.0.0)
 Installing toupdate/notinstalled (1.0.0)