Parcourir la source

Refactor Installer class into separate install and update processes

- Introduce separate Lock and LocalRepo transactions, one for changes
  to the lock file, one for changes to locally installed packages based
  on lock file
- Remove various hacks to keep dev dependencies updated and
  incorporated the functionality into the transaction classes
- Remove installed repo, there are now local repo, locked repo and
  platform repo
- Remove access to local repo from solver, only supply locked packages
- Update can now be run to modify the lock file but not install packages
  to local repo
Nils Adermann il y a 6 ans
Parent
commit
10ada7bf82

+ 17 - 37
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -44,7 +44,7 @@ class DefaultPolicy implements PolicyInterface
         return $constraint->matchSpecific($version, true);
     }
 
-    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false)
+    public function findUpdatePackages(Pool $pool, PackageInterface $package, $mustMatchName = false)
     {
         $packages = array();
 
@@ -57,36 +57,34 @@ class DefaultPolicy implements PolicyInterface
         return $packages;
     }
 
-    public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null)
+    public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null)
     {
-        $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);
+        $packages = $this->groupLiteralsByName($pool, $literals);
 
-        foreach ($packages as &$literals) {
+        foreach ($packages as &$nameLiterals) {
             $policy = $this;
-            usort($literals, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
-                return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
+            usort($nameLiterals, function ($a, $b) use ($policy, $pool, $requiredPackage) {
+                return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
             });
         }
 
-        foreach ($packages as &$literals) {
-            $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
-
-            $literals = $this->pruneToBestVersion($pool, $literals);
-
-            $literals = $this->pruneRemoteAliases($pool, $literals);
+        foreach ($packages as &$sortedLiterals) {
+            $sortedLiterals = $this->pruneToHighestPriority($pool, $sortedLiterals);
+            $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals);
+            $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals);
         }
 
         $selected = call_user_func_array('array_merge', $packages);
 
         // now sort the result across all packages to respect replaces across packages
-        usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
-            return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
+        usort($selected, function ($a, $b) use ($policy, $pool, $requiredPackage) {
+            return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
         });
 
         return $selected;
     }
 
-    protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
+    protected function groupLiteralsByName(Pool $pool, $literals)
     {
         $packages = array();
         foreach ($literals as $literal) {
@@ -95,12 +93,7 @@ class DefaultPolicy implements PolicyInterface
             if (!isset($packages[$packageName])) {
                 $packages[$packageName] = array();
             }
-
-            if (isset($installedMap[abs($literal)])) {
-                array_unshift($packages[$packageName], $literal);
-            } else {
-                $packages[$packageName][] = $literal;
-            }
+            $packages[$packageName][] = $literal;
         }
 
         return $packages;
@@ -109,7 +102,7 @@ class DefaultPolicy implements PolicyInterface
     /**
      * @protected
      */
-    public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false)
+    public function compareByPriority(Pool $pool, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false)
     {
         if ($a->getRepository() === $b->getRepository()) {
             // prefer aliases to the original package
@@ -155,14 +148,6 @@ class DefaultPolicy implements PolicyInterface
             return ($a->id < $b->id) ? -1 : 1;
         }
 
-        if (isset($installedMap[$a->id])) {
-            return -1;
-        }
-
-        if (isset($installedMap[$b->id])) {
-            return 1;
-        }
-
         return ($pool->getPriority($a->id) > $pool->getPriority($b->id)) ? -1 : 1;
     }
 
@@ -214,9 +199,9 @@ class DefaultPolicy implements PolicyInterface
     }
 
     /**
-     * Assumes that installed packages come first and then all highest priority packages
+     * Assumes that highest priority packages come first
      */
-    protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals)
+    protected function pruneToHighestPriority(Pool $pool, array $literals)
     {
         $selected = array();
 
@@ -225,11 +210,6 @@ class DefaultPolicy implements PolicyInterface
         foreach ($literals as $literal) {
             $package = $pool->literalToPackage($literal);
 
-            if (isset($installedMap[$package->id])) {
-                $selected[] = $literal;
-                continue;
-            }
-
             if (null === $priority) {
                 $priority = $pool->getPriority($package->id);
             }

+ 185 - 0
src/Composer/DependencyResolver/LocalRepoTransaction.php

@@ -0,0 +1,185 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+use Composer\Package\AliasPackage;
+use Composer\Repository\PlatformRepository;
+use Composer\Repository\RepositoryInterface;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class LocalRepoTransaction
+{
+    /** @var RepositoryInterface */
+    protected $lockedRepository;
+
+    /** @var RepositoryInterface */
+    protected $localRepository;
+
+    public function __construct($lockedRepository, $localRepository)
+    {
+        $this->lockedRepository = $lockedRepository;
+        $this->localRepository = $localRepository;
+
+        $this->operations = $this->calculateOperations();
+    }
+
+    public function getOperations()
+    {
+        return $this->operations;
+    }
+
+    protected function calculateOperations()
+    {
+        $operations = array();
+
+        $localPackageMap = array();
+        $removeMap = array();
+        foreach ($this->localRepository->getPackages() as $package) {
+            if (isset($localPackageMap[$package->getName()])) {
+                die("Alias?");
+            }
+            $localPackageMap[$package->getName()] = $package;
+            $removeMap[$package->getName()] = $package;
+        }
+
+        $lockedPackages = array();
+        foreach ($this->lockedRepository->getPackages() as $package) {
+            if (isset($localPackageMap[$package->getName()])) {
+                $source = $localPackageMap[$package->getName()];
+
+                // 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?
+                }
+            } else {
+                $operations[] = new Operation\InstallOperation($package);
+                unset($removeMap[$package->getName()]);
+            }
+
+
+/*
+            if (isset($lockedPackages[$package->getName()])) {
+                die("Alias?");
+            }
+            $lockedPackages[$package->getName()] = $package;*/
+        }
+
+        foreach ($removeMap as $name => $package) {
+            $operations[] = new Operation\UninstallOperation($package, null);
+        }
+
+        $operations = $this->movePluginsToFront($operations);
+        $operations = $this->moveUninstallsToFront($operations);
+
+
+        // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place?
+        /*
+        if ('update' === $jobType) {
+            $targetPackage = $operation->getTargetPackage();
+            if ($targetPackage->isDev()) {
+                $initialPackage = $operation->getInitialPackage();
+                if ($targetPackage->getVersion() === $initialPackage->getVersion()
+                    && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference())
+                    && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference())
+                ) {
+                    $this->io->writeError('  - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG);
+                    $this->io->writeError('', true, IOInterface::DEBUG);
+
+                    continue;
+                }
+            }
+        }*/
+
+        return $operations;
+    }
+
+    /**
+     * Workaround: if your packages depend on plugins, we must be sure
+     * that those are installed / updated first; else it would lead to packages
+     * being installed multiple times in different folders, when running Composer
+     * twice.
+     *
+     * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147,
+     * it at least fixes the symptoms and makes usage of composer possible (again)
+     * in such scenarios.
+     *
+     * @param  Operation\OperationInterface[] $operations
+     * @return Operation\OperationInterface[] reordered operation list
+     */
+    private function movePluginsToFront(array $operations)
+    {
+        $pluginsNoDeps = array();
+        $pluginsWithDeps = array();
+        $pluginRequires = array();
+
+        foreach (array_reverse($operations, true) as $idx => $op) {
+            if ($op instanceof Operation\InstallOperation) {
+                $package = $op->getPackage();
+            } elseif ($op instanceof Operation\UpdateOperation) {
+                $package = $op->getTargetPackage();
+            } else {
+                continue;
+            }
+
+            // is this package a plugin?
+            $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer';
+
+            // is this a plugin or a dependency of a plugin?
+            if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) {
+                // get the package's requires, but filter out any platform requirements or 'composer-plugin-api'
+                $requires = array_filter(array_keys($package->getRequires()), function ($req) {
+                    return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req);
+                });
+
+                // is this a plugin with no meaningful dependencies?
+                if ($isPlugin && !count($requires)) {
+                    // plugins with no dependencies go to the very front
+                    array_unshift($pluginsNoDeps, $op);
+                } else {
+                    // capture the requirements for this package so those packages will be moved up as well
+                    $pluginRequires = array_merge($pluginRequires, $requires);
+                    // move the operation to the front
+                    array_unshift($pluginsWithDeps, $op);
+                }
+
+                unset($operations[$idx]);
+            }
+        }
+
+        return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations);
+    }
+
+    /**
+     * Removals of packages should be executed before installations in
+     * case two packages resolve to the same path (due to custom installers)
+     *
+     * @param  Operation\OperationInterface[] $operations
+     * @return Operation\OperationInterface[] reordered operation list
+     */
+    private function moveUninstallsToFront(array $operations)
+    {
+        $uninstOps = array();
+        foreach ($operations as $idx => $op) {
+            if ($op instanceof UninstallOperation) {
+                $uninstOps[] = $op;
+                unset($operations[$idx]);
+            }
+        }
+
+        return array_merge($uninstOps, $operations);
+    }
+}

