port 'doUpgrade' and make it available at toplevel
authorKlaus Kaempf <kkaempf@suse.de>
Tue, 31 Jan 2006 19:26:16 +0000 (19:26 +0000)
committerKlaus Kaempf <kkaempf@suse.de>
Tue, 31 Jan 2006 19:26:16 +0000 (19:26 +0000)
zypp/Resolver.cc
zypp/Resolver.h
zypp/solver/detail/Resolver.cc
zypp/solver/detail/Resolver.h
zypp/solver/detail/ResolverUpgrade.cc [new file with mode: 0644]

index 19dc77f..9a25aa3 100644 (file)
@@ -12,6 +12,7 @@
 #include <iostream>
 
 #include "zypp/Resolver.h"
+#include "zypp/UpgradeStatistics.h"
 #include "zypp/solver/detail/Resolver.h"
 
 using namespace std;
@@ -57,15 +58,16 @@ namespace zypp
 
   void Resolver::verifySystem ()
   { return _pimpl->verifySystem(); }
-  void Resolver::establishState ()
-  { return _pimpl->establishState(); }
+  void Resolver::establishPool ()
+  { return _pimpl->establishPool(); }
   bool Resolver::resolvePool ()
   { return _pimpl->resolvePool (); }
   ResolverProblemList Resolver::problems ()
   { return _pimpl->problems (); }
   bool Resolver::applySolutions( const ProblemSolutionList & solutions )
   { return _pimpl->applySolutions (solutions); }      
-    
+  void Resolver::doUpgrade( UpgradeStatistics & opt_stats_r )
+  { return _pimpl->doUpgrade(opt_stats_r); }
 
   // ResolverContext_constPtr bestContext (void) const;
 
index 57df2f4..3cee509 100644 (file)
@@ -19,6 +19,7 @@
 #include "zypp/base/PtrTypes.h"
 
 #include "zypp/ResPool.h"
+#include "zypp/UpgradeStatistics.h"
 #include "zypp/solver/detail/Resolver.h"
 #include "zypp/solver/detail/ResolverContext.h"
 #include "zypp/ProblemTypes.h"
@@ -53,13 +54,13 @@ namespace zypp
     void verifySystem (void);
 
     /**
-     * Establish state of 'higher level' Resolvables
+     * Establish state of 'higher level' Resolvables in Pool
      *
      * Must be called when dealing with non-package resolvables,
      * like Patches, Patterns, and Products
      *
      **/
