tighten up --quiet output for --unmerge
[portage.git] / pym / emerge / __init__.py
index 3ee134c5dc64816dfd722554525673684aed6cc6..9372a1dd3aaa6cbc1b8721a750b2dba75b31a0eb 100644 (file)
@@ -31,7 +31,7 @@ except ImportError:
        import portage
 del os.environ["PORTAGE_LEGACY_GLOBALS"]
 from portage import digraph, portdbapi
-from portage.const import NEWS_LIB_PATH
+from portage.const import NEWS_LIB_PATH, CACHE_PATH, PRIVATE_PATH
 
 import emerge.help
 import portage.xpak, commands, errno, re, socket, time, types
@@ -49,9 +49,8 @@ import portage.locks
 import portage.exception
 from portage.data import secpass
 from portage.util import normalize_path as normpath
+from portage.util import writemsg
 
-if not hasattr(__builtins__, "set"):
-       from sets import Set as set
 from itertools import chain, izip
 from UserDict import DictMixin
 
@@ -207,7 +206,7 @@ def emergelog(xterm_titles, mystr, short_msg=None):
        if xterm_titles:
                if short_msg == None:
                        short_msg = mystr
-               if "HOSTNAME" in os.environ.keys():
+               if "HOSTNAME" in os.environ:
                        short_msg = os.environ["HOSTNAME"]+": "+short_msg
                xtermTitle(short_msg)
        try:
@@ -342,6 +341,7 @@ def create_depgraph_params(myopts, myaction):
        sub=[]
        if "--update" in myopts or \
                "--newuse" in myopts or \
+               "--reinstall" in myopts or \
                "--noreplace" in myopts or \
                myaction in ("system", "world"):
                add.extend(["selective"])
@@ -361,7 +361,7 @@ def create_depgraph_params(myopts, myaction):
        return myparams
 
 # search functionality
-class search:
+class search(object):
 
        #
        # class constants
@@ -398,10 +398,10 @@ class search:
                print "Searching...   ",
 
                regexsearch = False
-               if self.searchkey[0] == '%':
+               if self.searchkey.startswith('%'):
                        regexsearch = True
                        self.searchkey = self.searchkey[1:]
-               if self.searchkey[0] == '@':
+               if self.searchkey.startswith('@'):
                        match_category = 1
                        self.searchkey = self.searchkey[1:]
                if regexsearch:
@@ -440,7 +440,7 @@ class search:
                                if self.searchre.search(full_desc):
                                        self.matches["desc"].append([full_package,masked])
                self.mlen=0
-               for mtype in self.matches.keys():
+               for mtype in self.matches:
                        self.matches[mtype].sort()
                        self.mlen += len(self.matches[mtype])
 
@@ -449,7 +449,7 @@ class search:
                print "\b\b  \n[ Results for search key : "+white(self.searchkey)+" ]"
                print "[ Applications found : "+white(str(self.mlen))+" ]"
                print " "
-               for mtype in self.matches.keys():
+               for mtype in self.matches:
                        for match,masked in self.matches[mtype]:
                                if mtype=="pkg":
                                        catpack=match
@@ -566,8 +566,10 @@ def getlist(settings, mode):
 
 def clean_world(vardb, cpv):
        """Remove a package from the world file when unmerged."""
-       world_filename = os.path.join(vardb.root, portage.WORLD_FILE)
-       worldlist = portage.util.grabfile(world_filename)
+       world_set = WorldSet(vardb.settings)
+       world_set.lock()
+       world_set.load()
+       worldlist = list(world_set)
        mykey = portage.cpv_getkey(cpv)
        newworldlist = []
        for x in worldlist:
@@ -586,19 +588,144 @@ def clean_world(vardb, cpv):
                        #this doesn't match the package we're unmerging; keep it.
                        newworldlist.append(x)
 
-       portage.util.ensure_dirs(os.path.join(vardb.root, portage.PRIVATE_PATH),
-               gid=portage.portage_gid, mode=02770)
-       portage.util.write_atomic(world_filename, "\n".join(newworldlist))
-
-def genericdict(mylist):
-       mynewdict={}
-       for x in mylist:
-               mynewdict[portage.dep_getkey(x)]=x
-       return mynewdict
+       world_set.clear()
+       world_set.update(newworldlist)
+       world_set.save()
+       world_set.unlock()
+
+class AtomSet(object):
+       def __init__(self, atoms=None):
+               self._atoms = {}
+               if atoms:
+                       self.update(atoms)
+       def clear(self):
+               self._atoms.clear()
+       def add(self, atom):
+               cp = portage.dep_getkey(atom)
+               cp_list = self._atoms.get(cp)
+               if cp_list is None:
+                       cp_list = []
+                       self._atoms[cp] = cp_list
+               if atom not in cp_list:
+                       cp_list.append(atom)
+       def update(self, atoms):
+               for atom in atoms:
+                       self.add(atom)
+       def __contains__(self, atom):
+               cp = portage.dep_getkey(atom)
+               if cp in self._atoms and atom in self._atoms[cp]:
+                       return True
+               return False
+       def findAtomForPackage(self, cpv, metadata):
+               """Return the best match for a given package from the arguments, or
+               None if there are no matches.  This matches virtual arguments against
+               the PROVIDE metadata.  This can raise an InvalidDependString exception
+               if an error occurs while parsing PROVIDE."""
+               cpv_slot = "%s:%s" % (cpv, metadata["SLOT"])
+               cp = portage.dep_getkey(cpv)
+               atoms = self._atoms.get(cp)
+               if atoms:
+                       best_match = portage.best_match_to_list(cpv_slot, atoms)
+                       if best_match:
+                               return best_match
+               if not metadata["PROVIDE"]:
+                       return None
+               provides = portage.flatten(portage.dep.use_reduce(
+                       portage.dep.paren_reduce(metadata["PROVIDE"]),
+                       uselist=metadata["USE"].split()))
+               for provide in provides:
+                       provided_cp = portage.dep_getkey(provide)
+                       atoms = self._atoms.get(provided_cp)
+                       if atoms:
+                               transformed_atoms = [atom.replace(provided_cp, cp) for atom in atoms]
+                               best_match = portage.best_match_to_list(cpv_slot, transformed_atoms)
+                               if best_match:
+                                       return atoms[transformed_atoms.index(best_match)]
+               return None
+       def __iter__(self):
+               for atoms in self._atoms.itervalues():
+                       for atom in atoms:
+                               yield atom
+
+class SystemSet(AtomSet):
+       def __init__(self, settings, **kwargs):
+               AtomSet.__init__(self, **kwargs)
+               self.update(getlist(settings, "system"))
+
+class WorldSet(AtomSet):
+       def __init__(self, settings, **kwargs):
+               AtomSet.__init__(self, **kwargs)
+               self.world_file = os.path.join(settings["ROOT"], portage.WORLD_FILE)
+               self._lock = None
+       def _ensure_dirs(self):
+               portage.util.ensure_dirs(os.path.dirname(self.world_file),
+                       gid=portage.portage_gid, mode=02750, mask=02)
+       def load(self):
+               self.clear()
+               self.update(portage.util.grabfile_package(self.world_file))
+       def save(self):
+               self._ensure_dirs()
+               portage.write_atomic(self.world_file,
+                       "\n".join(sorted(self)) + "\n")
+       def lock(self):
+               self._ensure_dirs()
+               self._lock = portage.locks.lockfile(self.world_file, wantnewlockfile=1)
+       def unlock(self):
+               portage.locks.unlockfile(self._lock)
+               self._lock = None
+
+class RootConfig(object):
+       """This is used internally by depgraph to track information about a
+       particular $ROOT."""
+       def __init__(self, trees):
+               self.trees = trees
+               self.settings = trees["vartree"].settings
+               self.root = self.settings["ROOT"]
+               self.sets = {}
+               world_set = WorldSet(self.settings)
+               world_set.load()
+               self.sets["world"] = world_set
+               system_set = SystemSet(self.settings)
+               self.sets["system"] = system_set
+
+def create_world_atom(pkg_key, metadata, args_set, sets, portdb):
+       """Create a new atom for the world file if one does not exist.  If the
+       argument atom is precise enough to identify a specific slot then a slot
+       atom will be returned. Atoms that are in the system set may also be stored
+       in world since system atoms can only match one slot while world atoms can
+       be greedy with respect to slots.  Unslotted system packages will not be
+       not be stored in world."""
+       arg_atom = args_set.findAtomForPackage(pkg_key, metadata)
+       cp = portage.dep_getkey(arg_atom)
+       new_world_atom = cp
+       available_slots = set(portdb.aux_get(cpv, ["SLOT"])[0] \
+               for cpv in portdb.match(cp))
+       slotted = len(available_slots) > 1 or "0" not in available_slots
+       if slotted and arg_atom != cp:
+               # If the user gave a specific atom, store it as a
+               # slot atom in the world file.
+               slot_atom = "%s:%s" % (cp, metadata["SLOT"])
+               # First verify the slot is in the portage tree to avoid
+               # adding a bogus slot like that produced by multislot.
+               if portdb.match(slot_atom):
+                       # Now verify that the argument is precise enough to identify a
+                       # specific slot.
+                       matches = portdb.match(arg_atom)
+                       matched_slots = set()
+                       for cpv in matches:
+                               matched_slots.add(portdb.aux_get(cpv, ["SLOT"])[0])
+                       if len(matched_slots) == 1:
+                               new_world_atom = slot_atom
+       if new_world_atom == sets["world"].findAtomForPackage(pkg_key, metadata):
+               # Both atoms would be identical, so there's nothing to add.
+               return None
+       if not slotted and sets["system"].findAtomForPackage(pkg_key, metadata):
+               return None
+       return new_world_atom
 
 def filter_iuse_defaults(iuse):
        for flag in iuse:
-               if flag.startswith("+"):
+               if flag.startswith("+") or flag.startswith("-"):
                        yield flag[1:]
                else:
                        yield flag
@@ -626,13 +753,15 @@ class DepPriority(object):
                levels:
 
                MEDIUM   The upper boundary for medium dependencies.
+               MEDIUM_SOFT   The upper boundary for medium-soft dependencies.
                SOFT     The upper boundary for soft dependencies.
                MIN      The lower boundary for soft dependencies.
        """
-       __slots__ = ("__weakref__", "satisfied", "buildtime", "runtime")
+       __slots__ = ("__weakref__", "satisfied", "buildtime", "runtime", "runtime_post")
        MEDIUM = -1
-       SOFT   = -2
-       MIN    = -4
+       MEDIUM_SOFT = -2
+       SOFT   = -3
+       MIN    = -6
        def __init__(self, **kwargs):
                for myattr in self.__slots__:
                        if myattr == "__weakref__":
@@ -645,11 +774,15 @@ class DepPriority(object):
                                return 0
                        if self.runtime:
                                return -1
+                       if self.runtime_post:
+                               return -2
                if self.buildtime:
-                       return -2
-               if self.runtime:
                        return -3
-               return -4
+               if self.runtime:
+                       return -4
+               if self.runtime_post:
+                       return -5
+               return -6
        def __lt__(self, other):
                return self.__int__() < other
        def __le__(self, other):
@@ -669,8 +802,10 @@ class DepPriority(object):
                myvalue = self.__int__()
                if myvalue > self.MEDIUM:
                        return "hard"
-               if myvalue > self.SOFT:
+               if myvalue > self.MEDIUM_SOFT:
                        return "medium"
+               if myvalue > self.SOFT:
+                       return "medium-soft"
                return "soft"
 
 class FakeVartree(portage.vartree):
@@ -698,7 +833,7 @@ class FakeVartree(portage.vartree):
                        if os.access(vdb_path, os.W_OK):
                                vdb_lock = portage.locks.lockdir(vdb_path)
                        mykeys = ["SLOT", "COUNTER", "PROVIDE", "USE", "IUSE",
-                               "DEPEND", "RDEPEND", "PDEPEND"]
+                               "RESTRICT", "DEPEND", "RDEPEND", "PDEPEND", "repository"]
                        real_dbapi = real_vartree.dbapi
                        slot_counters = {}
                        for cpv in real_dbapi.cpv_all():
@@ -874,6 +1009,13 @@ class BlockerCache(DictMixin):
                """
                return self.BlockerData(*self._cache_data["blockers"][cpv])
 