+ 167 - 0
src/Composer/DependencyResolver/LockTransaction.php

@@ -0,0 +1,167 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+use Composer\DependencyResolver\Operation\OperationInterface;
+use Composer\Package\AliasPackage;
+use Composer\Package\RootAliasPackage;
+use Composer\Package\RootPackageInterface;
+use Composer\Repository\RepositoryInterface;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class LockTransaction
+{
+    protected $policy;
+    /** @var Pool */
+    protected $pool;
+
+    /**
+     * packages in current lock file, platform repo or otherwise present
+     * @var array
+     */
+    protected $presentMap;
+
+    /**
+     * Packages which cannot be mapped, platform repo, root package, other fixed repos
+     * @var array
+     */
+    protected $unlockableMap;
+
+    protected $decisions;
+    protected $transaction;
+
+    public function __construct($policy, $pool, $presentMap, $unlockableMap, $decisions)
+    {
+        $this->policy = $policy;
+        $this->pool = $pool;
+        $this->presentMap = $presentMap;
+        $this->unlockableMap = $unlockableMap;
+        $this->decisions = $decisions;
+
+        $this->operations = $this->calculateOperations();
+    }
+
+    /**
+     * @return OperationInterface[]
+     */
+    public function getOperations()
+    {
+        return $this->operations;
+    }
+
+    protected function calculateOperations()
+    {
+        $operations = array();
+        $lockMeansUpdateMap = $this->findPotentialUpdates();
+
+        foreach ($this->decisions as $i => $decision) {
+            $literal = $decision[Decisions::DECISION_LITERAL];
+            $reason = $decision[Decisions::DECISION_REASON];
+
+            $package = $this->pool->literalToPackage($literal);
+
+            // wanted & !present
+            if ($literal > 0 && !isset($this->presentMap[$package->id])) {
+                if (isset($lockMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
+                    $operations[] = new Operation\UpdateOperation($lockMeansUpdateMap[abs($literal)], $package, $reason);
+
+                    // avoid updates to one package from multiple origins
+                    unset($lockMeansUpdateMap[abs($literal)]);
+                    $ignoreRemove[$source->id] = true;
+                } else {
+                    if ($package instanceof AliasPackage) {
+                        $operations[] = new Operation\MarkAliasInstalledOperation($package, $reason);
+                    } else {
+                        $operations[] = new Operation\InstallOperation($package, $reason);
+                    }
+                }
+            }
+        }
+
+        foreach ($this->decisions as $i => $decision) {
+            $literal = $decision[Decisions::DECISION_LITERAL];
+            $reason = $decision[Decisions::DECISION_REASON];
+            $package = $this->pool->literalToPackage($literal);
+
+            if ($literal <= 0 && isset($this->presentMap[$package->id]) && !isset($ignoreRemove[$package->id])) {
+                if ($package instanceof AliasPackage) {
+                    $operations[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
+                } else {
+                    $operations[] = new Operation\UninstallOperation($package, $reason);
+                }
+            }
+        }
+
+        return $operations;
+    }
+
+    // TODO additionalFixedRepository needs to be looked at here as well?
+    public function getNewLockNonDevPackages()
+    {
+        $packages = array();
+        foreach ($this->decisions as $i => $decision) {
+            $literal = $decision[Decisions::DECISION_LITERAL];
+
+            if ($literal > 0) {
+                $package = $this->pool->literalToPackage($literal);
+                if (!isset($this->unlockableMap[$package->id]) && !($package instanceof AliasPackage) && !($package instanceof RootAliasPackage)) {
+                    $packages[] = $package;
+                }
+            }
+        }
+
+        return $packages;
+    }
+
+    public function getNewLockDevPackages()
+    {
+        $packages = array();
+        return $packages;
+    }
+
+    protected function findPotentialUpdates()
+    {
+        $lockMeansUpdateMap = array();
+
+        foreach ($this->decisions as $i => $decision) {
+            $literal = $decision[Decisions::DECISION_LITERAL];
+            $package = $this->pool->literalToPackage($literal);
+
+            if ($package instanceof AliasPackage) {
+                continue;
+            }
+
+            // !wanted & present
+            if ($literal <= 0 && isset($this->presentMap[$package->id])) {
+                // TODO can't we just look at existing rules?
+                $updates = $this->policy->findUpdatePackages($this->pool, $package);
+
+                $literals = array($package->id);
+
+                foreach ($updates as $update) {
+                    $literals[] = $update->id;
+                }
+
+                foreach ($literals as $updateLiteral) {
+                    if ($updateLiteral !== $literal && !isset($lockMeansUpdateMap[$updateLiteral])) {
+                        $lockMeansUpdateMap[$updateLiteral] = $package;
+                    }
+                }
+            }
+        }
+
+        return $lockMeansUpdateMap;
+    }
+}

+ 2 - 2
src/Composer/DependencyResolver/PolicyInterface.php

@@ -21,7 +21,7 @@ interface PolicyInterface
 {
     public function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
 
-    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package);
+    public function findUpdatePackages(Pool $pool, PackageInterface $package);
 
-    public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null);
+    public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null);
 }