-    void establishState (void);
+    void establishPool (void);
 
     /**
      * Resolve package dependencies:
@@ -75,6 +76,21 @@ namespace zypp
     bool resolvePool (void);
 
     /**
+     * Do an distribution upgrade
+     *
+     * This will run a full upgrade on the pool, taking all upgrade
+     * dependencies (provide/obsolete for package renames, split-
+     * provides, etc.) into account and actually removing installed
+     * packages if no upgrade exists.
+     *
+     * To be run with great caution. It basically brings your
+     * system 'back to start'.
+     * Quite helpful to get back to a 'sane state'. Quite disastrous
+     * since you'll loose all non-distribution packages
+     **/
+    void doUpgrade( UpgradeStatistics & opt_stats_r );
+
+    /**
      * Return the dependency problems found by the last call to
      * resolveDependencies(). If there were no problems, the returned
      * list will be empty.
index 3a8c603..21637ee 100644 (file)
@@ -47,8 +47,8 @@ IMPL_PTR_TYPE(Resolver);
 
 //---------------------------------------------------------------------------
 
-ostream&
-operator<<( ostream& os, const Resolver & resolver)
+std::ostream &
+Resolver::dumpOn( std::ostream & os ) const
 {
     return os << "<resolver/>";
 }
@@ -243,6 +243,34 @@ Resolver::verifySystem (void)
 
 //---------------------------------------------------------------------------
 
+// copy marked item from solution back to pool
+
+static void
+solution_to_pool (PoolItem_Ref item, const ResStatus & status, void *data)
+{
+    if (status.isToBeInstalled()) {
+       item.status().setToBeInstalled(ResStatus::SOLVER);
+    }
+    else if (status.isToBeUninstalled()) {
+       item.status().setToBeUninstalled(ResStatus::SOLVER);
+    }
+    else if (status.isIncomplete()
+            || status.isNeeded()) {
+       item.status().setIncomplete();
+    }
+    else if (status.isUnneeded()) {
+       item.status().setUnneeded();
+    }
+    else if (status.isSatisfied()) {
+       item.status().setSatisfied();
+    }
+    return;
+}
+
+
+//---------------------------------------------------------------------------
+
+
 // establish state
 
 struct EstablishState : public resfilter::OnCapMatchCallbackFunctor
@@ -300,6 +328,23 @@ Resolver::establishState (ResolverContext_Ptr context)
     return;
 }
 
+
+void
+Resolver::establishPool ()
+{
+    establishState ();                                         // establish !
+    ResolverContext_Ptr solution = bestContext();
+
+    if (solution) {                                            // copy solution back to pool
+       solution->foreachMarked (solution_to_pool, NULL);
+    }
+    else {
+       ERR << "establishState did not return a bestContext" << endl;
+    }
+
+    return;
+}
+
 //---------------------------------------------------------------------------
 
 bool
@@ -507,7 +552,7 @@ struct CollectTransact : public resfilter::PoolItemFilterFunctor
     {
        ResStatus status = item.status();
        if (status.isBySolver()) {                      // clear any solver transactions
-           item.status().setNoTransact (ResStatus::SOLVER);
+           item.status().setNoTransact(ResStatus::SOLVER);
        }
        if (status.isUninstalled()) {                   // transact && uninstalled
            resolver.addPoolItemToInstall(item);        // -> install! 
@@ -520,26 +565,6 @@ struct CollectTransact : public resfilter::PoolItemFilterFunctor
 };
 
 
-// copy marked item from solution back to pool
-
-static void
-solution_to_pool (PoolItem_Ref item, const ResStatus & status, void *data)
-{
-    if (status.isToBeInstalled()) {
-       item.status().setToBeInstalled (ResStatus::SOLVER);
-    }
-    else if (status.isToBeUninstalled()) {
-       item.status().setToBeUninstalled (ResStatus::SOLVER);
-    }
-    else if (status.isIncomplete()
-            || status.isNeeded()) {
-       item.status().setIncomplete();
-    }
-    return;
-}
-
-
-
 //  This function loops over the pool and grabs
 //  all item.status().transacts() and item.status().byUser()
 //  It clears all previous bySolver() states also
index a1cea70..c37b3c2 100644 (file)
 #include "zypp/solver/detail/Types.h"
 #include "zypp/solver/detail/ResolverQueue.h"
 #include "zypp/solver/detail/ResolverContext.h"
+
 #include "zypp/ProblemTypes.h"
 #include "zypp/ResolverProblem.h"
 #include "zypp/ProblemSolution.h"
+#include "zypp/UpgradeStatistics.h"
 
 #include "zypp/CapSet.h"
 
+
 /////////////////////////////////////////////////////////////////////////
 namespace zypp
 { ///////////////////////////////////////////////////////////////////////
@@ -84,6 +87,10 @@ class Resolver : public base::ReferenceCounted, private base::NonCopyable {
 
     std::set<Source_Ref> _subscribed;
 
+    // helpers
+    bool doesObsoleteCapability (PoolItem_Ref candidate, const Capability & cap);
+    bool doesObsoleteItem (PoolItem_Ref candidate, PoolItem_Ref installed);
+
   public:
 
     Resolver (const ResPool & pool);
@@ -91,7 +98,9 @@ class Resolver : public base::ReferenceCounted, private base::NonCopyable {
 
     // ---------------------------------- I/O
 
-    friend std::ostream& operator<<(std::ostream&, const Resolver &resolver);
+    virtual std::ostream & dumpOn( std::ostream & str ) const;
+    friend std::ostream& operator<<(std::ostream& str, const Resolver & obj)
+    { return obj.dumpOn (str); }
 
     // ---------------------------------- accessors
 
@@ -130,9 +139,12 @@ class Resolver : public base::ReferenceCounted, private base::NonCopyable {
 
     void verifySystem (void);
     void establishState (const ResolverContext_Ptr context = NULL);
+    void establishPool (void);
     bool resolveDependencies (const ResolverContext_Ptr context = NULL);
     bool resolvePool (void);
 
+    void doUpgrade( zypp::UpgradeStatistics & opt_stats_r );
+
     ResolverProblemList problems (void) const;
     bool applySolutions (const ProblemSolutionList &solutions);
 
diff --git a/zypp/solver/detail/ResolverUpgrade.cc b/zypp/solver/detail/ResolverUpgrade.cc
new file mode 100644 (file)
index 0000000..961c184
--- /dev/null
@@ -0,0 +1,597 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*---------------------------------------------------------------------\
+|                          ____ _   __ __ ___                          |
+|                         |__  / \ / / . \ . \                         |
+|                           / / \ V /|  _/  _/                         |
+|                          / /__ | | | | | |                           |
+|                         /_____||_| |_| |_|                           |
+|                                                                      |
+\---------------------------------------------------------------------*/
+/* ResolverUpgrade.cc
+ *
+ * Implements the distribution upgrade algorithm.
+ *
+ * Copyright (C) 2005 SUSE Linux Products GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/*
+  stolen from PMPackageManager_update.cc
+  original author Michael Andres <ma@suse.de>
+  zypp port by Klaus Kaempf <kkaempf@suse.de>
+
+/-*/
+
+#include "zypp/CapSet.h"
+#include "zypp/capability/SplitCap.h"
+
+#include "zypp/base/Logger.h"
+#include "zypp/base/String.h"
+#include "zypp/base/Gettext.h"
+
+#include "zypp/base/Algorithm.h"
+#include "zypp/ResPool.h"
+#include "zypp/ResStatus.h"
+#include "zypp/ResFilters.h"
+#include "zypp/CapFilters.h"
+#include "zypp/Capability.h"
+#include "zypp/CapFactory.h"
+#include "zypp/VendorAttr.h"
+#include "zypp/Package.h"
+
+#include "zypp/solver/detail/Helper.h"
+#include "zypp/solver/detail/Resolver.h"
+
+#include "zypp/Target.h"
+
+/////////////////////////////////////////////////////////////////////////
+namespace zypp
+{ ///////////////////////////////////////////////////////////////////////
+  ///////////////////////////////////////////////////////////////////////
+  namespace solver
+  { /////////////////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////
+    namespace detail
+    { ///////////////////////////////////////////////////////////////////
+
+using namespace std;
+using namespace zypp;
+using zypp::capability::SplitCap;
+
+
+// check if downgrade is allowed
+//
+// both must have allowed vendor (e.g. 'SuSE', 'Novell', ...) and candidates buildtime must be
+// newer.
+
+static bool
+downgrade_allowed (PoolItem_Ref installed, PoolItem_Ref candidate)
+{
+    if ( installed->edition().compare (candidate->edition()) > 0 )
+       return false; // candidate is newer
+
+    static VendorAttr *va = VendorAttr::vendorAttr();
+
+    Resolvable::constPtr ires = installed.resolvable();
+    Package::constPtr ipkg = asKind<Package>(ires);
+    Resolvable::constPtr cres = candidate.resolvable();
+    Package::constPtr cpkg = asKind<Package>(cres);
+
+    if ( va->isKnown( ipkg->vendor() )
+        && va->isKnown( cpkg->vendor() ) )
+    {
+#warning Had Y2PM::runningFromSystem
+       return( ipkg->buildtime() >= cpkg->buildtime() );
+    }
+    return false;
+}
+
+
+
+struct FindObsoletes : public resfilter::OnCapMatchCallbackFunctor
+{
+    bool obsoletes;
+
+    FindObsoletes ()
+       : obsoletes (false)
+    { }
+
+    bool operator()( PoolItem_Ref provider, const Capability & match )
+    {
+       obsoletes = true;                                       // we have a match
+       return false;                                   // stop looping here
+    }
+};
+
+
+// does the candidate obsolete the capability ?
+
+bool
+Resolver::doesObsoleteCapability (PoolItem_Ref candidate, const Capability & cap)
+{
+    DBG << "doesObsoleteCapability " << candidate << ", " << cap << endl;
+
+    Dep dep (Dep::OBSOLETES);
+    FindObsoletes info;
+    invokeOnEach( _pool.byCapabilityIndexBegin( cap.index(), dep ),
+                 _pool.byCapabilityIndexEnd( cap.index(), dep ),
+                 resfilter::callOnCapMatchIn( dep, cap, functor::functorRef<bool,PoolItem_Ref,Capability>(info) ) );
+
+    DBG << (info.obsoletes ? "YES" : "NO");
+    return info.obsoletes;
+}
+
+
+bool
+Resolver::doesObsoleteItem (PoolItem_Ref candidate, PoolItem_Ref installed)
+{
+    CapFactory factory;
+    Capability installedCap =  factory.parse ( installed->kind(), installed->name(), Rel::EQ, installed->edition());
+
+    return doesObsoleteCapability (candidate, installedCap);
+}
+
+
+//-----------------------------------------------------------------------------
+
+
+// find all available providers for installed name
+
+struct FindProviders : public resfilter::OnCapMatchCallbackFunctor
+{
+    PoolItemSet providers;             // the providers which matched
+
+    FindProviders ()
+    { }
+
+    bool operator()( PoolItem_Ref provider, const Capability & match )
+    {
+       if ( provider.status().isToBeUninstalled() ) {
+           DBG << "  IGNORE relation match (package is tagged to delete): " << match << " ==> " << provider << endl;
+       }
+       else {
+           DBG << "  relation match: " << match << " ==> " << provider << endl;
+           providers.insert (provider);
+       }
+       return true;
+    }
+};
+
+
+//-----------------------------------------------------------------------------
+
+///////////////////////////////////////////////////////////////////
+//
+//
+//     METHOD NAME : Resolver::doUpgrade
+//     METHOD TYPE : int
+//
+//     DESCRIPTION : go through all installed (but not yet touched by user)
+//             packages and look for update candidates
+//             handle splitprovides and replaced and dropped
+//
+void
+Resolver::doUpgrade( UpgradeStatistics & opt_stats_r )
+{
+  typedef map<PoolItem_Ref,PoolItem_Ref> CandidateMap;
+
+  typedef intrusive_ptr<const SplitCap> SplitCapPtr;
+  typedef map<SplitCapPtr,PoolItemSet>  SplitMap;
+  typedef map<PoolItem_Ref,PoolItemSet> TodoMap;
+
+  CandidateMap candidatemap;
+
+  PoolItemList _update_items;
+
+  SplitMap    splitmap;
+  TodoMap     applyingSplits;
+  TodoMap     addSplitted;
+  TodoMap     addProvided;
+  TodoMap     addMultiProvided;
+
+  MIL << "doUpgrade start... "
+    << "(delete_unmaintained:" << (opt_stats_r.delete_unmaintained?"yes":"no") << ")"
+    << endl;
+
+  _update_items.clear();
+  {
+    UpgradeOptions opts( opt_stats_r );
+    opt_stats_r = UpgradeStatistics();
+    (UpgradeOptions&)opt_stats_r = opts;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Reset all auto states and build PoolItemSet of available candidates
+  // (those that do not belong to PoolItems set to delete).
+  //
+  // On the fly rememeber splitprovides and afterwards check, which
+  // of them do apply.
+  ///////////////////////////////////////////////////////////////////
+  PoolItemSet available; // candidates available for install (no matter if selected for install or not)
+
+  for ( ResPool::const_iterator it = _pool.begin(); it != _pool.end(); ++it ) {
+    PoolItem_Ref item = *it;
+    PoolItem_Ref candidate;
+    PoolItem_Ref installed;
+    item.status().setNoTransact(ResStatus::APPL_HIGH);
+
+    if ( item.status().isToBeUninstalled() ) {
+      DBG << "doUpgrade available: SKIP to delete " << item << endl;
+      ++opt_stats_r.pre_todel;
+      continue;
+    }
+
+    if ( item.status().isInstalled() ) {
+      installed = item;
+      candidate = Helper::findUpdateItem( _pool, installed);
+      if (!candidate) {
+       DBG << "doUpgrade available: SKIP no candidate for " << installed << endl;
+       ++opt_stats_r.pre_nocand;
+       continue;
+      }
+      candidatemap[installed] = candidate;
+    }
+    else {                                     // assume Uninstalled
+      installed = PoolItem_Ref();
+      candidate = item;
+    }
+
+#warning FIXME needs locks
+#if 0
+    if ( item->isLocked() ) {
+      DBG << "doUpgrade available: SKIP taboo candidate " << item << endl;
+      ++opt_stats_r.pre_nocand;
+      continue;
+    }
+#endif
+
+    ++opt_stats_r.pre_avcand;
+#warning this should add the best candidate
+    available.insert( candidate );
+
+MIL << "installed " << installed << ", candidate " << candidate << endl;
+
+    // remember any splitprovides to packages actually installed.
+    CapSet caps = candidate->dep (Dep::PROVIDES);
+    for (CapSet::iterator cit = caps.begin(); cit != caps.end(); ++cit ) {
+#warning How do I get to the SplitCap ?
+#if 0
+      SplitCapPtr scap (asKind<SplitCap>(*cit));
+      if (scap) {
+MIL << "has split cap " << *scap << endl;
+       if (!installed) {
+           installed = Helper::findInstalledByNameAndKind (_pool, scap->name_str(), ResTraits<zypp::Package>::kind);
+       }
+       if ( installed ) {
+           splitmap[scap].insert( candidate );
+       }
+      }
+#endif
+    }
+
+  } // iterate over the complete pool
+
+#warning Cant update from broken install medium like STABLE
+#if 0
+  // filter packages with requires that are not fulfilled by other candidates,
+  // to reduce errors a bit when trying to update from a broken installation
+  // medium (ie. STABLE)
+  {
+    CheckSetDeps::BrokenMap broken;
+    CheckSetDeps checker(available, broken);
+
+    checker.setTrackRelations(false);
+    checker.checkAll();
+
+    if(!broken.empty())
+    {
+      CheckSetDeps::BrokenMap::iterator bit, bend;
+      for(bit = broken.begin(), bend = broken.end(); bit != bend; ++bit)
+      {
+       DBG << bit->first->name() << " is broken, not considering it for update" << endl;
+       available.remove(bit->first);
+       --opt_stats_r.pre_avcand;
+       ++opt_stats_r.pre_nocand;
+      }
+    }
+  }
+#endif
+
+  MIL << "doUpgrade: " << opt_stats_r.pre_todel  << " packages tagged to delete" << endl;
+  MIL << "doUpgrade: " << opt_stats_r.pre_nocand << " packages without candidate (foreign, replaced or dropped)" << endl;
+  MIL << "doUpgrade: " << opt_stats_r.pre_avcand << " packages available for update" << endl;
+
+  MIL << "doUpgrade: going to check " << splitmap.size() << " probabely splitted packages" << endl;
+  {
+    ///////////////////////////////////////////////////////////////////
+    // splitmap entries are gouped by PoolItems (we know this). So get the
+    // filelist as a new PoolItem occurres, and use it for consecutive entries.
+    //
+    // On the fly buld SplitPkgMap from splits that do apply (i.e. file is
+    // in PoolItems's filelist). The way splitmap was created, candidates added
+    // are not initially tagged to delete!
+    ///////////////////////////////////////////////////////////////////
+
+    PoolItem_Ref citem;
+
+    for ( SplitMap::iterator it = splitmap.begin(); it != splitmap.end(); ++it ) {
+#warning How to access the target ?
+#if 0
+      if (Target::providesFile (it->first->name_str(), it->first->path_str())) {
+       DBG << "  " << it->second.size() << " package(s) for " << it->first << endl;
+       applyingSplits[citem].insert( it->second.begin(), it->second.end() );
+       DBG << "  split count for " << citem->name() << " now " << applyingSplits[citem].size() << endl;
+      }
+      else {
+       DBG << "  " << it-> first << " does not apply" << endl;
+      }
+#endif
+    }
+    splitmap.clear();
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Now iterate installed packages, not selected to delete, and
+  // figure out what might be an appropriate replacement. Current
+  // packages state is changed immediately. Additional packages are
+  // reported but set to install later.
+  ///////////////////////////////////////////////////////////////////
+  MIL << "doUpgrade pass 1..." << endl;
+
+  for ( ResPool::const_iterator it = _pool.begin(); it != _pool.end(); ++it ) {
+
+    PoolItem_Ref installed(*it);
+    ResStatus status (installed.status());
+
+    if ( ! status.isInstalled() ) {
+      continue;
+    }
+    ++opt_stats_r.chk_installed_total;
+
+    if ( status.transacts() ) {                                                // we know its installed, if it transacts also
+      DBG << "SKIP to delete: " << it->resolvable() << endl;   // it'll be deleted
+      ++opt_stats_r.chk_already_todel;
+      continue;
+    }
+
+#warning This needs locks
+#if 0
+    if ( (*it)->is_taboo() ) {
+      DBG << "SKIP taboo: " << (*it)->installedObj() << endl;
+      ++opt_stats_r.chk_is_taboo;
+      _update_items.push_back ( *it ); // remember in problem list ?
+      continue;
+    }
+#endif
+
+    CandidateMap::iterator cand_it = candidatemap.find(installed);
+
+    bool probably_dropped = false;
+
+    DBG << "REPLACEMENT FOR " << installed << endl;
+    ///////////////////////////////////////////////////////////////////
+    // figure out replacement
+    ///////////////////////////////////////////////////////////////////
+    if ( cand_it != candidatemap.end() ) {
+
+      PoolItem_Ref candidate (cand_it->second);
+
+      if ( ! candidate.status().isToBeInstalled() ) {
+
+       if ( installed->edition().compare (candidate->edition()) < 0 ) {          // new version
+         candidate.status().setToBeInstalled(ResStatus::APPL_HIGH);
+         DBG << " ==> INSTALL (new version): " << candidate << endl;
+         ++opt_stats_r.chk_to_update;
+       } else {
+         // check whether to downgrade:
+
+         if (!downgrade_allowed (installed, candidate)) {
+           DBG << " ==> (keep installed)" << candidate << endl;
+           ++opt_stats_r.chk_to_keep_installed;
+         } else {
+           candidate.status().setToBeInstalled(ResStatus::APPL_HIGH);
+           DBG << " ==> INSTALL (SuSE version downgrade): " << candidate << endl;
+           ++opt_stats_r.chk_to_downgrade;
+         }
+       }
+      } else {
+       DBG << " ==> INSTALL (preselected): " << candidate << endl;
+       ++opt_stats_r.chk_already_toins;
+      }
+
+    }
+    else {             // no candidate
+
+      // replaced or dropped (anyway there's no candidate for this!)
+      // If unique provides exists check if obsoleted (replaced).
+      // Remember new package for 2nd pass.
+
+      Dep dep (Dep::PROVIDES);
+      CapFactory factory;
+      Capability installedCap =  factory.parse ( installed->kind(), installed->name(), Rel::EQ, installed->edition());
+
+      FindProviders info;
+
+      invokeOnEach( _pool.byCapabilityIndexBegin( installed->name(), dep ),
+                   _pool.byCapabilityIndexEnd( installed->name(), dep ),
+                   functor::chain (resfilter::ByUninstalled (),
+                                   resfilter::callOnCapMatchIn( dep, installedCap, functor::functorRef<bool,PoolItem,Capability>(info) ) ) );
+
+      int num_providers = info.providers.size();
+
+      DBG << "lookup " << num_providers << " provides for installed " << installedCap << endl;
+
+      switch ( info.providers.size() ) {
+      case 0:
+       DBG << " ==> (dropped)" << endl;
+       // wait untill splits are processed. Might be a split obsoletes
+       // this one (i.e. package replaced but not provided by new one).
+       // otherwise it's finaly dropped.
+       probably_dropped = true;
+       break;
+      case 1:
+        addProvided[installed] = info.providers;
+       DBG << " ==> REPLACED by: " << (*info.providers.begin()) << endl;
+       // count stats later
+       // check obsoletes later
+       break;
+      default:
+       addMultiProvided[installed] = info.providers;
+       DBG << " ==> pass 2 (" << info.providers.size() << " times provided)" << endl;
+       // count stats later
+       // check obsoletes later
+       break;
+      }
+
+    }  // no candidate
+
+    ///////////////////////////////////////////////////////////////////
+    // anyway check for packages split off
+    ///////////////////////////////////////////////////////////////////
+
+    TodoMap::iterator sit = applyingSplits.find( installed );
+    if ( sit != applyingSplits.end() ) {
+      PoolItemSet & toadd( sit->second );
+      if ( !toadd.size() ) {
+       INT << "Empty SplitPkgMap entry for " << installed << endl;
+      } else {
+       for ( PoolItemSet::iterator ait = toadd.begin(); ait != toadd.end(); ++ait ) {
+         PoolItem_Ref split_candidate = *ait;
+         DBG << " ==> ADD (splitted): " << split_candidate << endl;
+         if ( probably_dropped
+              && split_candidate.status().isUninstalled()
+              && doesObsoleteItem (split_candidate, installed))
+         {
+           probably_dropped = false;
+         }
+       }
+       addSplitted[installed] = toadd;
+      }
+      // count stats later
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    // now handle dropped package
+    ///////////////////////////////////////////////////////////////////
+
+    if ( probably_dropped ) {
+      if ( opt_stats_r.delete_unmaintained ) {
+       installed.status().setToBeUninstalled(ResStatus::APPL_HIGH);
+      }
+      ++opt_stats_r.chk_dropped;
+      _update_items.push_back ( installed );
+    }
+
+  } // pass 1 end
+
+  ///////////////////////////////////////////////////////////////////
+  // Now check the remembered packages and check non unique provided.
+  // Maybe one of them was somehow selected. Otherwise we have to guess
+  // one.
+  ///////////////////////////////////////////////////////////////////
+  MIL << "doUpgrade pass 2..." << endl;
+
+  // look at the ones with a single provide first
+
+  for ( TodoMap::iterator it = addProvided.begin(); it != addProvided.end(); ++it ) {
+
+    PoolItemSet & tset( it->second );          // these are the providers (well, just one)
+
+    for ( PoolItemSet::iterator sit = tset.begin(); sit != tset.end(); ++sit ) {
+      PoolItem_Ref provider (*sit);
+
+      if (provider.status().setToBeInstalled(ResStatus::APPL_HIGH)) {
+       ++opt_stats_r.chk_replaced;
+      }
+
+      // needs installed
+
+      if ( doesObsoleteItem (provider, it->first ) ) {
+       it->first.status().setToBeUninstalled(ResStatus::APPL_HIGH);
+      }
+    }
+
+  }
+
+  // look at the split providers
+
+  for ( TodoMap::iterator it = addSplitted.begin(); it != addSplitted.end(); ++it ) {
+
+    PoolItemSet & tset( it->second );
+    for ( PoolItemSet::iterator sit = tset.begin(); sit != tset.end(); ++sit ) {
+      if ((*sit).status().setToBeInstalled(ResStatus::APPL_HIGH)) {
+       ++opt_stats_r.chk_add_split;
+      }
+    }
+
+  }
+
+  // look at the ones with multiple providers
+
+  for ( TodoMap::iterator it = addMultiProvided.begin(); it != addMultiProvided.end(); ++it ) {
+    DBG << "GET ONE OUT OF " << it->second.size() << " for " << it->first << endl;
+
+    PoolItem_Ref guess;
+    PoolItemSet & gset( it->second );
+    for ( PoolItemSet::iterator git = gset.begin(); git != gset.end(); ++git ) {
+      PoolItem_Ref item;
+      if ( item.status().isToBeInstalled()) {
+       DBG << " ==> (pass 2: meanwhile set to instaall): " << (*git) << endl;
+       if ( ! doesObsoleteItem (item, it->first ) ) {
+         it->first.status().setToBeUninstalled(ResStatus::APPL_HIGH);
+       }
+       guess = PoolItem_Ref();
+       break;
+      } else {
+       // Be prepared to guess.
+       // Most common situation for guessing is something like:
+       //   qt-devel
+       //   qt-devel-experimental
+       //   qt-devel-japanese
+       // That's why currently the shortest package name wins.
+       if ( !guess || guess->name().size() > item->name().size() ) {
+         guess = item;
+       }
+      }
+    }
+
+    if ( guess ) {
+      guess.status().setToBeInstalled(ResStatus::APPL_HIGH);
+      DBG << " ==> REPLACED by: (pass 2: guessed): " << guess << endl;
+      if ( ! doesObsoleteItem (guess, it->first ) ) {
+       it->first.status().setToBeUninstalled(ResStatus::APPL_HIGH);
+      }
+      ++opt_stats_r.chk_replaced_guessed;
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // done
+  ///////////////////////////////////////////////////////////////////
+  MIL << opt_stats_r << endl;
+}
+
+///////////////////////////////////////////////////////////////////
+    };// namespace detail
+    /////////////////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////
+  };// namespace solver
+  ///////////////////////////////////////////////////////////////////////
+  ///////////////////////////////////////////////////////////////////////
+};// namespace zypp
+/////////////////////////////////////////////////////////////////////////
+
+