+       def keys(self):
+               """This needs to be implemented so that self.__repr__() doesn't raise
+               an AttributeError."""
+               if self._cache_data and "blockers" in self._cache_data:
+                       return self._cache_data["blockers"].keys()
+               return []
+
 def show_invalid_depstring_notice(parent_node, depstring, error_msg):
 
        from formatter import AbstractFormatter, DumbWriter
@@ -909,7 +1051,7 @@ def show_invalid_depstring_notice(parent_node, depstring, error_msg):
                f.add_flowing_data(x)
        f.end_paragraph(1)
 
-class depgraph:
+class depgraph(object):
 
        pkg_tree_map = {
                "ebuild":"porttree",
@@ -931,9 +1073,11 @@ class depgraph:
                # Maps slot atom to digraph node for all nodes added to the graph.
                self._slot_node_map = {}
                self.mydbapi = {}
-               self._mydbapi_keys = ["SLOT", "DEPEND", "RDEPEND", "PDEPEND"]
+               self._mydbapi_keys = ["SLOT", "DEPEND", "RDEPEND", "PDEPEND",
+                       "USE", "IUSE", "PROVIDE", "RESTRICT", "repository"]
                self.useFlags = {}
                self.trees = {}
+               self.roots = {}
                for myroot in trees:
                        self.trees[myroot] = {}
                        for tree in ("porttree", "bintree"):
@@ -946,6 +1090,7 @@ class depgraph:
                        self.pkg_node_map[myroot] = {}
                        self._slot_node_map[myroot] = {}
                        vardb = self.trees[myroot]["vartree"].dbapi
+                       self.roots[myroot] = RootConfig(self.trees[myroot])
                        # This fakedbapi instance will model the state that the vdb will
                        # have after new packages have been installed.
                        fakedb = portage.fakedbapi(settings=self.pkgsettings[myroot])
@@ -974,9 +1119,18 @@ class depgraph:
                self._parent_child_digraph = digraph()
                self.orderedkeys=[]
                self.outdatedpackages=[]
-               self.args_keys = []
+               # contains all sets added to the graph
+               self._sets = {}
+               # contains atoms given as arguments
+               self._sets["args"] = AtomSet()
+               # contains all atoms from all sets added to the graph, including
+               # atoms given as arguments
+               self._set_atoms = AtomSet()
+               # contains all nodes pulled in by self._set_atoms
+               self._set_nodes = set()
                self.blocker_digraph = digraph()
                self.blocker_parents = {}
+               self._unresolved_blocker_parents = {}
                self._slot_collision_info = []
                # Slot collision nodes are not allowed to block other packages since
                # blocker validation is only able to account for one package per slot.
@@ -1037,6 +1191,21 @@ class depgraph:
                f.end_paragraph(1)
                f.writer.flush()
 
+       def _reinstall_for_flags(self, forced_flags,
+               orig_use, orig_iuse, cur_use, cur_iuse):
+               if "--newuse" in self.myopts:
+                       if orig_iuse.symmetric_difference(
+                               cur_iuse).difference(forced_flags):
+                               return True
+                       elif orig_iuse.intersection(orig_use) != \
+                               cur_iuse.intersection(cur_use):
+                               return True
+               elif "changed-use" == self.myopts.get("--reinstall"):
+                       if orig_iuse.intersection(orig_use) != \
+                               cur_iuse.intersection(cur_use):
+                               return True
+               return False
+
        def create(self, mybigkey, myparent=None, addme=1, myuse=None,
                priority=DepPriority(), rev_dep=False, arg=None):
                """
@@ -1078,8 +1247,7 @@ class depgraph:
                                self.blocker_parents.setdefault(
                                        tuple(mybigkey), set()).add(myparent)
                        return 1
-               if not arg and myroot == self.target_root:
-                       arg = portage.best_match_to_list(mykey, self.args_keys)
+
                # select the correct /var database that we'll be checking against
                vardbapi = self.trees[myroot]["vartree"].dbapi
                portdb = self.trees[myroot]["porttree"].dbapi
@@ -1090,10 +1258,22 @@ class depgraph:
                # directive, otherwise we add a "merge" directive.
 
                mydbapi = self.trees[myroot][self.pkg_tree_map[mytype]].dbapi
+               metadata = dict(izip(self._mydbapi_keys,
+                       mydbapi.aux_get(mykey, self._mydbapi_keys)))
+               if mytype == "ebuild":
+                       pkgsettings.setcpv(mykey, mydb=portdb)
+                       metadata["USE"] = pkgsettings["USE"]
+                       myuse = pkgsettings["USE"].split()
 
-               if myuse is None:
-                       self.pkgsettings[myroot].setcpv(mykey, mydb=portdb)
-                       myuse = self.pkgsettings[myroot]["USE"].split()
+               if not arg and myroot == self.target_root:
+                       try:
+                               arg = self._set_atoms.findAtomForPackage(mykey, metadata)
+                       except portage.exception.InvalidDependString, e:
+                               if mytype != "installed":
+                                       show_invalid_depstring_notice(tuple(mybigkey+["merge"]),
+                                               metadata["PROVIDE"], str(e))
+                                       return 0
+                               del e
 
                merging=1
                if mytype == "installed":
@@ -1107,23 +1287,21 @@ class depgraph:
                        """ If we aren't merging, perform the --newuse check.
                            If the package has new iuse flags or different use flags then if
                            --newuse is specified, we need to merge the package. """
-                       if merging==0 and "--newuse" in self.myopts and \
+                       if merging == 0 and \
+                               ("--newuse" in self.myopts or
+                               "--reinstall" in self.myopts) and \
                                vardbapi.cpv_exists(mykey):
                                pkgsettings.setcpv(mykey, mydb=mydbapi)
                                forced_flags = set()
                                forced_flags.update(pkgsettings.useforce)
                                forced_flags.update(pkgsettings.usemask)
                                old_use = vardbapi.aux_get(mykey, ["USE"])[0].split()
-                               iuses = set(filter_iuse_defaults(
-                                       mydbapi.aux_get(mykey, ["IUSE"])[0].split()))
+                               iuses = set(filter_iuse_defaults(metadata["IUSE"].split()))
                                old_iuse = set(filter_iuse_defaults(
                                        vardbapi.aux_get(mykey, ["IUSE"])[0].split()))
-                               if iuses.symmetric_difference(
-                                       old_iuse).difference(forced_flags):
+                               if self._reinstall_for_flags(
+                                       forced_flags, old_use, old_iuse, myuse, iuses):
                                        merging = 1
-                               elif old_iuse.intersection(old_use) != \
-                                       iuses.intersection(myuse):
-                                       merging=1
 
                if addme and merging == 1:
                        mybigkey.append("merge")
@@ -1132,16 +1310,15 @@ class depgraph:
                jbigkey = tuple(mybigkey)
 
                if addme:
-                       metadata = dict(izip(self._mydbapi_keys,
-                               mydbapi.aux_get(mykey, self._mydbapi_keys)))
                        if merging == 0 and vardbapi.cpv_exists(mykey) and \
                                mytype != "installed":
+                               mytype = "installed"
                                mybigkey[0] = "installed"
                                mydbapi = vardbapi
                                jbigkey = tuple(mybigkey)
                                metadata = dict(izip(self._mydbapi_keys,
                                        mydbapi.aux_get(mykey, self._mydbapi_keys)))
-                               myuse = mydbapi.aux_get(mykey, ["USE"])[0].split()
+                               myuse = metadata["USE"].split()
                        slot_atom = "%s:%s" % (portage.dep_getkey(mykey), metadata["SLOT"])
                        existing_node = self._slot_node_map[myroot].get(
                                slot_atom, None)
@@ -1199,6 +1376,9 @@ class depgraph:
                                self.digraph.addnode(jbigkey, myparent,
                                        priority=priority)
 
+               if arg:
+                       self._set_nodes.add(jbigkey)
+
                # Do this even when addme is False (--onlydeps) so that the
                # parent/child relationship is always known in case
                # self._show_slot_collision_notice() needs to be called later.
@@ -1222,9 +1402,8 @@ class depgraph:
 
                edepend={}
                depkeys = ["DEPEND","RDEPEND","PDEPEND"]
-               depvalues = mydbapi.aux_get(mykey, depkeys)
-               for i in xrange(len(depkeys)):
-                       edepend[depkeys[i]] = depvalues[i]
+               for k in depkeys:
+                       edepend[k] = metadata[k]
 
                if mytype == "ebuild":
                        if "--buildpkgonly" in self.myopts:
@@ -1259,7 +1438,7 @@ class depgraph:
                                # Post Depend -- Add to the list without a parent, as it depends
                                # on a package being present AND must be built after that package.
                                if not self.select_dep(myroot, edepend["PDEPEND"], myparent=mp,
-                                       myuse=myuse, priority=DepPriority(), rev_deps=True,
+                                       myuse=myuse, priority=DepPriority(runtime_post=True),
                                        parent_arg=arg):
                                        return 0
                except ValueError, e:
@@ -1307,26 +1486,38 @@ class depgraph:
                                        else:
                                                print "\n\n!!! Binary package '"+str(x)+"' does not exist."
                                                print "!!! Please ensure the tbz2 exists as specified.\n"
-                                               sys.exit(1)
+                                               return 0, myfavorites
                                mytbz2=portage.xpak.tbz2(x)
                                mykey=mytbz2.getelements("CATEGORY")[0]+"/"+os.path.splitext(os.path.basename(x))[0]
                                if os.path.realpath(x) != \
                                        os.path.realpath(self.trees[myroot]["bintree"].getname(mykey)):
                                        print colorize("BAD", "\n*** You need to adjust PKGDIR to emerge this package.\n")
-                                       sys.exit(1)
+                                       return 0, myfavorites
                                if not self.create(["binary", myroot, mykey],
-                                       None, "--onlydeps" not in self.myopts):
+                                       None, "--onlydeps" not in self.myopts,
+                                       myuse=mytbz2.getelements("USE"), arg=x):
                                        return (0,myfavorites)
-                               elif not "--oneshot" in self.myopts:
-                                       myfavorites.append(mykey)
+                               arg_atoms.append((x, "="+mykey))
                        elif ext==".ebuild":
-                               x = os.path.realpath(x)
-                               mykey=os.path.basename(os.path.normpath(x+"/../.."))+"/"+os.path.splitext(os.path.basename(x))[0]
+                               ebuild_path = portage.util.normalize_path(os.path.abspath(x))
+                               pkgdir = os.path.dirname(ebuild_path)
+                               tree_root = os.path.dirname(os.path.dirname(pkgdir))
+                               cp = pkgdir[len(tree_root)+1:]
+                               e = portage.exception.PackageNotFound(
+                                       ("%s is not in a valid portage tree " + \
+                                       "hierarchy or does not exist") % x)
+                               if not portage.isvalidatom(cp):
+                                       raise e
+                               cat = portage.catsplit(cp)[0]
+                               mykey = cat + "/" + os.path.basename(ebuild_path[:-7])
+                               if not portage.isvalidatom("="+mykey):
+                                       raise e
                                ebuild_path = portdb.findname(mykey)
                                if ebuild_path:
-                                       if os.path.realpath(ebuild_path) != x:
+                                       if ebuild_path != os.path.join(os.path.realpath(tree_root),
+                                               cp, os.path.basename(ebuild_path)):
                                                print colorize("BAD", "\n*** You need to adjust PORTDIR or PORTDIR_OVERLAY to emerge this package.\n")
-                                               sys.exit(1)
+                                               return 0, myfavorites
                                        if mykey not in portdb.xmatch(
                                                "match-visible", portage.dep_getkey(mykey)):
                                                print colorize("BAD", "\n*** You are emerging a masked package. It is MUCH better to use")
@@ -1338,10 +1529,9 @@ class depgraph:
                                        raise portage.exception.PackageNotFound(
                                                "%s is not in a valid portage tree hierarchy or does not exist" % x)
                                if not self.create(["ebuild", myroot, mykey],
-                                       None, "--onlydeps" not in self.myopts):
+                                       None, "--onlydeps" not in self.myopts, arg=x):
                                        return (0,myfavorites)
-                               elif not "--oneshot" in self.myopts:
-                                       myfavorites.append(mykey)
+                               arg_atoms.append((x, "="+mykey))
                        else:
                                if not is_valid_package_atom(x):
                                        portage.writemsg("\n\n!!! '%s' is not a valid package atom.\n" % x,
@@ -1419,9 +1609,17 @@ class depgraph:
                                                        greedy_atoms.append((myarg, myslot_atom))
                        arg_atoms = greedy_atoms
 
+               oneshot = "--oneshot" in self.myopts or "--onlydeps" in self.myopts
                """ These are used inside self.create() in order to ensure packages
                that happen to match arguments are not incorrectly marked as nomerge."""
-               self.args_keys = [x[1] for x in arg_atoms]
+               args_set = self._sets["args"]
+               for myarg, myatom in arg_atoms:
+                       if myatom in args_set:
+                               continue
+                       args_set.add(myatom)
+                       self._set_atoms.add(myatom)
+                       if not oneshot:
+                               myfavorites.append(myatom)
                for myarg, myatom in arg_atoms:
                                try:
                                        self.mysd = self.select_dep(myroot, myatom, arg=myarg)
@@ -1448,10 +1646,6 @@ class depgraph:
 
                                if not self.mysd:
                                        return (0,myfavorites)
-                               elif not "--oneshot" in self.myopts:
-                                       mykey = portage.dep_getkey(myatom)
-                                       if mykey not in myfavorites:
-                                               myfavorites.append(mykey)
 
                missing=0
                if "--usepkgonly" in self.myopts:
@@ -1525,11 +1719,11 @@ class depgraph:
                                return 0
                        mymerge = mycheck[1]
 
-               if not mymerge and arg and \
-                       portage.best_match_to_list(depstring, self.args_keys):
+               if not mymerge and arg:
                        # A provided package has been specified on the command line.  The
                        # package will not be merged and a warning will be displayed.
-                       self._pprovided_args.append(arg)
+                       if depstring in self._set_atoms:
+                               self._pprovided_args.append((arg, depstring))
 
                if myparent:
                        # The parent is added after it's own dep_check call so that it
@@ -1589,7 +1783,9 @@ class depgraph:
                                        if myeb_pkg_matches:
                                                myeb_pkg = portage.best(myeb_pkg_matches)
 
-                               if myeb_pkg and "--newuse" in self.myopts:
+                               if myeb_pkg and \
+                                       ("--newuse" in self.myopts or \
+                                       "--reinstall" in self.myopts):
                                        iuses = set(filter_iuse_defaults(
                                                bindb.aux_get(myeb_pkg, ["IUSE"])[0].split()))
                                        old_use = bindb.aux_get(myeb_pkg, ["USE"])[0].split()
@@ -1608,11 +1804,8 @@ class depgraph:
                                        if "--usepkgonly" not in self.myopts and myeb:
                                                cur_iuse = set(filter_iuse_defaults(
                                                        portdb.aux_get(myeb, ["IUSE"])[0].split()))
-                                       if iuses.symmetric_difference(
-                                               cur_iuse).difference(forced_flags):
-                                               myeb_pkg = None
-                                       elif iuses.intersection(old_use) != \
-                                               cur_iuse.intersection(now_use):
+                                       if self._reinstall_for_flags(
+                                               forced_flags, old_use, iuses, now_use, cur_iuse):
                                                myeb_pkg = None
                                if myeb_pkg:
                                        binpkguseflags = \
@@ -1903,7 +2096,6 @@ class depgraph:
                                blocked_slots_final[cpv] = \
                                        "%s:%s" % (portage.dep_getkey(cpv),
                                                final_db.aux_get(cpv, ["SLOT"])[0])
-                       blocked_slots_final_values = set(blocked_slots_final.itervalues())
                        for parent in list(self.blocker_parents[blocker]):
                                ptype, proot, pcpv, pstatus = parent
                                pdbapi = self.trees[proot][self.pkg_tree_map[ptype]].dbapi
@@ -1915,10 +2107,10 @@ class depgraph:
                                for cpv in blocked_initial:
                                        slot_atom = blocked_slots_initial[cpv]
                                        if slot_atom == pslot_atom:
-                                               # The parent blocks an initial package in the same
-                                               # slot as itself.  The merge/nomerge status of neither
-                                               # node matters.  In any case, this particular block is
-                                               # automatically resolved.
+                                               # TODO: Support blocks within slots in cases where it
+                                               # might make sense.  For example, a new version might
+                                               # require that the old version be uninstalled at build
+                                               # time.
                                                continue
                                        if parent_static and \
                                                slot_atom not in modified_slots[myroot]:
@@ -1926,23 +2118,23 @@ class depgraph:
                                                # merge of either package is triggered.
                                                continue
                                        if pstatus == "merge" and \
-                                               slot_atom not in blocked_slots_final_values:
-                                               upgrade_matches = final_db.match(slot_atom)
-                                               if upgrade_matches:
-                                                       # Apparently an upgrade may be able to invalidate
-                                                       # this block.
-                                                       upgrade_node = \
-                                                               self.pkg_node_map[proot][upgrade_matches[0]]
-                                                       depends_on_order.add((upgrade_node, parent))
-                                                       continue
+                                               slot_atom in modified_slots[myroot]:
+                                               replacement = final_db.match(slot_atom)
+                                               if replacement:
+                                                       if not portage.match_from_list(mydep, replacement):
+                                                               # Apparently a replacement may be able to
+                                                               # invalidate this block.
+                                                               replacement_node = \
+                                                                       self.pkg_node_map[proot][replacement[0]]
+                                                               depends_on_order.add((replacement_node, parent))
+                                                               continue
                                        # None of the above blocker resolutions techniques apply,
                                        # so apparently this one is unresolvable.
                                        unresolved_blocks = True
                                for cpv in blocked_final:
                                        slot_atom = blocked_slots_final[cpv]
                                        if slot_atom == pslot_atom:
-                                               # The parent blocks itself, so the merge order does not
-                                               # need to be enforced.
+                                               # TODO: Support blocks within slots.
                                                continue
                                        if parent_static and \
                                                slot_atom not in modified_slots[myroot]:
@@ -1977,6 +2169,9 @@ class depgraph:
                                                self.blocker_digraph.addnode(node, blocker)
                                if not unresolved_blocks and not depends_on_order:
                                        self.blocker_parents[blocker].remove(parent)
+                               if unresolved_blocks:
+                                       self._unresolved_blocker_parents.setdefault(
+                                               blocker, set()).add(parent)
                        if not self.blocker_parents[blocker]:
                                del self.blocker_parents[blocker]
                # Validate blockers that depend on merge order.
@@ -2020,6 +2215,9 @@ class depgraph:
                        self._altlist_cache[reversed] = retlist[:]
                        return retlist
                mygraph=self.digraph.copy()
+               for node in mygraph.order[:]:
+                       if node[-1] == "nomerge":
+                               mygraph.remove(node)
                self._merge_order_bias(mygraph)
                myblockers = self.blocker_digraph.copy()
                retlist=[]
@@ -2034,32 +2232,34 @@ class depgraph:
                                if "portage" == portage.catsplit(portage.dep_getkey(cpv))[-1]:
                                        asap_nodes.append(node)
                                        break
-               ignore_priority_range = [None]
-               ignore_priority_range.extend(
-                       xrange(DepPriority.MIN, DepPriority.MEDIUM + 1))
+               ignore_priority_soft_range = [None]
+               ignore_priority_soft_range.extend(
+                       xrange(DepPriority.MIN, DepPriority.SOFT + 1))
                tree_mode = "--tree" in self.myopts
+               # Tracks whether or not the current iteration should prefer asap_nodes
+               # if available.  This is set to False when the previous iteration
+               # failed to select any nodes.  It is reset whenever nodes are
+               # successfully selected.
+               prefer_asap = True
                while not mygraph.empty():
-                       ignore_priority = None
-                       nodes = None
-                       if asap_nodes:
+                       selected_nodes = None
+                       if prefer_asap and asap_nodes:
                                """ASAP nodes are merged before their soft deps."""
+                               asap_nodes = [node for node in asap_nodes \
+                                       if mygraph.contains(node)]
                                for node in asap_nodes:
-                                       if not mygraph.contains(node):
-                                               asap_nodes.remove(node)
-                                               continue
                                        if not mygraph.child_nodes(node,
                                                ignore_priority=DepPriority.SOFT):
-                                               nodes = [node]
+                                               selected_nodes = [node]
                                                asap_nodes.remove(node)
                                                break
-                       if not nodes:
-                               for ignore_priority in ignore_priority_range:
+                       if not selected_nodes and \
+                               not (prefer_asap and asap_nodes):
+                               for ignore_priority in ignore_priority_soft_range:
                                        nodes = get_nodes(ignore_priority=ignore_priority)
                                        if nodes:
                                                break
-                       selected_nodes = None
-                       if nodes:
-                               if ignore_priority <= DepPriority.SOFT:
+                               if nodes:
                                        if ignore_priority is None and not tree_mode:
                                                # Greedily pop all of these nodes since no relationship
                                                # has been ignored.  This optimization destroys --tree
@@ -2078,30 +2278,65 @@ class depgraph:
                                                if not selected_nodes:
                                                        # settle for a root node
                                                        selected_nodes = [nodes[0]]
-                               else:
+                       if not selected_nodes:
+                               nodes = get_nodes(ignore_priority=DepPriority.MEDIUM)
+                               if nodes:
                                        """Recursively gather a group of nodes that RDEPEND on
                                        eachother.  This ensures that they are merged as a group
                                        and get their RDEPENDs satisfied as soon as possible."""
-                                       def gather_deps(mergeable_nodes, selected_nodes, node):
+                                       def gather_deps(ignore_priority,
+                                               mergeable_nodes, selected_nodes, node):
                                                if node in selected_nodes:
                                                        return True
                                                if node not in mergeable_nodes:
                                                        return False
                                                selected_nodes.add(node)
                                                for child in mygraph.child_nodes(node,
-                                                       ignore_priority=DepPriority.SOFT):
-                                                       if not gather_deps(
+                                                       ignore_priority=ignore_priority):
+                                                       if not gather_deps(ignore_priority,
                                                                mergeable_nodes, selected_nodes, child):
                                                                return False
                                                return True
                                        mergeable_nodes = set(nodes)
-                                       for node in nodes:
-                                               selected_nodes = set()
-                                               if gather_deps(
-                                                       mergeable_nodes, selected_nodes, node):
+                                       if prefer_asap and asap_nodes:
+                                               nodes = asap_nodes
+                                       for ignore_priority in xrange(DepPriority.SOFT,
+                                               DepPriority.MEDIUM_SOFT + 1):
+                                               for node in nodes:
+                                                       selected_nodes = set()
+                                                       if gather_deps(ignore_priority,
+                                                               mergeable_nodes, selected_nodes, node):
+                                                               break
+                                                       else:
+                                                               selected_nodes = None
+                                               if selected_nodes:
                                                        break
-                                               else:
-                                                       selected_nodes = None
+
+                                       if prefer_asap and asap_nodes and not selected_nodes:
+                                               # We failed to find any asap nodes to merge, so ignore
+                                               # them for the next iteration.
+                                               prefer_asap = False
+                                               continue
+
+                                       if selected_nodes and ignore_priority > DepPriority.SOFT:
+                                               # Try to merge ignored medium deps as soon as possible.
+                                               for node in selected_nodes:
+                                                       children = set(mygraph.child_nodes(node))
+                                                       soft = children.difference(
+                                                               mygraph.child_nodes(node,
+                                                               ignore_priority=DepPriority.SOFT))
+                                                       medium_soft = children.difference(
+                                                               mygraph.child_nodes(node,
+                                                               ignore_priority=DepPriority.MEDIUM_SOFT))
+                                                       medium_soft.difference_update(soft)
+                                                       for child in medium_soft:
+                                                               if child in selected_nodes:
+                                                                       continue
+                                                               if child in asap_nodes:
+                                                                       continue
+                                                               # TODO: Try harder to make these nodes get
+                                                               # merged absolutely as soon as possible.
+                                                               asap_nodes.append(child)
 
                        if not selected_nodes:
                                if not myblockers.is_empty():
@@ -2113,8 +2348,16 @@ class depgraph:
                                        if not circular_blocks:
                                                circular_blocks = True
                                                blocker_deps = myblockers.leaf_nodes()
-                                       if blocker_deps:
-                                               selected_nodes = [blocker_deps.pop()]
+                                       while blocker_deps:
+                                               # Some of these nodes might have already been selected
+                                               # by the normal node selection process after the
+                                               # circular_blocks flag has been set.  Therefore, we
+                                               # have to verify that they're still in the graph so
+                                               # that they're not selected more than once.
+                                               node = blocker_deps.pop()
+                                               if mygraph.contains(node):
+                                                       selected_nodes = [node]
+                                                       break
 
                        if not selected_nodes:
                                # No leaf nodes are available, so we have a circular
@@ -2154,6 +2397,10 @@ class depgraph:
                                print "!!! disabling USE flags that trigger optional dependencies."
                                sys.exit(1)
 
+                       # At this point, we've succeeded in selecting one or more nodes, so
+                       # it's now safe to reset the prefer_asap to it's default state.
+                       prefer_asap = True
+
                        for node in selected_nodes:
                                retlist.append(list(node))
                                mygraph.remove(node)
@@ -2163,7 +2410,12 @@ class depgraph:
                                        for blocker in myblockers.root_nodes():
                                                if not myblockers.child_nodes(blocker):
                                                        myblockers.remove(blocker)
-                                                       del self.blocker_parents[blocker]
+                                                       unresolved = \
+                                                               self._unresolved_blocker_parents.get(blocker)
+                                                       if unresolved:
+                                                               self.blocker_parents[blocker] = unresolved
+                                                       else:
+                                                               del self.blocker_parents[blocker]
 
                if not reversed:
                        """Blocker validation does not work with reverse mode,
@@ -2186,30 +2438,45 @@ class depgraph:
                        return [x for x in mylist \
                                if x in matches or not portdb.cpv_exists(x)]
                world_problems = False
-               if mode=="system":
-                       mylist = getlist(self.settings, "system")
-               else:
-                       #world mode
-                       worldlist = getlist(self.settings, "world")
-                       mylist = getlist(self.settings, "system")
-                       worlddict=genericdict(worldlist)
 
-                       for x in worlddict.keys():
+               root_config = self.roots[self.target_root]
+               world_set = root_config.sets["world"]
+               system_set = root_config.sets["system"]
+               mylist = list(system_set)
+               self._sets["system"] = system_set
+               if mode == "world":
+                       self._sets["world"] = world_set
+                       for x in world_set:
                                if not portage.isvalidatom(x):
                                        world_problems = True
-                               elif not self.trees[self.target_root]["vartree"].dbapi.match(x):
+                                       continue
+                               elif not vardb.match(x):
                                        world_problems = True
-                               else:
-                                       mylist.append(x)
+                                       available = False
+                                       if "--usepkgonly" not in self.myopts and \
+                                               portdb.match(x):
+                                               available = True
+                                       elif "--usepkg" in self.myopts:
+                                               mymatches = bindb.match(x)
+                                               if "--usepkgonly" not in self.myopts:
+                                                       mymatches = visible(mymatches)
+                                               if mymatches:
+                                                       available = True
+                                       if not available:
+                                               continue
+                               mylist.append(x)
 
                newlist = []
                for atom in mylist:
                        mykey = portage.dep_getkey(atom)
                        if True:
                                newlist.append(atom)
-                               """Make sure all installed slots are updated when possible.
-                               Do this with --emptytree also, to ensure that all slots are
-                               remerged."""
+                               if mode == "system" or atom not in world_set:
+                                       # only world is greedy for slots, not system
+                                       continue
+                               # Make sure all installed slots are updated when possible.
+                               # Do this with --emptytree also, to ensure that all slots are
+                               # remerged.
                                myslots = set()
                                for cpv in vardb.match(mykey):
                                        myslots.add(vardb.aux_get(cpv, ["SLOT"])[0])
@@ -2250,7 +2517,10 @@ class depgraph:
                                                if available:
                                                        newlist.append(myslot_atom)
                mylist = newlist
-               
+
+               for myatom in mylist:
+                       self._set_atoms.add(myatom)
+
                missing_atoms = []
                for mydep in mylist:
                        try:
@@ -2276,10 +2546,14 @@ class depgraph:
 
                return 1
 
-       def display(self,mylist,verbosity=None):
+       def display(self, mylist, favorites=[], verbosity=None):
                if verbosity is None:
                        verbosity = ("--quiet" in self.myopts and 1 or \
                                "--verbose" in self.myopts and 3 or 2)
+               if "--resume" in self.myopts and favorites:
+                       self._sets["args"].update(favorites)
+               favorites_set = AtomSet()
+               favorites_set.update(favorites)
                changelogs=[]
                p=[]
                blockers = []
@@ -2363,6 +2637,7 @@ class depgraph:
                mygraph = self._parent_child_digraph
                i = 0
                depth = 0
+               shown_edges = set()
                for x in mylist:
                        if "blocks" == x[0]:
                                display_list.append((x, 0, True))
@@ -2377,11 +2652,17 @@ class depgraph:
                                        tree_nodes = tree_nodes[:depth]
                                        tree_nodes.append(graph_key)
                                        display_list.append((x, depth, True))
+                                       shown_edges.add((graph_key, tree_nodes[depth-1]))
                                else:
                                        traversed_nodes = set() # prevent endless circles
                                        traversed_nodes.add(graph_key)
                                        def add_parents(current_node, ordered):
-                                               parent_nodes = mygraph.parent_nodes(current_node)
+                                               parent_nodes = None
+                                               # Do not traverse to parents if this node is an
+                                               # an argument or a direct member of a set that has
+                                               # been specified as an argument (system or world).
+                                               if current_node not in self._set_nodes:
+                                                       parent_nodes = mygraph.parent_nodes(current_node)
                                                if parent_nodes:
                                                        child_nodes = set(mygraph.child_nodes(current_node))
                                                        selected_parent = None
@@ -2389,15 +2670,22 @@ class depgraph:
                                                        for node in parent_nodes:
                                                                if node not in traversed_nodes and \
                                                                        node not in child_nodes:
+                                                                       edge = (current_node, node)
+                                                                       if edge in shown_edges:
+                                                                               continue
                                                                        selected_parent = node
                                                                        break
                                                        if not selected_parent:
                                                                # A direct cycle is unavoidable.
                                                                for node in parent_nodes:
                                                                        if node not in traversed_nodes:
+                                                                               edge = (current_node, node)
+                                                                               if edge in shown_edges:
+                                                                                       continue
                                                                                selected_parent = node
                                                                                break
                                                        if selected_parent:
+                                                               shown_edges.add((current_node, selected_parent))
                                                                traversed_nodes.add(selected_parent)
                                                                add_parents(selected_parent, False)
                                                display_list.append((list(current_node),
@@ -2429,10 +2717,13 @@ class depgraph:
                                depth >= mylist[i+1][1]:
                                        del mylist[i]
 
+               from portage import flatten
+               from portage.dep import use_reduce, paren_reduce
                display_overlays=False
                # files to fetch list - avoids counting a same file twice
                # in size display (verbose mode)
                myfetchlist=[]
+
                for mylist_index in xrange(len(mylist)):
                        x, depth, ordered = mylist[mylist_index]
                        pkg_type = x[0]
@@ -2467,6 +2758,8 @@ class depgraph:
                                blockers.append(addl)
                        else:
                                mydbapi = self.trees[myroot][self.pkg_tree_map[pkg_type]].dbapi
+                               pkg_status = x[3]
+                               pkg_merge = ordered and pkg_status != "nomerge"
                                binary_package = True
                                if "ebuild" == pkg_type:
                                        if "merge" == x[3] or \
@@ -2481,15 +2774,38 @@ class depgraph:
                                        else:
                                                # An ebuild "nomerge" node, so USE come from the vardb.
                                                mydbapi = vartree.dbapi
+                               # reuse cached metadata from when the depgraph was built
+                               if "--resume" in self.myopts:
+                                       # Populate the fakedb with relevant metadata, just like
+                                       # would have happened when the depgraph was originally
+                                       # built.
+                                       metadata = dict(izip(self._mydbapi_keys,
+                                               mydbapi.aux_get(pkg_key, self._mydbapi_keys)))
+                                       self.mydbapi[myroot].cpv_inject(pkg_key, metadata=metadata)
+                               else:
+                                       metadata = dict(izip(self._mydbapi_keys,
+                                               self.mydbapi[myroot].aux_get(
+                                               pkg_key, self._mydbapi_keys)))
+                               mydbapi = self.mydbapi[myroot] # use the cached metadata
                                if pkg_key not in self.useFlags[myroot]:
                                        """If this is a --resume then the USE flags need to be
                                        fetched from the appropriate locations here."""
                                        self.useFlags[myroot][pkg_key] = mydbapi.aux_get(
                                                pkg_key, ["USE"])[0].split()
 
+                               try:
+                                       restrict = flatten(use_reduce(paren_reduce(
+                                               mydbapi.aux_get(pkg_key, ["RESTRICT"])[0]),
+                                               uselist=self.useFlags[myroot][pkg_key]))
+                               except portage.exception.InvalidDependString, e:
+                                       if pkg_status != "nomerge":
+                                               restrict = mydbapi.aux_get(pkg_key, ["RESTRICT"])[0]
+                                               show_invalid_depstring_notice(x, restrict, str(e))
+                                               del e
+                                               return 1
+                                       restrict = []
                                if "ebuild" == pkg_type and x[3] != "nomerge" and \
-                                       "fetch" in portdb.aux_get(
-                                       x[2], ["RESTRICT"])[0].split():
+                                       "fetch" in restrict:
                                        fetch = red("F")
                                        if ordered:
                                                counters.restrict_fetch += 1
@@ -2630,7 +2946,7 @@ class depgraph:
                                if verbosity == 3:
                                        # size verbose
                                        mysize=0
-                                       if x[0] == "ebuild" and ordered and x[-1] != "nomerge":
+                                       if pkg_type == "ebuild" and pkg_merge:
                                                try:
                                                        myfilesdict = portdb.getfetchsizes(pkg_key,
                                                                useflags=self.useFlags[myroot][pkg_key],
@@ -2639,11 +2955,11 @@ class depgraph:
                                                        src_uri = portdb.aux_get(pkg_key, ["SRC_URI"])[0]
                                                        show_invalid_depstring_notice(x, src_uri, str(e))
                                                        del e
-                                                       sys.exit(1)
+                                                       return 1
                                                if myfilesdict is None:
                                                        myfilesdict="[empty/missing/bad digest]"
                                                else:
-                                                       for myfetchfile in myfilesdict.keys():
+                                                       for myfetchfile in myfilesdict:
                                                                if myfetchfile not in myfetchlist:
                                                                        mysize+=myfilesdict[myfetchfile]
                                                                        myfetchlist.append(myfetchfile)
@@ -2655,15 +2971,39 @@ class depgraph:
                                        # x = ['binary', '/', 'sys-apps/pcmcia-cs-3.2.7.2.6', 'merge']
                                        file_name = portdb.findname(pkg_key)
                                        if file_name: # It might not exist in the tree
+                                               newrepo = pkgsettings["PORTDIR"]
+                                               newrepoindex = "0"
+                                               if self.trees[x[1]]["vartree"].dbapi.cpv_exists(pkg):
+                                                       oldrepo = self.trees[x[1]]["vartree"].dbapi.aux_get(pkg, ["repository"])[0]
+                                               else:
+                                                       oldrepo = ""
+                                               if oldrepo != "":
+                                                       oldrepo = portdb.getRepositoryPath(oldrepo)
                                                dir_name=os.path.abspath(os.path.dirname(file_name)+"/../..")
-                                               if (overlays_real.count(dir_name)>0):
-                                                       verboseadd+=teal("["+str(overlays_real.index(
-                                                               os.path.normpath(dir_name))+1)+"]")+" "
-                                                       display_overlays=True
+                                               if (overlays_real.count(dir_name) > 0):
+                                                       newrepoindex = overlays_real.index(os.path.normpath(dir_name))
+                                                       newrepo = overlays_real[newrepoindex]
+                                                       newrepoindex += 1
+                                               
+                                               # assing lookup indexes
+                                               if oldrepo == "":
+                                                       oldrepoindex = "?"
+                                               elif oldrepo == pkgsettings["PORTDIR"]:
+                                                       oldrepoindex = "0"
+                                               else:
+                                                       oldrepoindex = str(overlays_real.index(os.path.normpath(oldrepo)) + 1)
+                                               if oldrepoindex == newrepoindex \
+                                                               or not self.trees[x[1]]["vartree"].dbapi.cpv_exists(pkg):
+                                                       repoadd = newrepoindex
+                                               else:
+                                                       repoadd = "%s=>%s" % (oldrepoindex, newrepoindex)
+
+                                               verboseadd+=teal("[%s]" % repoadd)+" "
+                                               display_overlays=True
                                        else:
                                                verboseadd += "[No ebuild?]"
 
-                               xs=portage.pkgsplit(x[2])
+                               xs = list(portage.pkgsplit(x[2]))
                                if xs[2]=="r0":
                                        xs[2]=""
                                else:
@@ -2690,17 +3030,53 @@ class depgraph:
                                                myoldbest=myoldbest[:-3]
                                        myoldbest=blue("["+myoldbest+"]")
 
+                               pkg_cp = xs[0]
+                               root_config = self.roots[myroot]
+                               system_set = root_config.sets["system"]
+                               world_set  = root_config.sets["world"]
+
+                               pkg_system = False
+                               pkg_world = False
+                               try:
+                                       pkg_system = system_set.findAtomForPackage(pkg_key, metadata)
+                                       pkg_world  = world_set.findAtomForPackage(pkg_key, metadata)
+                                       if not pkg_world and myroot == self.target_root and \
+                                               favorites_set.findAtomForPackage(pkg_key, metadata):
+                                               # Maybe it will be added to world now.
+                                               if create_world_atom(pkg_key, metadata,
+                                                       favorites_set, root_config.sets, portdb):
+                                                       pkg_world = True
+                               except portage.exception.InvalidDependString:
+                                       # This is reported elsewhere if relevant.
+                                       pass
+
+                               def pkgprint(pkg):
+                                       if pkg_merge:
+                                               if pkg_world:
+                                                       return colorize("PKG_MERGE_WORLD", pkg)
+                                               elif pkg_system:
+                                                       return colorize("PKG_MERGE_SYSTEM", pkg)
+                                               else:
+                                                       return colorize("PKG_MERGE", pkg)
+                                       else:
+                                               if pkg_world:
+                                                       return colorize("PKG_NOMERGE_WORLD", pkg)
+                                               elif pkg_system:
+                                                       return colorize("PKG_NOMERGE_SYSTEM", pkg)
+                                               else:
+                                                       return colorize("PKG_NOMERGE", pkg)
+
                                if x[1]!="/":
                                        if myoldbest:
                                                myoldbest +=" "
                                        if "--columns" in self.myopts:
                                                if "--quiet" in self.myopts:
-                                                       myprint=addl+" "+indent+darkgreen(xs[0])
+                                                       myprint=addl+" "+indent+pkgprint(pkg_cp)
                                                        myprint=myprint+darkblue(" "+xs[1]+xs[2])+" "
                                                        myprint=myprint+myoldbest
                                                        myprint=myprint+darkgreen("to "+x[1])
                                                else:
-                                                       myprint="["+x[0]+" "+addl+"] "+indent+darkgreen(xs[0])
+                                                       myprint="["+pkgprint(pkg_type)+" "+addl+"] "+indent+pkgprint(pkg_cp)
                                                        if (newlp-nc_len(myprint)) > 0:
                                                                myprint=myprint+(" "*(newlp-nc_len(myprint)))
                                                        myprint=myprint+"["+darkblue(xs[1]+xs[2])+"] "
@@ -2709,21 +3085,21 @@ class depgraph:
                                                        myprint=myprint+myoldbest
                                                        myprint=myprint+darkgreen("to "+x[1])+" "+verboseadd
                                        else:
-                                               if x[-1] == "nomerge" or not ordered:
-                                                       myprint = darkblue("[nomerge      ] ")
+                                               if not pkg_merge:
+                                                       myprint = "[%s      ] " % pkgprint("nomerge")
                                                else:
                                                        myprint = "[" + pkg_type + " " + addl + "] "
-                                               myprint += indent + darkgreen(pkg_key) + " " + \
+                                               myprint += indent + pkgprint(pkg_key) + " " + \
                                                        myoldbest + darkgreen("to " + myroot) + " " + \
                                                        verboseadd
                                else:
                                        if "--columns" in self.myopts:
                                                if "--quiet" in self.myopts:
-                                                       myprint=addl+" "+indent+darkgreen(xs[0])
+                                                       myprint=addl+" "+indent+pkgprint(pkg_cp)
                                                        myprint=myprint+" "+green(xs[1]+xs[2])+" "
                                                        myprint=myprint+myoldbest
                                                else:
-                                                       myprint="["+x[0]+" "+addl+"] "+indent+darkgreen(xs[0])
+                                                       myprint="["+pkgprint(pkg_type)+" "+addl+"] "+indent+pkgprint(pkg_cp)
                                                        if (newlp-nc_len(myprint)) > 0:
                                                                myprint=myprint+(" "*(newlp-nc_len(myprint)))
                                                        myprint=myprint+green(" ["+xs[1]+xs[2]+"] ")
@@ -2731,10 +3107,10 @@ class depgraph:
                                                                myprint=myprint+(" "*(oldlp-nc_len(myprint)))
                                                        myprint=myprint+myoldbest+"  "+verboseadd
                                        else:
-                                               if x[-1] == "nomerge" or not ordered:
-                                                       myprint=darkblue("[nomerge      ] "+indent+x[2]+" "+myoldbest+" ")+verboseadd
+                                               if not pkg_merge:
+                                                       myprint="["+pkgprint("nomerge")+"      ] "+indent+pkgprint(pkg_key)+" "+myoldbest+" "+verboseadd
                                                else:
-                                                       myprint="["+x[0]+" "+addl+"] "+indent+darkgreen(x[2])+" "+myoldbest+" "+verboseadd
+                                                       myprint="["+pkgprint(pkg_type)+" "+addl+"] "+indent+pkgprint(pkg_key)+" "+myoldbest+" "+verboseadd
                                p.append(myprint)
 
                        mysplit = portage.pkgsplit(x[2])
@@ -2764,11 +3140,13 @@ class depgraph:
                        print
                        print counters
                        if overlays and display_overlays:
-                               print "Portage overlays:"
+                               print "Portage tree and overlays:"
                                y=0
+                               print " "+teal("[0]"), self.settings["PORTDIR"]
                                for x in overlays:
                                        y=y+1
                                        print " "+teal("["+str(y)+"]"),x
+                               print " "+teal("[?]"), "indicates that the source repository could not be determined"
 
                if "--changelog" in self.myopts:
                        print
@@ -2777,6 +3155,14 @@ class depgraph:
                                sys.stdout.write(text)
 
                if self._pprovided_args:
+                       arg_refs = {}
+                       for arg_atom in self._pprovided_args:
+                               arg, atom = arg_atom
+                               arg_refs[arg_atom] = []
+                               cp = portage.dep_getkey(atom)
+                               for set_name, atom_set in self._sets.iteritems():
+                                       if atom in atom_set:
+                                               arg_refs[arg_atom].append(set_name)
                        msg = []
                        msg.append(bad("\nWARNING: "))
                        if len(self._pprovided_args) > 1:
@@ -2785,17 +3171,36 @@ class depgraph:
                        else:
                                msg.append("A requested package will not be " + \
                                        "merged because it is listed in\n")
-                       msg.append("         package.provided:\n\n")
-                       for arg in self._pprovided_args:
-                               msg.append("             " + arg + "\n")
+                       msg.append("package.provided:\n\n")
+                       problems_sets = set()
+                       for (arg, atom), refs in arg_refs.iteritems():
+                               ref_string = ""
+                               if refs:
+                                       problems_sets.update(refs)
+                                       refs.sort()
+                                       ref_string = ", ".join(["'%s'" % name for name in refs])
+                                       ref_string = " pulled in by " + ref_string
+                               msg.append("  %s%s\n" % (colorize("INFORM", arg), ref_string))
                        msg.append("\n")
+                       if "world" in problems_sets:
+                               msg.append("This problem can be solved in one of the following ways:\n\n")
+                               msg.append("  A) Use emaint to clean offending packages from world (if not installed).\n")
+                               msg.append("  B) Uninstall offending packages (cleans them from world).\n")
+                               msg.append("  C) Remove offending entries from package.provided.\n\n")
+                               msg.append("The best course of action depends on the reason that an offending\n")
+                               msg.append("package.provided entry exists.\n\n")
                        sys.stderr.write("".join(msg))
+               return os.EX_OK
 
        def calc_changelog(self,ebuildpath,current,next):
+               if ebuildpath == None or not os.path.exists(ebuildpath):
+                       return []
                current = '-'.join(portage.catpkgsplit(current)[1:])
-               if current.endswith('-r0'): current = current[:-3]
+               if current.endswith('-r0'):
+                       current = current[:-3]
                next = '-'.join(portage.catpkgsplit(next)[1:])
-               if next.endswith('-r0'): next = next[:-3]
+               if next.endswith('-r0'):
+                       next = next[:-3]
                changelogpath = os.path.join(os.path.split(ebuildpath)[0],'ChangeLog')
                try:
                        changelog = open(changelogpath).read()
@@ -2839,8 +3244,44 @@ class depgraph:
                        if release.endswith('-r0'):
                                release = release[:-3]
 
-       def outdated(self):
-               return self.outdatedpackages
+       def saveNomergeFavorites(self):
+               """Find atoms in favorites that are not in the mergelist and add them
+               to the world file if necessary."""
+               for x in ("--fetchonly", "--fetch-all-uri",
+                       "--oneshot", "--onlydeps", "--pretend"):
+                       if x in self.myopts:
+                               return
+               root_config = self.roots[self.target_root]
+               world_set = root_config.sets["world"]
+               world_set.lock()
+               world_set.load()
+               args_set = self._sets["args"]
+               portdb = self.trees[self.target_root]["porttree"].dbapi
+               added_favorites = set()
+               for x in self._set_nodes:
+                       pkg_type, root, pkg_key, pkg_status = x
+                       if pkg_status != "nomerge":
+                               continue
+                       metadata = dict(izip(self._mydbapi_keys,
+                               self.mydbapi[root].aux_get(pkg_key, self._mydbapi_keys)))
+                       try:
+                               myfavkey = create_world_atom(pkg_key, metadata,
+                                       args_set, root_config.sets, portdb)
+                               if myfavkey:
+                                       if myfavkey in added_favorites:
+                                               continue
+                                       added_favorites.add(myfavkey)
+                                       world_set.add(myfavkey)
+                                       print ">>> Recording",myfavkey,"in \"world\" favorites file..."
+                       except portage.exception.InvalidDependString, e:
+                               writemsg("\n\n!!! '%s' has invalid PROVIDE: %s\n" % \
+                                       (pkg_key, str(e)), noiselevel=-1)
+                               writemsg("!!! see '%s'\n\n" % os.path.join(
+                                       root, portage.VDB_PATH, pkg_key, "PROVIDE"), noiselevel=-1)
+                               del e
+               if added_favorites:
+                       world_set.save()
+               world_set.unlock()
 
 class PackageCounters(object):
 
@@ -2917,7 +3358,12 @@ class MergeTask(object):
                                portage.config(clone=trees["/"]["vartree"].settings)
 
        def merge(self, mylist, favorites, mtimedb):
+               from portage.elog import elog_process
+               from portage.elog.filtering import filter_mergephases
                failed_fetches = []
+               fetchonly = "--fetchonly" in self.myopts or \
+                       "--fetch-all-uri" in self.myopts
+               pretend = "--pretend" in self.myopts
                mymergelist=[]
                ldpath_mtimes = mtimedb["ldpath"]
                xterm_titles = "notitles" not in self.settings.features
@@ -2975,35 +3421,11 @@ class MergeTask(object):
                                del x, mytype, myroot, mycpv, mystatus, quiet_config
                        del shown_verifying_msg, quiet_settings
 
-               #buildsyspkg: I need mysysdict also on resume (moved from the else block)
-               mysysdict = genericdict(getlist(self.settings, "system"))
+               system_set = SystemSet(self.settings)
+               args_set = AtomSet(favorites)
+               world_set = WorldSet(self.settings)
                if "--resume" not in self.myopts:
-                       myfavs = portage.grabfile(
-                               os.path.join(self.target_root, portage.WORLD_FILE))
-                       myfavdict=genericdict(myfavs)
-                       for x in range(len(mylist)):
-                               if mylist[x][3]!="nomerge":
-                                       # Add to the mergelist
-                                       mymergelist.append(mylist[x])
-                               else:
-                                       myfavkey=portage.cpv_getkey(mylist[x][2])
-                                       if "--onlydeps" in self.myopts:
-                                               continue
-                                       # Add to the world file. Since we won't be able to later.
-                                       if "--fetchonly" not in self.myopts and \
-                                               myfavkey in favorites:
-                                               #don't record if already in system profile or already recorded
-                                               if (not mysysdict.has_key(myfavkey)) and (not myfavdict.has_key(myfavkey)):
-                                                       #we don't have a favorites entry for this package yet; add one
-                                                       myfavdict[myfavkey]=myfavkey
-                                                       print ">>> Recording",myfavkey,"in \"world\" favorites file..."
-                       if not ("--fetchonly" in self.myopts or \
-                               "--fetch-all-uri" in self.myopts or \
-                               "--pretend" in self.myopts):
-                               portage.write_atomic(
-                                       os.path.join(self.target_root, portage.WORLD_FILE),
-                                       "\n".join(myfavdict.values()))
-
+                       mymergelist = mylist
                        mtimedb["resume"]["mergelist"]=mymergelist[:]
                        mtimedb.commit()
 
@@ -3048,9 +3470,13 @@ class MergeTask(object):
                                del fetch_log, logfile, fd_pipes, fetch_env, fetch_args, \
                                        resume_opts
 
+               metadata_keys = [k for k in portage.auxdbkeys \
+                       if not k.startswith("UNUSED_")] + ["USE"]
+
                mergecount=0
                for x in mymergelist:
                        mergecount+=1
+                       pkg_type = x[0]
                        myroot=x[1]
                        pkg_key = x[2]
                        pkgindex=2
@@ -3058,6 +3484,22 @@ class MergeTask(object):
                        bindb  = self.trees[myroot]["bintree"].dbapi
                        vartree = self.trees[myroot]["vartree"]
                        pkgsettings = self.pkgsettings[myroot]
+                       metadata = {}
+                       if pkg_type == "blocks":
+                               pass
+                       elif pkg_type == "ebuild":
+                               mydbapi = portdb
+                               metadata.update(izip(metadata_keys,
+                                       mydbapi.aux_get(pkg_key, metadata_keys)))
+                               pkgsettings.setcpv(pkg_key, mydb=mydbapi)
+                               metadata["USE"] = pkgsettings["USE"]
+                       else:
+                               if pkg_type == "binary":
+                                       mydbapi = bindb
+                               else:
+                                       raise AssertionError("Package type: '%s'" % pkg_type)
+                               metadata.update(izip(metadata_keys,
+                                       mydbapi.aux_get(pkg_key, metadata_keys)))
                        if x[0]=="blocks":
                                pkgindex=3
                        y = portdb.findname(pkg_key)
@@ -3077,7 +3519,7 @@ class MergeTask(object):
                        #buildsyspkg: Check if we need to _force_ binary package creation
                        issyspkg = ("buildsyspkg" in myfeat) \
                                        and x[0] != "blocks" \
-                                       and mysysdict.has_key(portage.cpv_getkey(x[2])) \
+                                       and system_set.findAtomForPackage(pkg_key, metadata) \
                                        and "--buildpkg" not in self.myopts
                        if x[0] in ["ebuild","blocks"]:
                                if x[0] == "blocks" and "--fetchonly" not in self.myopts:
@@ -3140,14 +3582,21 @@ class MergeTask(object):
                                                        (mergecount, len(mymergelist), pkg_key)
                                                emergelog(xterm_titles, msg, short_msg=short_msg)
                                                self.trees[myroot]["bintree"].prevent_collision(pkg_key)
+                                               binpkg_tmpfile = os.path.join(pkgsettings["PKGDIR"],
+                                                       pkg_key + ".tbz2." + str(os.getpid()))
+                                               pkgsettings["PORTAGE_BINPKG_TMPFILE"] = binpkg_tmpfile
+                                               pkgsettings.backup_changes("PORTAGE_BINPKG_TMPFILE")
                                                retval = portage.doebuild(y, "package", myroot,
                                                        pkgsettings, self.edebug, mydbapi=portdb,
                                                        tree="porttree")
+                                               del pkgsettings["PORTAGE_BINPKG_TMPFILE"]
+                                               if retval != os.EX_OK or \
+                                                       "--buildpkgonly" in self.myopts:
+                                                       elog_process(pkg_key, pkgsettings, phasefilter=filter_mergephases)
                                                if retval != os.EX_OK:
                                                        return retval
                                                bintree = self.trees[myroot]["bintree"]
-                                               if bintree.populated:
-                                                       bintree.inject(pkg_key)
+                                               bintree.inject(pkg_key, filename=binpkg_tmpfile)
                                                if "--buildpkgonly" not in self.myopts:
                                                        msg = " === (%s of %s) Merging (%s::%s)" % \
                                                                (mergecount, len(mymergelist), pkg_key, y)
@@ -3214,9 +3663,29 @@ class MergeTask(object):
                                                        short_msg = "emerge: (%s of %s) %s Fetch" % \
                                                                (mergecount, len(mymergelist), pkg_key)
                                                        emergelog(xterm_titles, msg, short_msg=short_msg)
-                                                       if not self.trees[myroot]["bintree"].gettbz2(
-                                                               pkg_key):
-                                                               return 1
+                                                       try:
+                                                               self.trees[myroot]["bintree"].gettbz2(pkg_key)
+                                                       except portage.exception.FileNotFound:
+                                                               writemsg("!!! Fetching Binary failed " + \
+                                                                       "for '%s'\n" % pkg_key, noiselevel=-1)
+                                                               if not fetchonly:
+                                                                       return 1
+                                                               failed_fetches.append(pkg_key)
+                                                       except portage.exception.DigestException, e:
+                                                               writemsg("\n!!! Digest verification failed:\n",
+                                                                       noiselevel=-1)
+                                                               writemsg("!!! %s\n" % e.value[0],
+                                                                       noiselevel=-1)
+                                                               writemsg("!!! Reason: %s\n" % e.value[1],
+                                                                       noiselevel=-1)
+                                                               writemsg("!!! Got: %s\n" % e.value[2],
+                                                                       noiselevel=-1)
+                                                               writemsg("!!! Expected: %s\n" % e.value[3],
+                                                                       noiselevel=-1)
+                                                               os.unlink(mytbz2)
+                                                               if not fetchonly:
+                                                                       return 1
+                                                               failed_fetches.append(pkg_key)
                                        finally:
                                                if tbz2_lock:
                                                        portage.locks.unlockfile(tbz2_lock)
@@ -3238,24 +3707,23 @@ class MergeTask(object):
                                #need to check for errors
                        if "--buildpkgonly" not in self.myopts:
                                self.trees[x[1]]["vartree"].inject(x[2])
-                               myfavkey=portage.cpv_getkey(x[2])
-                               if "--fetchonly" not in self.myopts and \
-                                       "--fetch-all-uri" not in self.myopts and \
-                                       myfavkey in favorites:
-                                       myfavs = portage.grabfile(os.path.join(myroot, portage.WORLD_FILE))
-                                       myfavdict=genericdict(myfavs)
-                                       #don't record if already in system profile or already recorded
-                                       if (not mysysdict.has_key(myfavkey)) and (not myfavdict.has_key(myfavkey)):
-                                               #we don't have a favorites entry for this package yet; add one
-                                               myfavdict[myfavkey]=myfavkey
+                               myfavkey = portage.cpv_getkey(x[2])
+                               if not fetchonly and not pretend and \
+                                       args_set.findAtomForPackage(pkg_key, metadata):
+                                       world_set.lock()
+                                       world_set.load()
+                                       myfavkey = create_world_atom(pkg_key, metadata,
+                                               args_set, {"world":world_set, "system":system_set},
+                                               portdb)
+                                       if myfavkey:
+                                               world_set.add(myfavkey)
                                                print ">>> Recording",myfavkey,"in \"world\" favorites file..."
                                                emergelog(xterm_titles, " === ("+\
                                                        str(mergecount)+" of "+\
                                                        str(len(mymergelist))+\
                                                        ") Updating world file ("+x[pkgindex]+")")
-                                               portage.write_atomic(
-                                               os.path.join(myroot, portage.WORLD_FILE),
-                                               "\n".join(myfavdict.values()))
+                                               world_set.save()
+                                       world_set.unlock()
 
                                if "--pretend" not in self.myopts and \
                                        "--fetchonly" not in self.myopts and \
@@ -3528,7 +3996,7 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files,
                                        if not slotmap.has_key(myslot):
                                                slotmap[myslot]={}
                                        slotmap[myslot][localtree.dbapi.cpv_counter(mypkg)]=mypkg
-                               for myslot in slotmap.keys():
+                               for myslot in slotmap:
                                        counterkeys=slotmap[myslot].keys()
                                        counterkeys.sort()
                                        if not counterkeys:
@@ -3555,7 +4023,7 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files,
        finally:
                if vdb_lock:
                        portage.locks.unlockdir(vdb_lock)
-       for x in pkgmap.keys():
+       for x in pkgmap:
                for y in localtree.dep_match(x):
                        if y not in pkgmap[x]["omitted"] and \
                           y not in pkgmap[x]["selected"] and \
@@ -3570,9 +4038,13 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files,
                        if "--pretend" not in myopts and "--ask" not in myopts:
                                countdown(int(settings["EMERGE_WARNING_DELAY"]),
                                        colorize("UNMERGE_WARN", "Press Ctrl-C to Stop"))
-               print "\n "+white(x)
+               if "--quiet" not in myopts:
+                       print "\n "+white(x)
+               else:
+                       print white(x)+": ",
                for mytype in ["selected","protected","omitted"]:
-                       portage.writemsg_stdout((mytype + ": ").rjust(14), noiselevel=-1)
+                       if "--quiet" not in myopts:
+                               portage.writemsg_stdout((mytype + ": ").rjust(14), noiselevel=-1)
                        if pkgmap[x][mytype]:
                                for mypkg in pkgmap[x][mytype]:
                                        mysplit=portage.catpkgsplit(mypkg)
@@ -3587,7 +4059,10 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files,
                                                portage.writemsg_stdout(
                                                        colorize("GOOD", myversion + " "), noiselevel=-1)
                        else:
-                               portage.writemsg_stdout("none", noiselevel=-1)
+                               portage.writemsg_stdout("none ", noiselevel=-1)
+                       if "--quiet" not in myopts:
+                               portage.writemsg_stdout("\n", noiselevel=-1)
+               if "--quiet" in myopts:
                        portage.writemsg_stdout("\n", noiselevel=-1)
 
        portage.writemsg_stdout("\n>>> " + colorize("UNMERGE_WARN", "'Selected'") + \
@@ -3611,7 +4086,7 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files,
        if not autoclean:
                countdown(int(settings["CLEAN_DELAY"]), ">>> Unmerging")
 
-       for x in pkgmap.keys():
+       for x in pkgmap:
                for y in pkgmap[x]["selected"]:
                        print ">>> Unmerging "+y+"..."
                        emergelog(xterm_titles, "=== Unmerging... ("+y+")")
@@ -3716,7 +4191,34 @@ def chk_updated_info_files(root, infodirs, prev_mtimes, retval):
                                print " "+green("*")+" Processed",icount,"info files."
 
 
-def post_emerge(settings, mtimedb, retval):
+def display_news_notification(trees):
+       for target_root in trees:
+               if len(trees) > 1 and target_root != "/":
+                       break
+       settings = trees[target_root]["vartree"].settings
+       portdb = trees[target_root]["porttree"].dbapi
+       vardb = trees[target_root]["vartree"].dbapi
+       NEWS_PATH = os.path.join("metadata", "news")
+       UNREAD_PATH = os.path.join(target_root, NEWS_LIB_PATH, "news")
+       newsReaderDisplay = False
+
+       for repo in portdb.getRepositories():
+               unreadItems = checkUpdatedNewsItems(
+                       portdb, vardb, NEWS_PATH, UNREAD_PATH, repo)
+               if unreadItems:
+                       if not newsReaderDisplay:
+                               newsReaderDisplay = True
+                               print
+                       print colorize("WARN", " * IMPORTANT:"),
+                       print "%s news items need reading for repository '%s'." % (unreadItems, repo)
+                       
+       
+       if newsReaderDisplay:
+               print colorize("WARN", " *"),
+               print "Use " + colorize("GOOD", "eselect news") + " to read news items."
+               print
+
+def post_emerge(trees, mtimedb, retval):
        """
        Misc. things to run at the end of a merge session.
        
@@ -3724,10 +4226,11 @@ def post_emerge(settings, mtimedb, retval):
        Update Config Files
        Update News Items
        Commit mtimeDB
+       Display preserved libs warnings
        Exit Emerge
-       
-       @param settings: Configuration settings (typically portage.settings)
-       @type settings: portage.config()
+
+       @param trees: A dictionary mapping each ROOT to it's package databases
+       @type trees: dict
        @param mtimedb: The mtimeDB to store data needed across merge invocations
        @type mtimedb: MtimeDB class instance
        @param retval: Emerge's return value
@@ -3736,7 +4239,11 @@ def post_emerge(settings, mtimedb, retval):
        @returns:
        1.  Calls sys.exit(retval)
        """
-       target_root = settings["ROOT"]
+       for target_root in trees:
+               if len(trees) > 1 and target_root != "/":
+                       break
+       vardbapi = trees[target_root]["vartree"].dbapi
+       settings = vardbapi.settings
        info_mtimes = mtimedb["info"]
 
        # Load the most current variables from ${ROOT}/etc/profile.env
@@ -3753,26 +4260,27 @@ def post_emerge(settings, mtimedb, retval):
        emergelog("notitles" not in settings.features,
                " *** exiting successfully.")
 
+       # Dump the mod_echo output now so that our other notifications are shown
+       # last.
+       from portage.elog import mod_echo
+       mod_echo.finalize()
+
        if "noinfo" not in settings.features:
                chk_updated_info_files(target_root, infodirs, info_mtimes, retval)
 
        chk_updated_cfg_files(target_root, config_protect)
        
-       NEWS_PATH = os.path.join( "metadata", "news" )
-       UNREAD_PATH = os.path.join( target_root, NEWS_LIB_PATH, 'news')
-       porttree = portdbapi( porttree_root = settings["PORTDIR"], mysettings = settings )
-       newsReaderDisplay = False
+       display_news_notification(trees)
        
-       for repo in porttree.getRepositories():
-               unreadItems = checkUpdatedNewsItems(target_root, NEWS_PATH, UNREAD_PATH, repo)
-               if unreadItems:
-                       print colorize("WARN", " * IMPORTANT:"),
-                       print "%s news items need reading for repository '%s'." % (unreadItems, repo)
-                       newsReaderDisplay = True
-       
-       if newsReaderDisplay:
-               print colorize("WARN", " *"),
-               print "Use " + colorize("GOOD", "eselect news") + " to read news items."
+       if vardbapi.plib_registry.hasEntries():
+               print colorize("WARN", "!!!") + " existing preserved libs:"
+               plibdata = vardbapi.plib_registry.getPreservedLibs()
+               for cpv in plibdata:
+                       print colorize("WARN", ">>>") + " package: %s" % cpv
+                       for f in plibdata[cpv]:
+                               print colorize("WARN", " * ") + " - %s" % f
+               print "Use " + colorize("GOOD", "revdep-rebuild") + " to rebuild packages using these libraries"
+               print "and then remerge the packages listed above."
        
        mtimedb.commit()
        sys.exit(retval)
@@ -3812,13 +4320,15 @@ def chk_updated_cfg_files(target_root, config_protect):
                        #print " "+yellow("*")+" Type "+green("emerge --help config")+" to learn how to update config files."
                        print " "+yellow("*")+" Type "+green("emerge --help config")+" to learn how to update config files."
 
-def checkUpdatedNewsItems( root, NEWS_PATH, UNREAD_PATH, repo_id ):
+def checkUpdatedNewsItems(portdb, vardb, NEWS_PATH, UNREAD_PATH, repo_id):
        """
        Examines news items in repodir + '/' + NEWS_PATH and attempts to find unread items
        Returns the number of unread (yet relevent) items.
        
-       @param root:
-       @type root:
+       @param portdb: a portage tree database
+       @type portdb: pordbapi
+       @param vardb: an installed package database
+       @type vardb: vardbapi
        @param NEWS_PATH:
        @type NEWS_PATH:
        @param UNREAD_PATH:
@@ -3831,7 +4341,7 @@ def checkUpdatedNewsItems( root, NEWS_PATH, UNREAD_PATH, repo_id ):
        
        """
        from portage.news import NewsManager
-       manager = NewsManager( root, NEWS_PATH, UNREAD_PATH )
+       manager = NewsManager(portdb, vardb, NEWS_PATH, UNREAD_PATH)
        return manager.getUnreadItems( repo_id, update=True )
 
 def is_valid_package_atom(x):
@@ -3896,7 +4406,7 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                mytimeout=180
 
                rsync_opts = []
-
+               import shlex, StringIO
                if settings["PORTAGE_RSYNC_OPTS"] == "":
                        portage.writemsg("PORTAGE_RSYNC_OPTS empty or unset, using hardcoded defaults\n")
                        rsync_opts.extend([
@@ -3923,7 +4433,11 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                        # defaults.
 
                        portage.writemsg("Using PORTAGE_RSYNC_OPTS instead of hardcoded defaults\n", 1)
-                       rsync_opts.extend(settings["PORTAGE_RSYNC_OPTS"].split())
+                       lexer = shlex.shlex(StringIO.StringIO(
+                               settings.get("PORTAGE_RSYNC_OPTS","")), posix=True)
+                       lexer.whitespace_split = True
+                       rsync_opts.extend(lexer)
+                       del lexer
 
                        for opt in ("--recursive", "--times"):
                                if opt not in rsync_opts:
@@ -4012,6 +4526,12 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                                pass
                del content
 
+               try:
+                       rsync_initial_timeout = \
+                               int(settings.get("PORTAGE_RSYNC_INITIAL_TIMEOUT", "15"))
+               except ValueError:
+                       rsync_initial_timeout = 15
+
                try:
                        if settings.has_key("RSYNC_RETRIES"):
                                print yellow("WARNING:")+" usage of RSYNC_RETRIES is deprecated, use PORTAGE_RSYNC_RETRIES instead"
@@ -4032,8 +4552,12 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                        user_name=""
                updatecache_flg=True
                all_rsync_opts = set(rsync_opts)
-               all_rsync_opts.update(
-                       settings.get("PORTAGE_RSYNC_EXTRA_OPTS","").split())
+               lexer = shlex.shlex(StringIO.StringIO(
+                       settings.get("PORTAGE_RSYNC_EXTRA_OPTS","")), posix=True)
+               lexer.whitespace_split = True
+               extra_rsync_opts = list(lexer)
+               del lexer
+               all_rsync_opts.update(extra_rsync_opts)
                family = socket.AF_INET
                if "-4" in all_rsync_opts or "--ipv4" in all_rsync_opts:
                        family = socket.AF_INET
@@ -4091,8 +4615,7 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                        if mytimestamp != 0 and "--quiet" not in myopts:
                                print ">>> Checking server timestamp ..."
 
-                       rsynccommand = " ".join(["/usr/bin/rsync", " ".join(rsync_opts),
-                               settings.get("PORTAGE_RSYNC_EXTRA_OPTS","")])
+                       rsynccommand = ["/usr/bin/rsync"] + rsync_opts + extra_rsync_opts
 
                        if "--debug" in myopts:
                                print rsynccommand
@@ -4105,7 +4628,7 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                        # connection attempt to an unresponsive server which rsync's
                        # --timeout option does not prevent.
                        if True:
-                               mycommand = rsynccommand.split()
+                               mycommand = rsynccommand[:]
                                mycommand.append(dosyncuri.rstrip("/") + \
                                        "/metadata/timestamp.chk")
                                mycommand.append(tmpservertimestampfile)
@@ -4118,14 +4641,16 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                                        # Timeout here in case the server is unresponsive.  The
                                        # --timeout rsync option doesn't apply to the initial
                                        # connection attempt.
-                                       signal.alarm(15)
+                                       if rsync_initial_timeout:
+                                               signal.alarm(rsync_initial_timeout)
                                        try:
                                                mypids.extend(portage.process.spawn(
                                                        mycommand, env=settings.environ(), returnpid=True))
                                                exitcode = os.waitpid(mypids[0], 0)[1]
                                                content = portage.grabfile(tmpservertimestampfile)
                                        finally:
-                                               signal.alarm(0)
+                                               if rsync_initial_timeout:
+                                                       signal.alarm(0)
                                                try:
                                                        os.unlink(tmpservertimestampfile)
                                                except OSError:
@@ -4179,8 +4704,7 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                                        print
                                elif (servertimestamp == 0) or (servertimestamp > mytimestamp):
                                        # actual sync
-                                       mycommand=rsynccommand+" "+dosyncuri+"/ "+myportdir
-                                       mycommand = mycommand.split()
+                                       mycommand = rsynccommand + [dosyncuri+"/", myportdir]
                                        exitcode = portage.process.spawn(mycommand,
                                                env=settings.environ())
                                        if exitcode in [0,1,3,4,11,14,20,21]:
@@ -4278,7 +4802,7 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
        if os.path.exists(myportdir+"/metadata/cache") and updatecache_flg:
                action_metadata(settings, portdb, myopts)
 
-       if portage.global_updates(settings, trees, mtimedb["updates"]):
+       if portage._global_updates(trees, mtimedb["updates"]):
                mtimedb.commit()
                # Reload the whole config from scratch.
                settings, trees, mtimedb = load_emerge_config(trees=trees)
@@ -4302,10 +4826,11 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                print
                print red(" * ")+bold("An update to portage is available.")+" It is _highly_ recommended"
                print red(" * ")+"that you update portage now, before any other packages are updated."
-               print red(" * ")+"Please run 'emerge portage' and then update "+bold("ALL")+" of your"
-               print red(" * ")+"configuration files."
-               print red(" * ")+"To update portage, run 'emerge portage'."
                print
+               print red(" * ")+"To update portage, run 'emerge portage' now."
+               print
+       
+       display_news_notification(trees)
 
 def action_metadata(settings, portdb, myopts):
        portage.writemsg_stdout("\n>>> Updating Portage cache:      ")
@@ -4713,10 +5238,11 @@ def action_depclean(settings, trees, ldpath_mtimes,
        vardb = dep_check_trees[myroot]["vartree"].dbapi
        # Constrain dependency selection to the installed packages.
        dep_check_trees[myroot]["porttree"] = dep_check_trees[myroot]["vartree"]
-       syslist = getlist(settings, "system")
-       worldlist = getlist(settings, "world")
-       system_world_dict = genericdict(worldlist)
-       system_world_dict.update(genericdict(syslist))
+       system_set = SystemSet(settings)
+       syslist = list(system_set)
+       world_set = WorldSet(settings)
+       world_set.load()
+       worldlist = list(world_set)
        fakedb = portage.fakedbapi(settings=settings)
        myvarlist = vardb.cpv_all()
 
@@ -4745,6 +5271,7 @@ def action_depclean(settings, trees, ldpath_mtimes,
        remaining_atoms += [(atom, 'system', hard) for atom in syslist if vardb.match(atom)]
        unresolveable = {}
        aux_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
+       metadata_keys = ["PROVIDE", "SLOT", "USE"]
 
        while remaining_atoms:
                atom, parent, priority = remaining_atoms.pop()
@@ -4753,11 +5280,11 @@ def action_depclean(settings, trees, ldpath_mtimes,
                        if not atom.startswith("!") and priority == hard:
                                unresolveable.setdefault(atom, []).append(parent)
                        continue
-               if portage.dep_getkey(atom) not in system_world_dict:
+               if len(pkgs) > 1 and parent != "world":
                        # Prune all but the best matching slot, since that's all that a
-                       # deep world update would pull in.  Don't prune if the cpv is in
-                       # system or world though, since those sets trigger greedy update
-                       # of all slots.
+                       # deep world update would pull in.  Don't prune if this atom comes
+                       # directly from world though, since world atoms are greedy when
+                       # they don't specify a slot.
                        pkgs = [portage.best(pkgs)]
                for pkg in pkgs:
                        if fakedb.cpv_exists(pkg):
@@ -4905,7 +5432,7 @@ def action_build(settings, trees, mtimedb,
        else:
                if ("--resume" in myopts):
                        print darkgreen("emerge: It seems we have nothing to resume...")
-                       sys.exit(0)
+                       return os.EX_OK
 
                myparams = create_depgraph_params(myopts, myaction)
                if myaction in ["system","world"]:
@@ -4915,7 +5442,7 @@ def action_build(settings, trees, mtimedb,
                        mydepgraph = depgraph(settings, trees, myopts, myparams, spinner)
                        if not mydepgraph.xcreate(myaction):
                                print "!!! Depgraph creation failed."
-                               sys.exit(1)
+                               return 1
                        if "--quiet" not in myopts and "--nodeps" not in myopts:
                                print "\b\b... done!"
                else:
@@ -4927,9 +5454,9 @@ def action_build(settings, trees, mtimedb,
                                retval, favorites = mydepgraph.select_files(myfiles)
                        except portage.exception.PackageNotFound, e:
                                portage.writemsg("\n!!! %s\n" % str(e), noiselevel=-1)
-                               sys.exit(1)
+                               return 1
                        if not retval:
-                               sys.exit(1)
+                               return 1
                        if "--quiet" not in myopts and "--nodeps" not in myopts:
                                print "\b\b... done!"
 
@@ -4940,7 +5467,7 @@ def action_build(settings, trees, mtimedb,
                        for x in mydepgraph.missingbins:
                                sys.stderr.write("   "+str(x)+"\n")
                        sys.stderr.write("\nThese are required by '--usepkgonly' -- Terminating.\n\n")
-                       sys.exit(1)
+                       return 1
 
        if "--pretend" not in myopts and \
                ("--ask" in myopts or "--tree" in myopts or \
@@ -4953,12 +5480,18 @@ def action_build(settings, trees, mtimedb,
                                mymergelist = mymergelist[1:]
                        if len(mymergelist) == 0:
                                print colorize("INFORM", "emerge: It seems we have nothing to resume...")
-                               sys.exit(0)
-                       mydepgraph.display(mymergelist)
+                               return os.EX_OK
+                       favorites = mtimedb["resume"]["favorites"]
+                       retval = mydepgraph.display(mymergelist, favorites=favorites)
+                       if retval != os.EX_OK:
+                               return retval
                        prompt="Would you like to resume merging these packages?"
                else:
-                       mydepgraph.display(
-                               mydepgraph.altlist(reversed=("--tree" in myopts)))
+                       retval = mydepgraph.display(
+                               mydepgraph.altlist(reversed=("--tree" in myopts)),
+                               favorites=favorites)
+                       if retval != os.EX_OK:
+                               return retval
                        mergecount=0
                        for x in mydepgraph.altlist():
                                if x[0] != "blocks" and x[3] != "nomerge":
@@ -4969,7 +5502,7 @@ def action_build(settings, trees, mtimedb,
                                        print   "!!!        at the same time on the same system."
                                        if "--quiet" not in myopts:
                                                show_blocker_docs_link()
-                                       sys.exit(1)
+                                       return 1
                        if mergecount==0:
                                if "--noreplace" in myopts and favorites:
                                        print
@@ -4982,7 +5515,7 @@ def action_build(settings, trees, mtimedb,
                                        print
                                        print "Nothing to merge; quitting."
                                        print
-                                       sys.exit(0)
+                                       return os.EX_OK
                        elif "--fetchonly" in myopts or "--fetch-all-uri" in myopts:
                                prompt="Would you like to fetch the source files for these packages?"
                        else:
@@ -4992,7 +5525,7 @@ def action_build(settings, trees, mtimedb,
                        print
                        print "Quitting."
                        print
-                       sys.exit(0)
+                       return os.EX_OK
                # Don't ask again (e.g. when auto-cleaning packages after merge)
                myopts.pop("--ask", None)
 
@@ -5004,22 +5537,28 @@ def action_build(settings, trees, mtimedb,
                                mymergelist = mymergelist[1:]
                        if len(mymergelist) == 0:
                                print colorize("INFORM", "emerge: It seems we have nothing to resume...")
-                               sys.exit(0)
-                       mydepgraph.display(mymergelist)
+                               return os.EX_OK
+                       favorites = mtimedb["resume"]["favorites"]
+                       retval = mydepgraph.display(mymergelist, favorites=favorites)
+                       if retval != os.EX_OK:
+                               return retval
                else:
-                       mydepgraph.display(
-                               mydepgraph.altlist(reversed=("--tree" in myopts)))
-               if ("--buildpkgonly" in myopts):
-                       if not mydepgraph.digraph.hasallzeros(ignore_priority=DepPriority.MEDIUM):
-                               print "\n!!! --buildpkgonly requires all dependencies to be merged."
-                               print "!!! You have to merge the dependencies before you can build this package.\n"
-                               sys.exit(1)
+                       retval = mydepgraph.display(
+                               mydepgraph.altlist(reversed=("--tree" in myopts)),
+                               favorites=favorites)
+                       if retval != os.EX_OK:
+                               return retval
+                       if "--buildpkgonly" in myopts and \
+                               not mydepgraph.digraph.hasallzeros(ignore_priority=DepPriority.MEDIUM):
+                                       print "\n!!! --buildpkgonly requires all dependencies to be merged."
+                                       print "!!! You have to merge the dependencies before you can build this package.\n"
+                                       return 1
        else:
                if ("--buildpkgonly" in myopts):
                        if not mydepgraph.digraph.hasallzeros(ignore_priority=DepPriority.MEDIUM):
                                print "\n!!! --buildpkgonly requires all dependencies to be merged."
                                print "!!! Cannot merge requested packages. Merge deps and try again.\n"
-                               sys.exit(1)
+                               return 1
 
                if ("--resume" in myopts):
                        favorites=mtimedb["resume"]["favorites"]
@@ -5033,7 +5572,7 @@ def action_build(settings, trees, mtimedb,
                        retval = mergetask.merge(
                                mtimedb["resume"]["mergelist"], favorites, mtimedb)
                        if retval != os.EX_OK:
-                               sys.exit(retval)
+                               return retval
                else:
                        if "resume" in mtimedb and \
                        "mergelist" in mtimedb["resume"] and \
@@ -5066,11 +5605,13 @@ def action_build(settings, trees, mtimedb,
                                                pkglist.append(pkg)
                        else:
                                pkglist = mydepgraph.altlist()
+                       if favorites:
+                               mydepgraph.saveNomergeFavorites()
                        del mydepgraph
                        mergetask = MergeTask(settings, trees, myopts)
                        retval = mergetask.merge(pkglist, favorites, mtimedb)
                        if retval != os.EX_OK:
-                               sys.exit(retval)
+                               return retval
 
                if mtimedb.has_key("resume"):
                        del mtimedb["resume"]
@@ -5111,6 +5652,11 @@ def parse_opts(tmpcmdline, silent=False):
                        "help":"include unnecessary build time dependencies",
                        "type":"choice",
                        "choices":("y", "n")
+               },
+               "--reinstall": {
+                       "help":"specify conditions to trigger package reinstallation",
+                       "type":"choice",
+                       "choices":["changed-use"]
                }
        }
 
@@ -5322,7 +5868,7 @@ def emerge_main():
                portage.writemsg("!!! %s\n" % str(e))
                del e
 
-       if portage.global_updates(settings, trees, mtimedb["updates"]):
+       if portage._global_updates(trees, mtimedb["updates"]):
                mtimedb.commit()
                # Reload the whole config from scratch.
                settings, trees, mtimedb = load_emerge_config(trees=trees)
@@ -5331,7 +5877,7 @@ def emerge_main():
        xterm_titles = "notitles" not in settings.features
 
        tmpcmdline = []
-       if "--ignore-default-opts" not in sys.argv:
+       if "--ignore-default-opts" not in myopts:
                tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
        tmpcmdline.extend(sys.argv[1:])
        myaction, myopts, myfiles = parse_opts(tmpcmdline)
@@ -5354,7 +5900,8 @@ def emerge_main():
        if "candy" in settings.features:
                spinner.update = spinner.update_scroll
 
-       portage.deprecated_profile_check()
+       if "--quiet" not in myopts:
+               portage.deprecated_profile_check()
 
        #Freeze the portdbapi for enhanced performance:
        for myroot in trees:
@@ -5384,7 +5931,6 @@ def emerge_main():
        for x in myfiles:
                ext = os.path.splitext(x)[1]
                if (ext == ".ebuild" or ext == ".tbz2") and os.path.exists(os.path.abspath(x)):
-                       print "emerging by path implies --oneshot... adding --oneshot to options."
                        print colorize("BAD", "\n*** emerging by path is broken and may not always work!!!\n")
                        break
 
@@ -5467,10 +6013,6 @@ def emerge_main():
                emerge.help.help(myaction, myopts, portage.output.havecolor)
                sys.exit(0)
 
-       if portage.wheelgid == portage.portage_gid:
-               print "emerge: wheel group use is being deprecated. Please update group and passwd to"
-               print "        include the portage user as noted above, and then use group portage."
-
        if "--debug" in myopts:
                print "myaction", myaction
                print "myopts", myopts
@@ -5482,12 +6024,12 @@ def emerge_main():
        # check if root user is the current user for the actions where emerge needs this
        if portage.secpass < 2:
                # We've already allowed "--version" and "--help" above.
-               if "--pretend" not in myopts and \
-               myaction not in ("search","info"):
+               if "--pretend" not in myopts and myaction not in ("search","info"):
                        need_superuser = not \
                                ("--fetchonly" in myopts or \
                                "--fetch-all-uri" in myopts or \
-                               myaction in ("metadata", "regen"))
+                               myaction in ("metadata", "regen") or \
+                               (myaction == "sync" and os.access(settings["PORTDIR"], os.W_OK)))
                        if portage.secpass < 1 or \
                                need_superuser:
                                if need_superuser:
@@ -5500,12 +6042,12 @@ def emerge_main():
                                if "--ask" in myopts:
                                        myopts["--pretend"] = True
                                        del myopts["--ask"]
-                                       print ("%s access would be required... " + \
+                                       print ("%s access is required... " + \
                                                "adding --pretend to options.\n") % access_desc
                                        if portage.secpass < 1 and not need_superuser:
                                                portage_group_warning()
                                else:
-                                       sys.stderr.write(("emerge: %s access would be " + \
+                                       sys.stderr.write(("emerge: %s access is " + \
                                                "required.\n\n") % access_desc)
                                        if portage.secpass < 1 and not need_superuser:
                                                portage_group_warning()
@@ -5587,21 +6129,26 @@ def emerge_main():
                if 1 == unmerge(settings, myopts, vartree, myaction, myfiles,
                        mtimedb["ldpath"]):
                        if "--pretend" not in myopts:
-                               post_emerge(settings, mtimedb, 0)
+                               post_emerge(trees, mtimedb, os.EX_OK)
 
        elif "depclean"==myaction:
                validate_ebuild_environment(trees)
                action_depclean(settings, trees, mtimedb["ldpath"],
                        myopts, spinner)
                if "--pretend" not in myopts:
-                       post_emerge(settings, mtimedb, 0)
+                       post_emerge(trees, mtimedb, os.EX_OK)
        # "update", "system", or just process files:
        else:
                validate_ebuild_environment(trees)
-               action_build(settings, trees, mtimedb,
+               if "--pretend" not in myopts:
+                       display_news_notification(trees)
+               retval = action_build(settings, trees, mtimedb,
                        myopts, myaction, myfiles, spinner)
                if "--pretend" not in myopts:
-                       post_emerge(settings, mtimedb, 0)
+                       post_emerge(trees, mtimedb, retval)
+               else:
+                       display_news_notification(trees)
+               return retval
 
 if __name__ == "__main__":
        retval = emerge_main()