+ 0 - 7
src/Composer/DependencyResolver/Pool.php

@@ -12,18 +12,11 @@
 
 namespace Composer\DependencyResolver;
 
-use Composer\Package\BasePackage;
 use Composer\Package\AliasPackage;
 use Composer\Package\Version\VersionParser;
-use Composer\Repository\RepositorySet;
 use Composer\Semver\Constraint\ConstraintInterface;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Semver\Constraint\EmptyConstraint;
-use Composer\Repository\RepositoryInterface;
-use Composer\Repository\CompositeRepository;
-use Composer\Repository\ComposerRepository;
-use Composer\Repository\InstalledRepositoryInterface;
-use Composer\Repository\PlatformRepository;
 use Composer\Package\PackageInterface;
 
 /**

+ 5 - 1
src/Composer/DependencyResolver/PoolBuilder.php

@@ -53,6 +53,10 @@ class PoolBuilder
 
         // 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;
+        }
         foreach ($request->getJobs() as $job) {
             switch ($job['cmd']) {
                 case 'install':
@@ -99,7 +103,7 @@ class PoolBuilder
         }
 
         foreach ($this->packages as $i => $package) {
-            // we check all alias related packages at once, so no need ot check individual aliases
+            // we check all alias related packages at once, so no need to check individual aliases
             // isset also checks non-null value
             if (!$package instanceof AliasPackage && isset($this->nameConstraints[$package->getName()])) {
                 $constraint = $this->nameConstraints[$package->getName()];

+ 1 - 1
src/Composer/DependencyResolver/Problem.php

@@ -220,7 +220,7 @@ class Problem
         if (isset($job['constraint'])) {
             $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
         } else {
-            $packages = array();
+            $packages = $this->pool->whatProvides($job['packageName'], null);
         }
 
         return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])';

+ 57 - 21
src/Composer/DependencyResolver/Request.php

@@ -12,6 +12,10 @@
 
 namespace Composer\DependencyResolver;
 
+use Composer\Package\Package;
+use Composer\Package\PackageInterface;
+use Composer\Package\RootAliasPackage;
+use Composer\Repository\RepositoryInterface;
 use Composer\Semver\Constraint\ConstraintInterface;
 
 /**
@@ -19,11 +23,14 @@ use Composer\Semver\Constraint\ConstraintInterface;
  */
 class Request
 {
-    protected $jobs;
+    protected $lockedRepository;
+    protected $jobs = array();
+    protected $fixedPackages = array();
+    protected $unlockables = array();
 
-    public function __construct()
+    public function __construct(RepositoryInterface $lockedRepository = null)
     {
-        $this->jobs = array();
+        $this->lockedRepository = $lockedRepository;
     }
 
     public function install($packageName, ConstraintInterface $constraint = null)
@@ -31,11 +38,6 @@ class Request
         $this->addJob($packageName, 'install', $constraint);
     }
 
-    public function update($packageName, ConstraintInterface $constraint = null)
-    {
-        $this->addJob($packageName, 'update', $constraint);
-    }
-
     public function remove($packageName, ConstraintInterface $constraint = null)
     {
         $this->addJob($packageName, 'remove', $constraint);
@@ -43,18 +45,21 @@ class Request
 
     /**
      * Mark an existing package as being installed and having to remain installed
-     *
-     * These jobs will not be tempered with by the solver
-     *
-     * @param string                   $packageName
-     * @param ConstraintInterface|null $constraint
      */
-    public function fix($packageName, ConstraintInterface $constraint = null)
+    public function fixPackage(PackageInterface $package, $lockable = true)
     {
-        $this->addJob($packageName, 'install', $constraint, true);
+        if ($package instanceof RootAliasPackage) {
+            $package = $package->getAliasOf();
+        }
+
+        $this->fixedPackages[] = $package;
+
+        if (!$lockable) {
+            $this->unlockables[] = $package;
+        }
     }
 
-    protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null, $fixed = false)
+    protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null)
     {
         $packageName = strtolower($packageName);
 
@@ -62,17 +67,48 @@ class Request
             'cmd' => $cmd,
             'packageName' => $packageName,
             'constraint' => $constraint,
-            'fixed' => $fixed,
         );
     }
 
-    public function updateAll()
+    public function getJobs()
+    {
+        return $this->jobs;
+    }
+
+    public function getFixedPackages()
     {
-        $this->jobs[] = array('cmd' => 'update-all');
+        return $this->fixedPackages;
     }
 
-    public function getJobs()
+    public function getPresentMap()
+    {
+        $presentMap = array();
+
+        if ($this->lockedRepository) {
+            foreach ($this->lockedRepository as $package) {
+                $presentMap[$package->id] = $package;
+            }
+        }
+
+        foreach ($this->fixedPackages as $package) {
+            $presentMap[$package->id] = $package;
+        }
+
+        return $presentMap;
+    }
+
+    public function getUnlockableMap()
+    {
+        $unlockableMap = array();
+
+        foreach ($this->unlockables as $package) {
+            $unlockableMap[$package->id] = $package;
+        }
+
+        return $unlockableMap;
+    }
+
+    public function getLockMap()
     {
-        return $this->jobs;
     }
 }

+ 1 - 1
src/Composer/DependencyResolver/Rule.php

@@ -231,7 +231,7 @@ abstract class Rule
             case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
                 return $ruleText;
             case self::RULE_PACKAGE_SAME_NAME:
-                return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.';
+                return 'Same name, can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.';
             case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
                 return $ruleText;
             case self::RULE_LEARNED:

+ 19 - 18
src/Composer/DependencyResolver/RuleSetGenerator.php

@@ -24,8 +24,6 @@ class RuleSetGenerator
     protected $policy;
     protected $pool;
     protected $rules;
-    protected $jobs;
-    protected $installedMap;
     protected $addedMap;
     protected $conflictAddedMap;
     protected $addedPackages;
@@ -218,8 +216,6 @@ class RuleSetGenerator
             }
 
             // check obsoletes and implicit obsoletes of a package
-            $isInstalled = isset($this->installedMap[$package->id]);
-
             foreach ($package->getReplaces() as $link) {
                 if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                     continue;
@@ -232,7 +228,7 @@ class RuleSetGenerator
                     }
 
                     if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
-                        $reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
+                        $reason = Rule::RULE_PACKAGE_OBSOLETES;
                         $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
                     }
                 }
@@ -254,21 +250,31 @@ class RuleSetGenerator
         return $impossible;
     }
 
-    protected function addRulesForJobs($ignorePlatformReqs)
+    protected function addRulesForRequest($request, $ignorePlatformReqs)
     {
-        foreach ($this->jobs as $job) {
+        foreach ($request->getFixedPackages() as $package) {
+            $this->addRulesForPackage($package, $ignorePlatformReqs);
+
+            $rule = $this->createInstallOneOfRule(array($package), Rule::RULE_JOB_INSTALL, array(
+                'cmd' => 'fix',
+                'packageName' => $package->getName(),
+                'constraint' => null,
+                'fixed' => true
+            ));
+            $this->addRule(RuleSet::TYPE_JOB, $rule);
+        }
+
+        foreach ($request->getJobs() as $job) {
             switch ($job['cmd']) {
                 case 'install':
-                    if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
+                    if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
                         break;
                     }
 
                     $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                     if ($packages) {
                         foreach ($packages as $package) {
-                            if (!isset($this->installedMap[$package->id])) {
-                                $this->addRulesForPackage($package, $ignorePlatformReqs);
-                            }
+                            $this->addRulesForPackage($package, $ignorePlatformReqs);
                         }
 
                         $rule = $this->createInstallOneOfRule($packages, Rule::RULE_JOB_INSTALL, $job);
@@ -288,21 +294,16 @@ class RuleSetGenerator
         }
     }
 
-    public function getRulesFor($jobs, $installedMap, $ignorePlatformReqs = false)
+    public function getRulesFor(Request $request, $ignorePlatformReqs = false)
     {
-        $this->jobs = $jobs;
         $this->rules = new RuleSet;
-        $this->installedMap = $installedMap;
 
         $this->addedMap = array();
         $this->conflictAddedMap = array();
         $this->addedPackages = array();
         $this->addedPackagesByNames = array();
-        foreach ($this->installedMap as $package) {
-            $this->addRulesForPackage($package, $ignorePlatformReqs);
-        }
 
-        $this->addRulesForJobs($ignorePlatformReqs);
+        $this->addRulesForRequest($request, $ignorePlatformReqs);
 
         $this->addConflictRules();
 

+ 25 - 62
src/Composer/DependencyResolver/Solver.php

@@ -29,23 +29,18 @@ class Solver
     protected $policy;
     /** @var Pool */
     protected $pool = null;
-    /** @var RepositoryInterface */
-    protected $installed;
+
     /** @var RuleSet */
     protected $rules;
     /** @var RuleSetGenerator */
     protected $ruleSetGenerator;
-    /** @var array */
-    protected $jobs;
 
-    /** @var int[] */
-    protected $updateMap = array();
     /** @var RuleWatchGraph */
     protected $watchGraph;
     /** @var Decisions */
     protected $decisions;
-    /** @var int[] */
-    protected $installedMap;
+    /** @var Package[] */
+    protected $fixedMap;
 
     /** @var int */
     protected $propagateIndex;
@@ -67,15 +62,13 @@ class Solver
     /**
      * @param PolicyInterface     $policy
      * @param Pool                $pool
-     * @param RepositoryInterface $installed
      * @param IOInterface         $io
      */
-    public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io)
+    public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io)
     {
         $this->io = $io;
         $this->policy = $policy;
         $this->pool = $pool;
-        $this->installed = $installed;
     }
 
     /**
@@ -164,36 +157,22 @@ class Solver
         }
     }
 
-    protected function setupInstalledMap()
+    protected function setupFixedMap(Request $request)
     {
-        $this->installedMap = array();
-        foreach ($this->installed->getPackages() as $package) {
-            $this->installedMap[$package->id] = $package;
+        $this->fixedMap = array();
+        foreach ($request->getFixedPackages() as $package) {
+            $this->fixedMap[$package->id] = $package;
         }
     }
 
     /**
+     * @param  Request $request
      * @param bool $ignorePlatformReqs
      */
-    protected function checkForRootRequireProblems($ignorePlatformReqs)
+    protected function checkForRootRequireProblems($request, $ignorePlatformReqs)
     {
-        foreach ($this->jobs as $job) {
+        foreach ($request->getJobs() as $job) {
             switch ($job['cmd']) {
-                case 'update':
-                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
-                    foreach ($packages as $package) {
-                        if (isset($this->installedMap[$package->id])) {
-                            $this->updateMap[$package->id] = true;
-                        }
-                    }
-                    break;
-
-                case 'update-all':
-                    foreach ($this->installedMap as $package) {
-                        $this->updateMap[$package->id] = true;
-                    }
-                    break;
-
                 case 'install':
                     if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
                         break;
@@ -212,18 +191,16 @@ class Solver
     /**
      * @param  Request $request
      * @param  bool    $ignorePlatformReqs
-     * @return array
+     * @return LockTransaction
      */
     public function solve(Request $request, $ignorePlatformReqs = false)
     {
-        $this->jobs = $request->getJobs();
-
-        $this->setupInstalledMap();
+        $this->setupFixedMap($request);
 
         $this->io->writeError('Generating rules', true, IOInterface::DEBUG);
         $this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
-        $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
-        $this->checkForRootRequireProblems($ignorePlatformReqs);
+        $this->rules = $this->ruleSetGenerator->getRulesFor($request, $ignorePlatformReqs);
+        $this->checkForRootRequireProblems($request, $ignorePlatformReqs);
         $this->decisions = new Decisions($this->pool);
         $this->watchGraph = new RuleWatchGraph;
 
@@ -240,20 +217,11 @@ class Solver
         $this->io->writeError('', true, IOInterface::DEBUG);
         $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);
 
-        // decide to remove everything that's installed and undecided
-        foreach ($this->installedMap as $packageId => $void) {
-            if ($this->decisions->undecided($packageId)) {
-                $this->decisions->decide(-$packageId, 1, null);
-            }
-        }
-
         if ($this->problems) {
-            throw new SolverProblemsException($this->problems, $this->installedMap, $this->learnedPool);
+            throw new SolverProblemsException($this->problems, $request->getPresentMap(), $this->learnedPool);
         }
 
-        $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);
-
-        return $transaction->getOperations();
+        return new LockTransaction($this->policy, $this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions);
     }
 
     /**
@@ -392,7 +360,7 @@ class Solver
     private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
     {
         // choose best package to install from decisionQueue
-        $literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $decisionQueue, $rule->getRequiredPackage());
+        $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage());
 
         $selectedLiteral = array_shift($literals);
 
@@ -728,19 +696,14 @@ class Solver
                         }
 
                         if ($noneSatisfied && count($decisionQueue)) {
-                            // prune all update packages until installed version
-                            // except for requested updates
-                            if (count($this->installed) != count($this->updateMap)) {
-                                $prunedQueue = array();
-                                foreach ($decisionQueue as $literal) {
-                                    if (isset($this->installedMap[abs($literal)])) {
-                                        $prunedQueue[] = $literal;
-                                        if (isset($this->updateMap[abs($literal)])) {
-                                            $prunedQueue = $decisionQueue;
-                                            break;
-                                        }
-                                    }
+                            // if any of the options in the decision queue are fixed, only use those
+                            $prunedQueue = array();
+                            foreach ($decisionQueue as $literal) {
+                                if (isset($this->fixedMap[abs($literal)])) {
+                                    $prunedQueue[] = $literal;
                                 }
+                            }
+                            if (!empty($prunedQueue)) {
                                 $decisionQueue = $prunedQueue;
                             }
                         }

+ 0 - 244
src/Composer/DependencyResolver/Transaction.php

@@ -1,244 +0,0 @@
-<?php
-
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- *     Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer\DependencyResolver;
-
-use Composer\Package\AliasPackage;
-
-/**
- * @author Nils Adermann <naderman@naderman.de>
- */
-class Transaction
-{
-    protected $policy;
-    protected $pool;
-    protected $installedMap;
-    protected $decisions;
-    protected $transaction;
-
-    public function __construct($policy, $pool, $installedMap, $decisions)
-    {
-        $this->policy = $policy;
-        $this->pool = $pool;
-        $this->installedMap = $installedMap;
-        $this->decisions = $decisions;
-        $this->transaction = array();
-    }
-
-    public function getOperations()
-    {
-        $installMeansUpdateMap = $this->findUpdates();
-
-        $updateMap = array();
-        $installMap = array();
-        $uninstallMap = array();
-
-        foreach ($this->decisions as $i => $decision) {
-            $literal = $decision[Decisions::DECISION_LITERAL];
-            $reason = $decision[Decisions::DECISION_REASON];
-
-            $package = $this->pool->literalToPackage($literal);
-
-            // wanted & installed || !wanted & !installed
-            if (($literal > 0) == isset($this->installedMap[$package->id])) {
-                continue;
-            }
-
-            if ($literal > 0) {
-                if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
-                    $source = $installMeansUpdateMap[abs($literal)];
-
-                    $updateMap[$package->id] = array(
-                        'package' => $package,
-                        'source' => $source,
-                        'reason' => $reason,
-                    );
-
-                    // avoid updates to one package from multiple origins
-                    unset($installMeansUpdateMap[abs($literal)]);
-                    $ignoreRemove[$source->id] = true;
-                } else {
-                    $installMap[$package->id] = array(
-                        'package' => $package,
-                        'reason' => $reason,
-                    );
-                }
-            }
-        }
-
-        foreach ($this->decisions as $i => $decision) {
-            $literal = $decision[Decisions::DECISION_LITERAL];
-            $reason = $decision[Decisions::DECISION_REASON];
-            $package = $this->pool->literalToPackage($literal);
-
-            if ($literal <= 0 &&
-                isset($this->installedMap[$package->id]) &&
-                !isset($ignoreRemove[$package->id])) {
-                $uninstallMap[$package->id] = array(
-                    'package' => $package,
-                    'reason' => $reason,
-                );
-            }
-        }
-
-        $this->transactionFromMaps($installMap, $updateMap, $uninstallMap);
-
-        return $this->transaction;
-    }
-
-    protected function transactionFromMaps($installMap, $updateMap, $uninstallMap)
-    {
-        $queue = array_map(
-            function ($operation) {
-                return $operation['package'];
-            },
-            $this->findRootPackages($installMap, $updateMap)
-        );
-
-        $visited = array();
-
-        while (!empty($queue)) {
-            $package = array_pop($queue);
-            $packageId = $package->id;
-
-            if (!isset($visited[$packageId])) {
-                $queue[] = $package;
-
-                if ($package instanceof AliasPackage) {
-                    $queue[] = $package->getAliasOf();
-                } else {
-                    foreach ($package->getRequires() as $link) {
-                        $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
-
-                        foreach ($possibleRequires as $require) {
-                            $queue[] = $require;
-                        }
-                    }
-                }
-
-                $visited[$package->id] = true;
-            } else {
-                if (isset($installMap[$packageId])) {
-                    $this->install(
-                        $installMap[$packageId]['package'],
-                        $installMap[$packageId]['reason']
-                    );
-                    unset($installMap[$packageId]);
-                }
-                if (isset($updateMap[$packageId])) {
-                    $this->update(
-                        $updateMap[$packageId]['source'],
-                        $updateMap[$packageId]['package'],
-                        $updateMap[$packageId]['reason']
-                    );
-                    unset($updateMap[$packageId]);
-                }
-            }
-        }
-
-        foreach ($uninstallMap as $uninstall) {
-            $this->uninstall($uninstall['package'], $uninstall['reason']);
-        }
-    }
-
-    protected function findRootPackages($installMap, $updateMap)
-    {
-        $packages = $installMap + $updateMap;
-        $roots = $packages;
-
-        foreach ($packages as $packageId => $operation) {
-            $package = $operation['package'];
-
-            if (!isset($roots[$packageId])) {
-                continue;
-            }
-
-            foreach ($package->getRequires() as $link) {
-                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
-
-                foreach ($possibleRequires as $require) {
-                    if ($require !== $package) {
-                        unset($roots[$require->id]);
-                    }
-                }
-            }
-        }
-
-        return $roots;
-    }
-
-    protected function findUpdates()
-    {
-        $installMeansUpdateMap = array();
-
-        foreach ($this->decisions as $i => $decision) {
-            $literal = $decision[Decisions::DECISION_LITERAL];
-            $package = $this->pool->literalToPackage($literal);
-
-            if ($package instanceof AliasPackage) {
-                continue;
-            }
-
-            // !wanted & installed
-            if ($literal <= 0 && isset($this->installedMap[$package->id])) {
-                $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
-
-                $literals = array($package->id);
-
-                foreach ($updates as $update) {
-                    $literals[] = $update->id;
-                }
-
-                foreach ($literals as $updateLiteral) {
-                    if ($updateLiteral !== $literal) {
-                        $installMeansUpdateMap[abs($updateLiteral)] = $package;
-                    }
-                }
-            }
-        }
-
-        return $installMeansUpdateMap;
-    }
-
-    protected function install($package, $reason)
-    {
-        if ($package instanceof AliasPackage) {
-            return $this->markAliasInstalled($package, $reason);
-        }
-
-        $this->transaction[] = new Operation\InstallOperation($package, $reason);
-    }
-
-    protected function update($from, $to, $reason)
-    {
-        $this->transaction[] = new Operation\UpdateOperation($from, $to, $reason);
-    }
-
-    protected function uninstall($package, $reason)
-    {
-        if ($package instanceof AliasPackage) {
-            return $this->markAliasUninstalled($package, $reason);
-        }
-
-        $this->transaction[] = new Operation\UninstallOperation($package, $reason);
-    }
-
-    protected function markAliasInstalled($package, $reason)
-    {
-        $this->transaction[] = new Operation\MarkAliasInstalledOperation($package, $reason);
-    }
-
-    protected function markAliasUninstalled($package, $reason)
-    {
-        $this->transaction[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
-    }
-}

+ 3 - 2
src/Composer/EventDispatcher/EventDispatcher.php

@@ -19,6 +19,7 @@ use Composer\IO\IOInterface;
 use Composer\Composer;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\Repository\CompositeRepository;
+use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositorySet;
 use Composer\Script;
 use Composer\Installer\PackageEvent;
@@ -130,9 +131,9 @@ class EventDispatcher
      * @return int return code of the executed script if any, for php scripts a false return
      *             value is changed to 1, anything else to 0
      */
-    public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations = array())
+    public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, RepositoryInterface $lockedRepo, Request $request, array $operations = array())
     {
-        return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $installedRepo, $request, $operations));
+        return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $lockedRepo, $request, $operations));
     }
 
     /**

Fichier diff supprimé car celui-ci est trop grand
+ 239 - 565
src/Composer/Installer.php


+ 7 - 7
src/Composer/Installer/SuggestedPackagesReporter.php

@@ -96,21 +96,21 @@ class SuggestedPackagesReporter
      * @param  RepositoryInterface       $installedRepo Installed packages
      * @return SuggestedPackagesReporter
      */
-    public function output(RepositoryInterface $installedRepo = null)
+    public function output(RepositoryInterface $lockedRepo = null)
     {
         $suggestedPackages = $this->getPackages();
-        $installedPackages = array();
-        if (null !== $installedRepo && ! empty($suggestedPackages)) {
-            foreach ($installedRepo->getPackages() as $package) {
-                $installedPackages = array_merge(
-                    $installedPackages,
+        $lockedPackages = array();
+        if (null !== $lockedRepo && ! empty($suggestedPackages)) {
+            foreach ($lockedRepo->getPackages() as $package) {
+                $lockedPackages = array_merge(
+                    $lockedPackages,
                     $package->getNames()
                 );
             }
         }
 
         foreach ($suggestedPackages as $suggestion) {
-            if (in_array($suggestion['target'], $installedPackages)) {
+            if (in_array($suggestion['target'], $lockedPackages)) {
                 continue;
             }
 

+ 18 - 5
src/Composer/Package/Locker.php

@@ -40,6 +40,7 @@ class Locker
     private $dumper;
     private $process;
     private $lockDataCache;
+    private $virtualFileWritten;
 
     /**
      * Initializes packages locker.
@@ -108,7 +109,7 @@ class Locker
      */
     public function isLocked()
     {
-        if (!$this->lockFile->exists()) {
+        if (!$this->virtualFileWritten && !$this->lockFile->exists()) {
             return false;
         }
 
@@ -282,10 +283,11 @@ class Locker
      * @param bool   $preferStable
      * @param bool   $preferLowest
      * @param array  $platformOverrides
+     * @param bool   $write             Whether to actually write data to disk, useful in tests and for --dry-run
      *
      * @return bool
      */
-    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest, array $platformOverrides)
+    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest, array $platformOverrides, $write = true)
     {
         $lock = array(
             '_readme' => array('This file locks the dependencies of your project to a known state',
@@ -325,7 +327,11 @@ class Locker
 
         if (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) {
             if ($this->lockFile->exists()) {
-                unlink($this->lockFile->getPath());
+                if ($write) {
+                    unlink($this->lockFile->getPath());
+                } else {
+                    $this->virtualFileWritten = false;
+                }
             }
 
             return false;
@@ -337,8 +343,15 @@ class Locker
             $isLocked = false;
         }
         if (!$isLocked || $lock !== $this->getLockData()) {
-            $this->lockFile->write($lock);
-            $this->lockDataCache = null;
+            if ($write) {
+                $this->lockFile->write($lock);
+//                $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock, 448 & JsonFile::JSON_PRETTY_PRINT));
+                $this->lockDataCache = null;
+                $this->virtualFileWritten = false;
+            } else {
+                $this->virtualFileWritten = true;
+                $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock, 448 & JsonFile::JSON_PRETTY_PRINT));
+            }
 
             return true;
         }

+ 22 - 32
tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php

@@ -29,7 +29,7 @@ class DefaultPolicyTest extends TestCase
     /** @var ArrayRepository */
     protected $repo;
     /** @var ArrayRepository */
-    protected $repoInstalled;
+    protected $repoLocked;
     /** @var DefaultPolicy */
     protected $policy;
 
@@ -37,7 +37,7 @@ class DefaultPolicyTest extends TestCase
     {
         $this->repositorySet = new RepositorySet(array(), 'dev');
         $this->repo = new ArrayRepository;
-        $this->repoInstalled = new ArrayRepository;
+        $this->repoLocked = new ArrayRepository;
 
         $this->policy = new DefaultPolicy;
     }
@@ -52,7 +52,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA->getId());
         $expected = array($packageA->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -68,7 +68,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA1->getId(), $packageA2->getId());
         $expected = array($packageA2->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -84,7 +84,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA1->getId(), $packageA2->getId());
         $expected = array($packageA2->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -101,7 +101,7 @@ class DefaultPolicyTest extends TestCase
         $expected = array($packageA1->getId());
 
         $policy = new DefaultPolicy(true);
-        $selected = $policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -117,24 +117,24 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA1->getId(), $packageA2->getId());
         $expected = array($packageA2->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
 
-    public function testSelectNewestOverInstalled()
+    public function testSelectNewestOverLocked()
     {
         $this->repo->addPackage($packageA = $this->getPackage('A', '2.0'));
-        $this->repoInstalled->addPackage($packageAInstalled = $this->getPackage('A', '1.0'));
-        $this->repositorySet->addRepository($this->repoInstalled);
+        $this->repoLocked->addPackage($packageAInstalled = $this->getPackage('A', '1.0'));
         $this->repositorySet->addRepository($this->repo);
+        $this->repositorySet->addRepository($this->repoLocked);
 
         $pool = $this->repositorySet->createPoolForPackage('A');
 
         $literals = array($packageA->getId(), $packageAInstalled->getId());
         $expected = array($packageA->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, $this->mapFromRepo($this->repoInstalled), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -146,16 +146,16 @@ class DefaultPolicyTest extends TestCase
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $otherRepository->addPackage($packageAImportant = $this->getPackage('A', '1.0'));
 
-        $this->repositorySet->addRepository($this->repoInstalled);
         $this->repositorySet->addRepository($otherRepository);
         $this->repositorySet->addRepository($this->repo);
+        $this->repositorySet->addRepository($this->repoLocked);
 
         $pool = $this->repositorySet->createPoolForPackage('A');
 
         $literals = array($packageA->getId(), $packageAImportant->getId());
         $expected = array($packageAImportant->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -177,7 +177,7 @@ class DefaultPolicyTest extends TestCase
 
         $literals = array($package1->getId(), $package2->getId(), $package3->getId(), $package4->getId());
         $expected = array($package2->getId());
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
 
@@ -188,7 +188,7 @@ class DefaultPolicyTest extends TestCase
         $pool = $this->repositorySet->createPoolForPackage('A');
 
         $expected = array($package4->getId());
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -205,9 +205,9 @@ class DefaultPolicyTest extends TestCase
         $repoImportant->addPackage($packageA2AliasImportant = new AliasPackage($packageA2Important, '2.1.9999999.9999999-dev', '2.1.x-dev'));
         $packageAAliasImportant->setRootPackageAlias(true);
 
-        $this->repositorySet->addRepository($this->repoInstalled);
         $this->repositorySet->addRepository($repoImportant);
         $this->repositorySet->addRepository($this->repo);
+        $this->repositorySet->addRepository($this->repoLocked);
 
         $pool = $this->repositorySet->createPoolForPackage('A');
 
@@ -219,7 +219,7 @@ class DefaultPolicyTest extends TestCase
 
         $expected = array($packageAAliasImportant->getId());
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -239,7 +239,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA->getId(), $packageB->getId());
         $expected = $literals;
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -258,7 +258,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA->getId(), $packageB->getId());
         $expected = $literals;
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $this->policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }
@@ -279,7 +279,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA->getId(), $packageB->getId());
         $expected = $literals;
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals, 'vendor-a/package');
+        $selected = $this->policy->selectPreferredPackages($pool, $literals, 'vendor-a/package');
         $this->assertEquals($expected, $selected);
 
         // test with reversed order in repo
@@ -295,20 +295,10 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA->getId(), $packageB->getId());
         $expected = $literals;
 
-        $selected = $this->policy->selectPreferredPackages($pool, array(), $literals, 'vendor-a/package');
+        $selected = $this->policy->selectPreferredPackages($pool, $literals, 'vendor-a/package');
         $this->assertSame($expected, $selected);
     }
 
-    protected function mapFromRepo(RepositoryInterface $repo)
-    {
-        $map = array();
-        foreach ($repo->getPackages() as $package) {
-            $map[$package->getId()] = true;
-        }
-
-        return $map;
-    }
-
     public function testSelectLowest()
     {
         $policy = new DefaultPolicy(false, true);
@@ -322,7 +312,7 @@ class DefaultPolicyTest extends TestCase
         $literals = array($packageA1->getId(), $packageA2->getId());
         $expected = array($packageA1->getId());
 
-        $selected = $policy->selectPreferredPackages($pool, array(), $literals);
+        $selected = $policy->selectPreferredPackages($pool, $literals);
 
         $this->assertSame($expected, $selected);
     }

+ 3 - 17
tests/Composer/Test/DependencyResolver/RequestTest.php

@@ -31,14 +31,12 @@ class RequestTest extends TestCase
 
         $request = new Request();
         $request->install('foo');
-        $request->fix('bar');
         $request->remove('foobar');
 
         $this->assertEquals(
             array(
-                array('cmd' => 'install', 'packageName' => 'foo', 'constraint' => null, 'fixed' => false),
-                array('cmd' => 'install', 'packageName' => 'bar', 'constraint' => null, 'fixed' => true),
-                array('cmd' => 'remove', 'packageName' => 'foobar', 'constraint' => null, 'fixed' => false),
+                array('cmd' => 'install', 'packageName' => 'foo', 'constraint' => null),
+                array('cmd' => 'remove', 'packageName' => 'foobar', 'constraint' => null),
             ),
             $request->getJobs()
         );
@@ -60,21 +58,9 @@ class RequestTest extends TestCase
 
         $this->assertEquals(
             array(
-                    array('cmd' => 'install', 'packageName' => 'foo', 'constraint' => $constraint, 'fixed' => false),
+                    array('cmd' => 'install', 'packageName' => 'foo', 'constraint' => $constraint),
             ),
             $request->getJobs()
         );
     }
-
-    public function testUpdateAll()
-    {
-        $request = new Request();
-
-        $request->updateAll();
-
-        $this->assertEquals(
-            array(array('cmd' => 'update-all')),
-            $request->getJobs()
-        );
-    }
 }

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

@@ -30,6 +30,7 @@ class SolverTest extends TestCase
     protected $repoSet;
     protected $repo;
     protected $repoInstalled;
+    protected $repoLocked;
     protected $request;
     protected $policy;
     protected $solver;
@@ -39,6 +40,7 @@ class SolverTest extends TestCase
         $this->repoSet = new RepositorySet(array());
         $this->repo = new ArrayRepository;
         $this->repoInstalled = new InstalledArrayRepository;
+        $this->repoLocked = new ArrayRepository;
 
         $this->request = new Request($this->repoSet);
         $this->policy = new DefaultPolicy;
@@ -59,6 +61,7 @@ class SolverTest extends TestCase
     public function testSolverRemoveIfNotInstalled()
     {
         $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0'));
+        $this->repoLocked->addPackage(clone $packageA);
         $this->reposComplete();
 
         $this->checkSolverResult(array(
@@ -919,8 +922,8 @@ class SolverTest extends TestCase
 
     protected function reposComplete()
     {
-        $this->repoSet->addRepository($this->repoInstalled);
         $this->repoSet->addRepository($this->repo);
+        $this->repoSet->addRepository($this->repoLocked);
     }
 
     protected function createSolver()

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

@@ -95,6 +95,55 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
         "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": [

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

@@ -16,6 +16,7 @@ use Composer\Installer;
 use Composer\Console\Application;
 use Composer\IO\BufferIO;
 use Composer\Json\JsonFile;
+use Composer\Package\Dumper\ArrayDumper;
 use Composer\Util\Filesystem;
 use Composer\Repository\ArrayRepository;
 use Composer\Repository\RepositoryManager;
@@ -74,10 +75,30 @@ class InstallerTest extends TestCase
         foreach ($repositories as $repository) {
             $repositoryManager->addRepository($repository);
         }
-
-        $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock();
         $installationManager = new InstallationManagerMock();
 
+        // emulate a writable lock file
+        $lockData = null;
+        $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
+        $lockJsonMock->expects($this->any())
+            ->method('read')
+            ->will($this->returnCallback(function() use (&$lockData) {
+                return json_decode($lockData, true);
+            }));
+        $lockJsonMock->expects($this->any())
+            ->method('exists')
+            ->will($this->returnCallback(function () use (&$lockData) {
+                return $lockData !== null;
+            }));
+        $lockJsonMock->expects($this->any())
+            ->method('write')
+            ->will($this->returnCallback(function ($value, $options = 0) use (&$lockData) {
+                $lockData = json_encode($value, JSON_PRETTY_PRINT);
+            }));
+
+        $tempLockData = null;
+        $locker = new Locker($io, $lockJsonMock, $repositoryManager, $installationManager, '{}');
+
         $autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock();
 
         $installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator);
@@ -91,7 +112,7 @@ class InstallerTest extends TestCase
         $expectedUninstalled = isset($options['uninstall']) ? $options['uninstall'] : array();
 
         $installed = $installationManager->getInstalledPackages();
-        $this->assertSame($expectedInstalled, $installed);
+        $this->assertEquals($this->makePackagesComparable($expectedInstalled), $this->makePackagesComparable($installed));
 
         $updated = $installationManager->getUpdatedPackages();
         $this->assertSame($expectedUpdated, $updated);
@@ -100,6 +121,17 @@ class InstallerTest extends TestCase
         $this->assertSame($expectedUninstalled, $uninstalled);
     }
 
+    protected function makePackagesComparable($packages)
+    {
+        $dumper = new ArrayDumper();
+
+        $comparable = [];
+        foreach ($packages as $package) {
+            $comparable[] = $dumper->dump($package);
+        }
+        return $comparable;
+    }
+
     public function provideInstaller()
     {
         $cases = array();
@@ -109,11 +141,11 @@ class InstallerTest extends TestCase
 
         $a = $this->getPackage('A', '1.0.0', 'Composer\Package\RootPackage');
         $a->setRequires(array(
-            new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
+            'b' => new Link('A', 'B', $v = $this->getVersionConstraint('=', '1.0.0'), 'requires', $v->getPrettyString()),
         ));
         $b = $this->getPackage('B', '1.0.0');
         $b->setRequires(array(
-            new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')),
+            'a' => new Link('B', 'A', $v = $this->getVersionConstraint('=', '1.0.0'), 'requires', $v->getPrettyString()),
         ));
 
         $cases[] = array(
@@ -129,11 +161,11 @@ class InstallerTest extends TestCase
 
         $a = $this->getPackage('A', '1.0.0', 'Composer\Package\RootPackage');
         $a->setRequires(array(
-            new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
+            'b' => new Link('A', 'B', $v = $this->getVersionConstraint('=', '1.0.0'), 'requires', $v->getPrettyString()),
         ));
         $b = $this->getPackage('B', '1.0.0');
         $b->setRequires(array(
-            new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')),
+            'a' => new Link('B', 'A', $v = $this->getVersionConstraint('=', '1.0.0'), 'requires', $v->getPrettyString()),
         ));
 
         $cases[] = array(
@@ -144,6 +176,7 @@ class InstallerTest extends TestCase
             ),
         );
 
+        // TODO why are there not more cases with uninstall/update?
         return $cases;
     }
 
@@ -182,13 +215,24 @@ class InstallerTest extends TestCase
         $repositoryManager = $composer->getRepositoryManager();
         $repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock));
 
+        // emulate a writable lock file
+        $lockData = $lock ? json_encode($lock, JSON_PRETTY_PRINT): null;
         $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
         $lockJsonMock->expects($this->any())
             ->method('read')
-            ->will($this->returnValue($lock));
+            ->will($this->returnCallback(function() use (&$lockData) {
+                return json_decode($lockData, true);
+            }));
         $lockJsonMock->expects($this->any())
             ->method('exists')
-            ->will($this->returnValue(true));
+            ->will($this->returnCallback(function () use (&$lockData) {
+                return $lockData !== null;
+            }));
+        $lockJsonMock->expects($this->any())
+            ->method('write')
+            ->will($this->returnCallback(function ($value, $options = 0) use (&$lockData) {
+                $lockData = json_encode($value, JSON_PRETTY_PRINT);
+            }));
 
         if ($expectLock) {
             $actualLock = array();
@@ -245,7 +289,7 @@ class InstallerTest extends TestCase
 
         $application->setAutoExit(false);
         $appOutput = fopen('php://memory', 'w+');
-        $input = new StringInput($run);
+        $input = new StringInput($run.' -vvv');
         $input->setInteractive(false);
         $result = $application->run($input, new StreamOutput($appOutput));
         fseek($appOutput, 0);

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff