For bug #185670, make sure package.use.{mask,force} are properly displayed.
[portage.git] / pym / emerge / __init__.py
index 05c6113569dc6b47010ab9026d6019dec92236a0..2246d9cf4047c8b53cdcd4e03f7559367dcd707b 100644 (file)
@@ -41,6 +41,8 @@ from portage.output import blue, bold, colorize, darkblue, darkgreen, darkred, g
 from portage.output import create_color_func
 good = create_color_func("GOOD")
 bad = create_color_func("BAD")
+# white looks bad on terminals with white background
+from portage.output import bold as white
 
 import portage.dep
 portage.dep._dep_check_strict = True
@@ -50,9 +52,10 @@ import portage.exception
 from portage.data import secpass
 from portage.util import normalize_path as normpath
 from portage.util import writemsg
+from portage.sets import InternalPackageSet
+from portage.sets.profiles import PackagesSystemSet as SystemSet
+from portage.sets.files import WorldSet
 
-if not hasattr(__builtins__, "set"):
-       from sets import Set as set
 from itertools import chain, izip
 from UserDict import DictMixin
 
@@ -135,7 +138,11 @@ def userquery(prompt, responses=None, colours=None):
        KeyboardInterrupt is converted to SystemExit to avoid tracebacks being
        printed."""
        if responses is None:
-               responses, colours = ["Yes", "No"], [green, red]
+               responses = ["Yes", "No"]
+               colours = [
+                       create_color_func("PROMPT_CHOICE_DEFAULT"),
+                       create_color_func("PROMPT_CHOICE_OTHER")
+               ]
        elif colours is None:
                colours=[bold]
        colours=(colours*len(responses))[:len(responses)]
@@ -338,28 +345,20 @@ def create_depgraph_params(myopts, myaction):
        # recurse:   go into the dependencies
        # deep:      go into the dependencies of already merged packages
        # empty:     pretend nothing is merged
-       myparams = ["recurse"]
-       add=[]
-       sub=[]
+       myparams = set(["recurse"])
        if "--update" in myopts or \
                "--newuse" in myopts or \
                "--reinstall" in myopts or \
                "--noreplace" in myopts or \
                myaction in ("system", "world"):
-               add.extend(["selective"])
+               myparams.add("selective")
        if "--emptytree" in myopts:
-               add.extend(["empty"])
-               sub.extend(["selective"])
+               myparams.add("empty")
+               myparams.discard("selective")
        if "--nodeps" in myopts:
-               sub.extend(["recurse"])
+               myparams.discard("recurse")
        if "--deep" in myopts:
-               add.extend(["deep"])
-       for x in add:
-               if (x not in myparams) and (x not in sub):
-                       myparams.append(x)
-       for x in sub:
-               if x in myparams:
-                       myparams.remove(x)
+               myparams.add("deep")
        return myparams
 
 # search functionality
@@ -568,8 +567,9 @@ 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("world", vardb.settings["ROOT"])
+       world_set.lock()
+       worldlist = list(world_set) # loads latest from disk
        mykey = portage.cpv_getkey(cpv)
        newworldlist = []
        for x in worldlist:
@@ -587,18 +587,80 @@ def clean_world(vardb, cpv):
                else:
                        #this doesn't match the package we're unmerging; keep it.
                        newworldlist.append(x)
-       
-       newworldlist.sort()
-       
-       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)+"\n")
 
-def genericdict(mylist):
-       mynewdict={}
-       for x in mylist:
-               mynewdict[portage.dep_getkey(x)]=x
-       return mynewdict
+       world_set.replace(newworldlist)
+       world_set.unlock()
+
+
+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("world", self.root)
+               self.sets["world"] = world_set
+               system_set = SystemSet("system", self.settings.profiles)
+               self.sets["system"] = system_set
+
+def create_world_atom(pkg_key, metadata, args_set, root_config):
+       """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
+       stored in world."""
+       arg_atom = args_set.findAtomForPackage(pkg_key, metadata)
+       cp = portage.dep_getkey(arg_atom)
+       new_world_atom = cp
+       sets = root_config.sets
+       portdb = root_config.trees["porttree"].dbapi
+       vardb = root_config.trees["vartree"].dbapi
+       available_slots = set(portdb.aux_get(cpv, ["SLOT"])[0] \
+               for cpv in portdb.match(cp))
+       slotted = len(available_slots) > 1 or \
+               (len(available_slots) == 1 and "0" not in available_slots)
+       if not slotted:
+               # check the vdb in case this is multislot
+               available_slots = set(vardb.aux_get(cpv, ["SLOT"])[0] \
+                       for cpv in vardb.match(cp))
+               slotted = len(available_slots) > 1 or \
+                       (len(available_slots) == 1 and "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:
+               # Unlike world atoms, system atoms are not greedy for slots, so they
+               # can't be safely excluded from world if they are slotted.
+               system_atom = sets["system"].findAtomForPackage(pkg_key, metadata)
+               if system_atom:
+                       if not portage.dep_getkey(system_atom).startswith("virtual/"):
+                               return None
+                       # System virtuals aren't safe to exclude from world since they can
+                       # match multiple old-style virtuals but only one of them will be
+                       # pulled in by update or depclean.
+                       providers = portdb.mysettings.getvirtuals().get(
+                               portage.dep_getkey(system_atom))
+                       if providers and len(providers) == 1 and providers[0] == cp:
+                               return None
+       return new_world_atom
 
 def filter_iuse_defaults(iuse):
        for flag in iuse:
@@ -630,13 +692,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__":
@@ -649,11 +713,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):
@@ -673,8 +741,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):
@@ -702,7 +772,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():
@@ -878,6 +948,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
@@ -934,10 +1011,14 @@ class depgraph(object):
                self.pkg_node_map = {}
                # Maps slot atom to digraph node for all nodes added to the graph.
                self._slot_node_map = {}
+               # Maps nodes to the reasons they were selected for reinstallation.
+               self._reinstall_nodes = {}
                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"):
@@ -950,6 +1031,7 @@ class depgraph(object):
                        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])
@@ -978,9 +1060,15 @@ class depgraph(object):
                self._parent_child_digraph = digraph()
                self.orderedkeys=[]
                self.outdatedpackages=[]
-               self._args_atoms = {}
-               self._args_virtual = None
-               self._args_nodes = set()
+               # contains all sets added to the graph
+               self._sets = {}
+               # contains atoms given as arguments
+               self._sets["args"] = InternalPackageSet()
+               # contains all atoms from all sets added to the graph, including
+               # atoms given as arguments
+               self._set_atoms = InternalPackageSet()
+               # contains all nodes pulled in by self._set_atoms
+               self._set_nodes = set()
                self.blocker_digraph = digraph()
                self.blocker_parents = {}
                self._unresolved_blocker_parents = {}
@@ -1045,19 +1133,22 @@ class depgraph(object):
                f.writer.flush()
 
        def _reinstall_for_flags(self, forced_flags,
-               orig_use, org_iuse, cur_use, cur_iuse):
+               orig_use, orig_iuse, cur_use, cur_iuse):
+               """Return a set of flags that trigger reinstallation, or None if there
+               are no such flags."""
                if "--newuse" in self.myopts:
-                       if org_iuse.symmetric_difference(
-                               cur_iuse).difference(forced_flags):
-                               return True
-                       elif org_iuse.intersection(orig_use) != \
-                               cur_iuse.intersection(cur_use):
-                               return True
-               elif "changed-use" in self.myopts.get("--reinstall","").split(","):
-                       if org_iuse.intersection(orig_use) != \
-                               cur_iuse.intersection(cur_use):
-                               return True
-               return False
+                       flags = orig_iuse.symmetric_difference(
+                               cur_iuse).difference(forced_flags)
+                       flags.update(orig_iuse.intersection(orig_use).symmetric_difference(
+                               cur_iuse.intersection(cur_use)))
+                       if flags:
+                               return flags
+               elif "changed-use" == self.myopts.get("--reinstall"):
+                       flags = orig_iuse.intersection(orig_use).symmetric_difference(
+                               cur_iuse.intersection(cur_use))
+                       if flags:
+                               return flags
+               return None
 
        def create(self, mybigkey, myparent=None, addme=1, myuse=None,
                priority=DepPriority(), rev_dep=False, arg=None):
@@ -1111,14 +1202,24 @@ class depgraph(object):
                # 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 not arg and myroot == self.target_root:
-                       arg = self._get_arg(mytype, mykey)
-
-               if myuse is None:
-                       self.pkgsettings[myroot].setcpv(mykey, mydb=portdb)
-                       myuse = self.pkgsettings[myroot]["USE"].split()
+                       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
 
+               reinstall_for_flags = None
                merging=1
                if mytype == "installed":
                        merging = 0
@@ -1132,6 +1233,7 @@ class depgraph(object):
                            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 \
+                               myroot == self.target_root and \
                                ("--newuse" in self.myopts or
                                "--reinstall" in self.myopts) and \
                                vardbapi.cpv_exists(mykey):
@@ -1140,12 +1242,12 @@ class depgraph(object):
                                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 self._reinstall_for_flags(
-                                       forced_flags, old_use, old_iuse, myuse, iuses):
+                               reinstall_for_flags = self._reinstall_for_flags(
+                                       forced_flags, old_use, old_iuse, myuse, iuses)
+                               if reinstall_for_flags:
                                        merging = 1
 
                if addme and merging == 1:
@@ -1155,16 +1257,15 @@ class depgraph(object):
                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)
@@ -1214,6 +1315,8 @@ class depgraph(object):
                                self._slot_node_map[myroot][slot_atom] = jbigkey
                                self.pkg_node_map[myroot][mykey] = jbigkey
                                self.useFlags[myroot][mykey] = myuse
+                               if reinstall_for_flags:
+                                       self._reinstall_nodes[jbigkey] = reinstall_for_flags
 
                        if rev_dep and myparent:
                                self.digraph.addnode(myparent, jbigkey,
@@ -1223,7 +1326,7 @@ class depgraph(object):
                                        priority=priority)
 
                if arg:
-                       self._args_nodes.add(jbigkey)
+                       self._set_nodes.add(jbigkey)
 
                # Do this even when addme is False (--onlydeps) so that the
                # parent/child relationship is always known in case
@@ -1248,9 +1351,8 @@ class depgraph(object):
 
                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:
@@ -1285,7 +1387,7 @@ class depgraph(object):
                                # 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:
@@ -1312,6 +1414,7 @@ class depgraph(object):
                "given a list of .tbz2s, .ebuilds and deps, create the appropriate depgraph and return a favorite list"
                myfavorites=[]
                myroot = self.target_root
+               vardb = self.trees[myroot]["vartree"].dbapi
                portdb = self.trees[myroot]["porttree"].dbapi
                bindb = self.trees[myroot]["bintree"].dbapi
                pkgsettings = self.pkgsettings[myroot]
@@ -1333,26 +1436,38 @@ class depgraph(object):
                                        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")
@@ -1364,10 +1479,9 @@ class depgraph(object):
                                        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,
@@ -1380,13 +1494,22 @@ class depgraph(object):
                                        if "--usepkg" in self.myopts:
                                                mykey = portage.dep_expand(x, mydb=bindb,
                                                        settings=pkgsettings)
-                                       if (mykey and not mykey.startswith("null/")) or \
-                                               "--usepkgonly" in self.myopts:
+                                       if "--usepkgonly" in self.myopts or \
+                                               (mykey and not portage.dep_getkey(mykey).startswith("null/")):
                                                arg_atoms.append((x, mykey))
                                                continue
 
-                                       mykey = portage.dep_expand(x,
-                                               mydb=portdb, settings=pkgsettings)
+                                       try:
+                                               mykey = portage.dep_expand(x,
+                                                       mydb=portdb, settings=pkgsettings)
+                                       except ValueError, e:
+                                               mykey = portage.dep_expand(x,
+                                                       mydb=vardb, settings=pkgsettings)
+                                               cp = portage.dep_getkey(mykey)
+                                               if cp.startswith("null/") or \
+                                                       cp not in e[0]:
+                                                       raise
+                                               del e
                                        arg_atoms.append((x, mykey))
                                except ValueError, errpkgs:
                                        print "\n\n!!! The short ebuild name \"" + x + "\" is ambiguous.  Please specify"
@@ -1445,11 +1568,17 @@ class depgraph(object):
                                                        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."""
+               args_set = self._sets["args"]
                for myarg, myatom in arg_atoms:
-                       self._args_atoms.setdefault(
-                               portage.dep_getkey(myatom), []).append(myatom)
+                       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)
@@ -1476,10 +1605,6 @@ class depgraph(object):
 
                                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:
@@ -1556,11 +1681,8 @@ class depgraph(object):
                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.
-                       
-                       cp = portage.dep_getkey(depstring)
-                       if cp in self._args_atoms and \
-                               portage.match_to_list(depstring, self._args_atoms[cp]):
-                               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
@@ -1573,9 +1695,17 @@ class depgraph(object):
                        if p_status == "merge":
                                # Update old-style virtuals if this package provides any.
                                # These are needed for dep_virtual calls inside dep_check.
-                               p_db = self.trees[p_root][self.pkg_tree_map[p_type]].dbapi
+                               p_db = self.mydbapi[p_root] # contains cached metadata
+                               if myparent in self._slot_collision_nodes:
+                                       # The metadata isn't cached due to the slot collision.
+                                       p_db = self.trees[p_root][self.pkg_tree_map[p_type]].dbapi
                                try:
                                        self.pkgsettings[p_root].setinst(p_key, p_db)
+                                       # For consistency, also update the global virtuals.
+                                       settings = self.roots[p_root].settings
+                                       settings.unlock()
+                                       settings.setinst(p_key, p_db)
+                                       settings.lock()
                                except portage.exception.InvalidDependString, e:
                                        provide = p_db.aux_get(p_key, ["PROVIDE"])[0]
                                        show_invalid_depstring_notice(myparent, provide, str(e))
@@ -2052,6 +2182,9 @@ class depgraph(object):
                        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=[]
@@ -2066,32 +2199,34 @@ class depgraph(object):
                                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
@@ -2110,30 +2245,65 @@ class depgraph(object):
                                                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():
@@ -2194,6 +2364,10 @@ class depgraph(object):
                                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)
@@ -2231,15 +2405,15 @@ class depgraph(object):
                        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:
+               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
                                        continue
@@ -2264,9 +2438,12 @@ class depgraph(object):
                        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])
@@ -2309,8 +2486,7 @@ class depgraph(object):
                mylist = newlist
 
                for myatom in mylist:
-                       self._args_atoms.setdefault(
-                               portage.dep_getkey(myatom), []).append(myatom)
+                       self._set_atoms.add(myatom)
 
                missing_atoms = []
                for mydep in mylist:
@@ -2337,38 +2513,11 @@ class depgraph(object):
 
                return 1
 
-       def _get_arg(self, pkg_type, cpv):
-               """Return the best match for a given package from the arguments, or
-               None if there are no matches.  This matches virtual arguments against
-               the current virtual settings."""
-               mydbapi = self.trees[self.target_root][self.pkg_tree_map[pkg_type]].dbapi
-               cpv_slot = "%s:%s" % (cpv, mydbapi.aux_get(cpv, ["SLOT"])[0])
-               cp = portage.dep_getkey(cpv)
-               atoms = self._args_atoms.get(cp)
-               if atoms:
-                       best_match = portage.best_match_to_list(cpv_slot, atoms)
-                       if best_match:
-                               return best_match
-               if self._args_virtual is None:
-                       self._args_virtual = {}
-                       for cp in self._args_atoms:
-                               if cp.startswith("virtual/"):
-                                       self._args_virtual[cp] = self._args_atoms[cp]
-               virts = self.pkgsettings[self.target_root].getvirtuals()
-               for cp, atoms in self._args_virtual.iteritems():
-                       choices = virts.get(cp)
-                       if choices:
-                               for choice in choices:
-                                       transformed_atoms = [atom.replace(cp, choice) 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 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)
+               favorites_set = InternalPackageSet(favorites)
                changelogs=[]
                p=[]
                blockers = []
@@ -2440,12 +2589,7 @@ class depgraph(object):
                                        ret = '%s="%s" ' % (name, ret)
                                return ret
 
-               if verbosity == 3:
-                       # FIXME: account for the possibility of different overlays in
-                       # /etc/make.conf vs. ${PORTAGE_CONFIGROOT}/etc/make.conf
-                       overlays = self.settings["PORTDIR_OVERLAY"].split()
-                       overlays_real = [os.path.realpath(t) \
-                               for t in self.settings["PORTDIR_OVERLAY"].split()]
+               repo_display = RepoDisplay(self.roots)
 
                tree_nodes = []
                display_list = []
@@ -2472,7 +2616,12 @@ class depgraph(object):
                                        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
@@ -2529,14 +2678,13 @@ class depgraph(object):
 
                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=[]
-               worldlist = set(getlist(self.settings, "world"))
 
                for mylist_index in xrange(len(mylist)):
                        x, depth, ordered = mylist[mylist_index]
+                       pkg_node = tuple(x)
                        pkg_type = x[0]
                        myroot = x[1]
                        pkg_key = x[2]
@@ -2568,23 +2716,27 @@ class depgraph(object):
                                        addl += bad(" (is blocking %s)") % block_parents
                                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 \
-                                               not vartree.dbapi.cpv_exists(pkg_key):
-                                               """An ebuild "merge" node or a --onlydeps "nomerge"
-                                               node."""
-                                               binary_package = False
-                                               pkgsettings.setcpv(pkg_key, mydb=portdb)
-                                               if pkg_key not in self.useFlags[myroot]:
-                                                       self.useFlags[myroot][pkg_key] = \
-                                                               pkgsettings["USE"].split()
-                                       else:
-                                               # An ebuild "nomerge" node, so USE come from the vardb.
-                                               mydbapi = vartree.dbapi
+                               if pkg_node in self._slot_collision_nodes or \
+                                       (pkg_status == "nomerge" and pkg_type != "installed"):
+                                       # The metadata isn't cached due to a slot collision or
+                                       # --onlydeps.
+                                       mydbapi = self.trees[myroot][self.pkg_tree_map[pkg_type]].dbapi
+                               else:
+                                       mydbapi = self.mydbapi[myroot] # contains cached metadata
+                               metadata = dict(izip(self._mydbapi_keys,
+                                       mydbapi.aux_get(pkg_key, self._mydbapi_keys)))
+                               ebuild_path = None
+                               repo_name = metadata["repository"]
+                               if pkg_type == "ebuild":
+                                       ebuild_path = portdb.findname(pkg_key)
+                                       if not ebuild_path: # shouldn't happen
+                                               raise portage.exception.PackageNotFound(pkg_key)
+                                       repo_path_real = os.path.dirname(os.path.dirname(
+                                               os.path.dirname(ebuild_path)))
+                               else:
+                                       repo_path_real = portdb.getRepositoryPath(repo_name)
                                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."""
@@ -2600,7 +2752,7 @@ class depgraph(object):
                                                restrict = mydbapi.aux_get(pkg_key, ["RESTRICT"])[0]
                                                show_invalid_depstring_notice(x, restrict, str(e))
                                                del e
-                                               sys.exit(1)
+                                               return 1
                                        restrict = []
                                if "ebuild" == pkg_type and x[3] != "nomerge" and \
                                        "fetch" in restrict:
@@ -2667,9 +2819,9 @@ class depgraph(object):
                                                mydbapi.aux_get(pkg_key, ["IUSE"])[0].split()))
 
                                        forced_flags = set()
-                                       if not binary_package:
-                                               forced_flags.update(pkgsettings.useforce)
-                                               forced_flags.update(pkgsettings.usemask)
+                                       pkgsettings.setcpv(pkg_key) # for package.use.{mask,force}
+                                       forced_flags.update(pkgsettings.useforce)
+                                       forced_flags.update(pkgsettings.usemask)
 
                                        cur_iuse = portage.unique_array(cur_iuse)
                                        cur_iuse.sort()
@@ -2702,7 +2854,8 @@ class depgraph(object):
                                        use_expand_hidden = \
                                                pkgsettings["USE_EXPAND_HIDDEN"].lower().split()
 
-                                       def map_to_use_expand(myvals, forcedFlags=False):
+                                       def map_to_use_expand(myvals, forcedFlags=False,
+                                               removeHidden=True):
                                                ret = {}
                                                forced = {}
                                                for exp in use_expand:
@@ -2717,13 +2870,32 @@ class depgraph(object):
                                                ret["USE"] = myvals
                                                forced["USE"] = [val for val in myvals \
                                                        if val in forced_flags]
-                                               for exp in use_expand_hidden:
-                                                       if exp in ret:
-                                                               del ret[exp]
+                                               if removeHidden:
+                                                       for exp in use_expand_hidden:
+                                                               ret.pop(exp, None)
                                                if forcedFlags:
                                                        return ret, forced
                                                return ret
 
+                                       # Prevent USE_EXPAND_HIDDEN flags from being hidden if they
+                                       # are the only thing that triggered reinstallation.
+                                       reinst_flags_map = None
+                                       reinstall_for_flags = self._reinstall_nodes.get(pkg_node)
+                                       if reinstall_for_flags:
+                                               reinst_flags_map = map_to_use_expand(
+                                                       list(reinstall_for_flags), removeHidden=False)
+                                               if reinst_flags_map["USE"]:
+                                                       reinst_flags_map = None
+                                               else:
+                                                       for k in reinst_flags_map.keys():
+                                                               if not reinst_flags_map[k]:
+                                                                       del reinst_flags_map[k]
+                                       if reinst_flags_map and \
+                                               not set(reinst_flags_map).difference(
+                                               use_expand_hidden):
+                                               use_expand_hidden = set(use_expand_hidden).difference(
+                                                       reinst_flags_map)
+
                                        cur_iuse_map, iuse_forced = \
                                                map_to_use_expand(cur_iuse, forcedFlags=True)
                                        cur_use_map = map_to_use_expand(cur_use)
@@ -2753,7 +2925,7 @@ class depgraph(object):
                                                        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:
@@ -2765,17 +2937,31 @@ class depgraph(object):
                                                verboseadd+=format_size(mysize)+" "
 
                                        # overlay verbose
-                                       # XXX: Invalid binaries have caused tracebacks here. 'if file_name'
-                                       # 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
-                                               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
+                                       # assign index for a previous version in the same slot
+                                       has_previous = False
+                                       repo_name_prev = None
+                                       slot_atom = "%s:%s" % (portage.dep_getkey(pkg_key),
+                                               metadata["SLOT"])
+                                       slot_matches = vardb.match(slot_atom)
+                                       if slot_matches:
+                                               has_previous = True
+                                               repo_name_prev = vardb.aux_get(slot_matches[0],
+                                                       ["repository"])[0]
+
+                                       # now use the data to generate output
+                                       repoadd = None
+                                       if pkg_status == "nomerge" or not has_previous:
+                                               repoadd = repo_display.repoStr(repo_path_real)
                                        else:
-                                               verboseadd += "[No ebuild?]"
+                                               repo_path_prev = None
+                                               if repo_name_prev:
+                                                       repo_path_prev = portdb.getRepositoryPath(
+                                                               repo_name_prev)
+                                               repoadd = "%s=>%s" % (
+                                                       repo_display.repoStr(repo_path_prev),
+                                                       repo_display.repoStr(repo_path_real))
+                                       if repoadd:
+                                               verboseadd += teal("[%s]" % repoadd)
 
                                xs = list(portage.pkgsplit(x[2]))
                                if xs[2]=="r0":
@@ -2805,16 +2991,37 @@ class depgraph(object):
                                        myoldbest=blue("["+myoldbest+"]")
 
                                pkg_cp = xs[0]
-                               pkg_world = pkg_cp in worldlist and myroot == self.target_root
+                               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):
+                                                       pkg_world = True
+                               except portage.exception.InvalidDependString:
+                                       # This is reported elsewhere if relevant.
+                                       pass
 
                                def pkgprint(pkg):
                                        if pkg_merge:
-                                               if pkg_world:
+                                               if pkg_system:
+                                                       return colorize("PKG_MERGE_SYSTEM", pkg)
+                                               elif pkg_world:
                                                        return colorize("PKG_MERGE_WORLD", pkg)
                                                else:
                                                        return colorize("PKG_MERGE", pkg)
                                        else:
-                                               if pkg_world:
+                                               if pkg_system:
+                                                       return colorize("PKG_NOMERGE_SYSTEM", pkg)
+                                               elif pkg_world:
                                                        return colorize("PKG_NOMERGE_WORLD", pkg)
                                                else:
                                                        return colorize("PKG_NOMERGE", pkg)
@@ -2892,12 +3099,8 @@ class depgraph(object):
                if verbosity == 3:
                        print
                        print counters
-                       if overlays and display_overlays:
-                               print "Portage overlays:"
-                               y=0
-                               for x in overlays:
-                                       y=y+1
-                                       print " "+teal("["+str(y)+"]"),x
+                       if p:
+                               sys.stdout.write(str(repo_display))
 
                if "--changelog" in self.myopts:
                        print
@@ -2906,6 +3109,14 @@ class depgraph(object):
                                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:
@@ -2914,11 +3125,26 @@ class depgraph(object):
                        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):
@@ -2972,8 +3198,132 @@ class depgraph(object):
                        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() # maybe it's changed on disk
+               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)
+                               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
+               world_set.unlock()
+
+       def loadResumeCommand(self, resume_data):
+               """
+               Add a resume command to the graph and validate it in the process.  This
+               will raise a PackageNotFound exception if a package is not available.
+               """
+               self._sets["args"].update(resume_data.get("favorites", []))
+               mergelist = resume_data.get("mergelist", [])
+               fakedb = self.mydbapi
+               trees = self.trees
+               for x in mergelist:
+                       if len(x) != 4:
+                               continue
+                       pkg_type, myroot, pkg_key, action = x
+                       if pkg_type not in self.pkg_tree_map:
+                               continue
+                       if action != "merge":
+                               continue
+                       mydb = trees[myroot][self.pkg_tree_map[pkg_type]].dbapi
+                       try:
+                               metadata = dict(izip(self._mydbapi_keys,
+                                       mydb.aux_get(pkg_key, self._mydbapi_keys)))
+                       except KeyError:
+                               # It does no exist or it is corrupt.
+                               raise portage.exception.PackageNotFound(pkg_key)
+                       fakedb[myroot].cpv_inject(pkg_key, metadata=metadata)
+                       if pkg_type == "ebuild":
+                               pkgsettings = self.pkgsettings[myroot]
+                               pkgsettings.setcpv(pkg_key, mydb=fakedb[myroot])
+                               fakedb[myroot].aux_update(pkg_key, {"USE":pkgsettings["USE"]})
+                       self.spinner.update()
+
+class RepoDisplay(object):
+       def __init__(self, roots):
+               self._shown_repos = {}
+               self._unknown_repo = False
+               repo_paths = set()
+               for root_config in roots.itervalues():
+                       portdir = root_config.settings.get("PORTDIR")
+                       if portdir:
+                               repo_paths.add(portdir)
+                       overlays = root_config.settings.get("PORTDIR_OVERLAY")
+                       if overlays:
+                               repo_paths.update(overlays.split())
+               repo_paths = list(repo_paths)
+               self._repo_paths = repo_paths
+               self._repo_paths_real = [ os.path.realpath(repo_path) \
+                       for repo_path in repo_paths ]
+
+               # pre-allocate index for PORTDIR so that it always has index 0.
+               for root_config in roots.itervalues():
+                       portdb = root_config.trees["porttree"].dbapi
+                       portdir = portdb.porttree_root
+                       if portdir:
+                               self.repoStr(portdir)
+
+       def repoStr(self, repo_path_real):
+               real_index = -1
+               if repo_path_real:
+                       real_index = self._repo_paths_real.index(repo_path_real)
+               if real_index == -1:
+                       s = "?"
+                       self._unknown_repo = True
+               else:
+                       shown_repos = self._shown_repos
+                       repo_paths = self._repo_paths
+                       repo_path = repo_paths[real_index]
+                       index = shown_repos.get(repo_path)
+                       if index is None:
+                               index = len(shown_repos)
+                               shown_repos[repo_path] = index
+                       s = str(index)
+               return s
+
+       def __str__(self):
+               output = []
+               shown_repos = self._shown_repos
+               unknown_repo = self._unknown_repo
+               if shown_repos or self._unknown_repo:
+                       output.append("Portage tree and overlays:\n")
+               show_repo_paths = list(shown_repos)
+               for repo_path, repo_index in shown_repos.iteritems():
+                       show_repo_paths[repo_index] = repo_path
+               if show_repo_paths:
+                       for index, repo_path in enumerate(show_repo_paths):
+                               output.append(" "+teal("["+str(index)+"]")+" %s\n" % repo_path)
+               if unknown_repo:
+                       output.append(" "+teal("[?]") + \
+                               " indicates that the source repository could not be determined\n")
+               return "".join(output)
 
 class PackageCounters(object):
 
@@ -3048,12 +3398,15 @@ class MergeTask(object):
                if self.target_root != "/":
                        self.pkgsettings["/"] = \
                                portage.config(clone=trees["/"]["vartree"].settings)
+               self.curval = 0
 
        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
@@ -3080,7 +3433,6 @@ class MergeTask(object):
                                del mtimedb["resume"]["mergelist"][0]
                                del mylist[0]
                                mtimedb.commit()
-                       validate_merge_list(self.trees, mylist)
                        mymergelist = mylist
 
                # Verify all the manifests now so that the user is notified of failure
@@ -3111,35 +3463,12 @@ 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"))
+               root_config = RootConfig(self.trees[self.target_root])
+               system_set = root_config.sets["system"]
+               args_set = InternalPackageSet(favorites)
+               world_set = root_config.sets["world"]
                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(sorted(myfavdict.values())) + "\n")
-
+                       mymergelist = mylist
                        mtimedb["resume"]["mergelist"]=mymergelist[:]
                        mtimedb.commit()
 
@@ -3184,9 +3513,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
@@ -3194,6 +3527,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)
@@ -3213,7 +3562,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:
@@ -3235,6 +3584,7 @@ class MergeTask(object):
                                                print "!!! Fetch for",y,"failed, continuing..."
                                                print
                                                failed_fetches.append(pkg_key)
+                                       self.curval += 1
                                        continue
 
                                portage.doebuild_environment(y, "setup", myroot,
@@ -3286,7 +3636,7 @@ class MergeTask(object):
                                                del pkgsettings["PORTAGE_BINPKG_TMPFILE"]
                                                if retval != os.EX_OK or \
                                                        "--buildpkgonly" in self.myopts:
-                                                       elog_process(pkg_key, pkgsettings)
+                                                       elog_process(pkg_key, pkgsettings, phasefilter=filter_mergephases)
                                                if retval != os.EX_OK:
                                                        return retval
                                                bintree = self.trees[myroot]["bintree"]
@@ -3386,6 +3736,7 @@ class MergeTask(object):
 
                                if "--fetchonly" in self.myopts or \
                                        "--fetch-all-uri" in self.myopts:
+                                       self.curval += 1
                                        continue
 
                                short_msg = "emerge: ("+str(mergecount)+" of "+str(len(mymergelist))+") "+x[pkgindex]+" Merge Binary"
@@ -3401,24 +3752,21 @@ 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() # maybe it's changed on disk
+                                       myfavkey = create_world_atom(pkg_key, metadata,
+                                               args_set, root_config)
+                                       if 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(sorted(myfavdict.values()))+"\n")
+                                               world_set.add(myfavkey)
+                                       world_set.unlock()
 
                                if "--pretend" not in self.myopts and \
                                        "--fetchonly" not in self.myopts and \
@@ -3493,6 +3841,7 @@ class MergeTask(object):
                        # in the event that portage is not allowed to exit normally
                        # due to power failure, SIGKILL, etc...
                        mtimedb.commit()
+                       self.curval += 1
 
                if "--pretend" not in self.myopts:
                        emergelog(xterm_titles, " *** Finished. Cleaning up...")
@@ -3733,9 +4082,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)
@@ -3750,7 +4103,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'") + \
@@ -4045,20 +4401,6 @@ def is_valid_package_atom(x):
                testatom = x
        return portage.isvalidatom(testatom)
 
-def validate_merge_list(trees, mergelist):
-       """Validate the list to make sure all the packages are still available.
-       This is needed for --resume."""
-       for (pkg_type, myroot, pkg_key, action) in mergelist:
-               if pkg_type == "binary" and \
-                       not trees[myroot]["bintree"].dbapi.match("="+pkg_key) or \
-                       pkg_type == "ebuild" and \
-                       not trees[myroot]["porttree"].dbapi.xmatch(
-                       "match-all", "="+pkg_key):
-                       print red("!!! Error: The resume list contains packages that are no longer")
-                       print red("!!!        available to be emerged. Please restart/continue")
-                       print red("!!!        the merge operation manually.")
-                       sys.exit(1)
-
 def show_blocker_docs_link():
        print
        print "For more information about " + bad("Blocked Packages") + ", please refer to the following"
@@ -4514,9 +4856,8 @@ 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)
@@ -4831,7 +5172,12 @@ def action_info(settings, trees, myopts, myfiles):
 
                # Loop through each package
                # Only print settings if they differ from global settings
-               header_printed = False
+               header_title = "Package Settings"
+               print header_width * "="
+               print header_title.rjust(int(header_width/2 + len(header_title)/2))
+               print header_width * "="
+               from portage.output import EOutput
+               out = EOutput()
                for pkg in mypkgs:
                        # Get all package specific variables
                        auxvalues = vardb.aux_get(pkg, auxkeys)
@@ -4861,16 +5207,6 @@ def action_info(settings, trees, myopts, myfiles):
                        # If a difference was found, print the info for
                        # this package.
                        if diff_values:
-
-                               # If we have not yet printed the header, 
-                               # print it now
-                               if not header_printed:
-                                       header_title = "Package Settings"
-                                       print header_width * "="
-                                       print header_title.rjust(int(header_width/2 + len(header_title)/2))
-                                       print header_width * "="
-                                       header_printed = True
-
                                # Print package info
                                print "%s was built with the following:" % pkg
                                for myvar in mydesiredvars + ["USE"]:
@@ -4879,6 +5215,15 @@ def action_info(settings, trees, myopts, myfiles):
                                                mylist.sort()
                                                print "%s=\"%s\"" % (myvar, " ".join(mylist))
                                print
+                       print ">>> Attempting to run pkg_info() for '%s'" % pkg
+                       ebuildpath = vardb.findname(pkg)
+                       if not ebuildpath or not os.path.exists(ebuildpath):
+                               out.ewarn("No ebuild found for '%s'" % pkg)
+                               continue
+                       portage.doebuild(ebuildpath, "info", pkgsettings["ROOT"],
+                               pkgsettings, debug=(settings.get("PORTAGE_DEBUG", "") == 1),
+                               mydbapi=trees[settings["ROOT"]]["vartree"].dbapi,
+                               tree="vartree")
 
 def action_search(settings, portdb, vartree, myopts, myfiles, spinner):
        if not myfiles:
@@ -4900,26 +5245,30 @@ def action_depclean(settings, trees, ldpath_mtimes,
        # Kill packages that aren't explicitly merged or are required as a
        # dependency of another package. World file is explicit.
 
-       warn_prefix = colorize("BAD", "*** WARNING ***  ")
-       print
-       print warn_prefix + "Depclean may break link level dependencies.  Thus, it is"
-       print warn_prefix + "recommended to use a tool such as " + good("`revdep-rebuild`") + " (from"
-       print warn_prefix + "app-portage/gentoolkit) in order to detect such breakage."
-       print warn_prefix
-       print warn_prefix + "Also study the list of packages to be cleaned for any obvious"
-       print warn_prefix + "mistakes. Packages that are part of the world set will always"
-       print warn_prefix + "be kept.  They can be manually added to this set with"
-       print warn_prefix + good("`emerge --noreplace <atom>`") + ".  Packages that are listed in"
-       print warn_prefix + "package.provided (see portage(5)) will be removed by"
-       print warn_prefix + "depclean, even if they are part of the world set."
-       print warn_prefix
-       print warn_prefix + "As a safety measure, depclean will not remove any packages"
-       print warn_prefix + "unless *all* required dependencies have been resolved.  As a"
-       print warn_prefix + "consequence, it is often necessary to run "
-       print warn_prefix + good("`emerge --update --newuse --deep world`") + " prior to depclean."
+       msg = []
+       msg.append("Depclean may break link level dependencies.  Thus, it is\n")
+       msg.append("recommended to use a tool such as " + good("`revdep-rebuild`") + " (from\n")
+       msg.append("app-portage/gentoolkit) in order to detect such breakage.\n")
+       msg.append("\n")
+       msg.append("Also study the list of packages to be cleaned for any obvious\n")
+       msg.append("mistakes. Packages that are part of the world set will always\n")
+       msg.append("be kept.  They can be manually added to this set with\n")
+       msg.append(good("`emerge --noreplace <atom>`") + ".  Packages that are listed in\n")
+       msg.append("package.provided (see portage(5)) will be removed by\n")
+       msg.append("depclean, even if they are part of the world set.\n")
+       msg.append("\n")
+       msg.append("As a safety measure, depclean will not remove any packages\n")
+       msg.append("unless *all* required dependencies have been resolved.  As a\n")
+       msg.append("consequence, it is often necessary to run\n")
+       msg.append(good("`emerge --update --newuse --deep world`") + " prior to depclean.\n")
+
+       portage.writemsg_stdout("\n")
+       for x in msg:
+               portage.writemsg_stdout(colorize("BAD", "*** WARNING ***  ") + x)
 
        xterm_titles = "notitles" not in settings.features
        myroot = settings["ROOT"]
+       portdb = trees[myroot]["porttree"].dbapi
        dep_check_trees = {}
        dep_check_trees[myroot] = {}
        dep_check_trees[myroot]["vartree"] = \
@@ -4927,10 +5276,10 @@ 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("system", settings.profiles)
+       syslist = list(system_set)
+       world_set = WorldSet("world", myroot)
+       worldlist = list(world_set)
        fakedb = portage.fakedbapi(settings=settings)
        myvarlist = vardb.cpv_all()
 
@@ -4959,6 +5308,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()
@@ -4967,11 +5317,17 @@ 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.
+                       visible_in_portdb = [cpv for cpv in pkgs if portdb.match("="+cpv)]
+                       if visible_in_portdb:
+                               # For consistency with the update algorithm, keep the highest
+                               # visible version and prune any versions that are either masked
+                               # or no longer exist in the portage tree.
+                               pkgs = visible_in_portdb
                        pkgs = [portage.best(pkgs)]
                for pkg in pkgs:
                        if fakedb.cpv_exists(pkg):
@@ -5056,6 +5412,12 @@ def action_build(settings, trees, mtimedb,
        myopts, myaction, myfiles, spinner):
        ldpath_mtimes = mtimedb["ldpath"]
        favorites=[]
+       merge_count = 0
+       pretend = "--pretend" in myopts
+       fetchonly = "--fetchonly" in myopts or "--fetch-all-uri" in myopts
+       if pretend or fetchonly:
+               # make the mtimedb readonly
+               mtimedb.filename = None
        if "--quiet" not in myopts and \
                ("--pretend" in myopts or "--ask" in myopts or \
                "--tree" in myopts or "--verbose" in myopts):
@@ -5090,16 +5452,6 @@ def action_build(settings, trees, mtimedb,
                        mtimedb["resume"] = mtimedb["resume_backup"]
                        del mtimedb["resume_backup"]
                        mtimedb.commit()
-               # XXX: "myopts" is a list for backward compatibility.
-               myresumeopts = dict([(k,True) for k in mtimedb["resume"]["myopts"]])
-
-               for opt in ("--skipfirst", "--ask", "--tree"):
-                       myresumeopts.pop(opt, None)
-
-               for myopt, myarg in myopts.iteritems():
-                       if myopt not in myresumeopts:
-                               myresumeopts[myopt] = myarg
-               myopts=myresumeopts
 
                # Adjust config according to options of the command being resumed.
                for myroot in trees:
@@ -5109,17 +5461,38 @@ def action_build(settings, trees, mtimedb,
                        mysettings.lock()
                        del myroot, mysettings
 
-               myparams = create_depgraph_params(myopts, myaction)
-               if "--quiet" not in myopts and "--nodeps" not in myopts:
+               # "myopts" is a list for backward compatibility.
+               resume_opts = mtimedb["resume"].get("myopts", [])
+               if isinstance(resume_opts, list):
+                       resume_opts = dict((k,True) for k in resume_opts)
+               for opt in ("--skipfirst", "--ask", "--tree"):
+                       resume_opts.pop(opt, None)
+               myopts.update(resume_opts)
+               show_spinner = "--quiet" not in myopts and "--nodeps" not in myopts
+               if not show_spinner:
+                       spinner.update = spinner.update_quiet
+               if show_spinner:
                        print "Calculating dependencies  ",
+               myparams = create_depgraph_params(myopts, myaction)
                mydepgraph = depgraph(settings, trees,
                        myopts, myparams, spinner)
-               if "--quiet" not in myopts and "--nodeps" not in myopts:
+               try:
+                       mydepgraph.loadResumeCommand(mtimedb["resume"])
+               except portage.exception.PackageNotFound:
+                       if show_spinner:
+                               print
+                       from portage.output import EOutput
+                       out = EOutput()
+                       out.eerror("Error: The resume list contains packages that are no longer")
+                       out.eerror("       available to be emerged. Please restart/continue")
+                       out.eerror("       the merge operation manually.")
+                       return 1
+               if show_spinner:
                        print "\b\b... done!"
        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"]:
@@ -5129,7 +5502,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:
@@ -5141,9 +5514,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!"
 
@@ -5154,25 +5527,30 @@ 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 \
                "--verbose" in myopts) and \
                not ("--quiet" in myopts and "--ask" not in myopts):
                if "--resume" in myopts:
-                       validate_merge_list(trees, mtimedb["resume"]["mergelist"])
                        mymergelist = mtimedb["resume"]["mergelist"]
                        if "--skipfirst" in myopts:
                                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":
@@ -5183,7 +5561,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
@@ -5196,7 +5574,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:
@@ -5206,34 +5584,39 @@ 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)
 
        if ("--pretend" in myopts) and not ("--fetchonly" in myopts or "--fetch-all-uri" in myopts):
                if ("--resume" in myopts):
-                       validate_merge_list(trees, mtimedb["resume"]["mergelist"])
                        mymergelist = mtimedb["resume"]["mergelist"]
                        if "--skipfirst" in myopts:
                                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)))
+                       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"
-                                       sys.exit(1)
+                                       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"]
@@ -5246,8 +5629,7 @@ def action_build(settings, trees, mtimedb,
                        del mydepgraph
                        retval = mergetask.merge(
                                mtimedb["resume"]["mergelist"], favorites, mtimedb)
-                       if retval != os.EX_OK:
-                               sys.exit(retval)
+                       merge_count = mergetask.curval
                else:
                        if "resume" in mtimedb and \
                        "mergelist" in mtimedb["resume"] and \
@@ -5280,23 +5662,28 @@ 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)
+                       merge_count = mergetask.curval
+
+               if retval == os.EX_OK and not (pretend or fetchonly):
+                       mtimedb.pop("resume", None)
+                       if "yes" == settings.get("AUTOCLEAN"):
+                               portage.writemsg_stdout(">>> Auto-cleaning packages...\n")
+                               vartree = trees[settings["ROOT"]]["vartree"]
+                               unmerge(settings, myopts, vartree, "clean", ["world"],
+                                       ldpath_mtimes, autoclean=1)
+                       else:
+                               portage.writemsg_stdout(colorize("WARN", "WARNING:")
+                                       + " AUTOCLEAN is disabled.  This can cause serious"
+                                       + " problems due to overlapping packages.\n")
 
-               if mtimedb.has_key("resume"):
-                       del mtimedb["resume"]
-               if settings["AUTOCLEAN"] and "yes"==settings["AUTOCLEAN"]:
-                       portage.writemsg_stdout(">>> Auto-cleaning packages...\n")
-                       vartree = trees[settings["ROOT"]]["vartree"]
-                       unmerge(settings, myopts, vartree, "clean", ["world"],
-                               ldpath_mtimes, autoclean=1)
-               else:
-                       portage.writemsg_stdout(colorize("WARN", "WARNING:")
-                               + " AUTOCLEAN is disabled.  This can cause serious"
-                               + " problems due to overlapping packages.\n")
+               if merge_count and not (pretend or fetchonly):
+                       post_emerge(trees, mtimedb, retval)
+               return retval
 
 def multiple_actions(action1, action2):
        sys.stderr.write("\n!!! Multiple actions requested... Please choose one only.\n")
@@ -5327,7 +5714,9 @@ def parse_opts(tmpcmdline, silent=False):
                        "choices":("y", "n")
                },
                "--reinstall": {
-                       "help":"specify conditions to trigger package reinstallation"
+                       "help":"specify conditions to trigger package reinstallation",
+                       "type":"choice",
+                       "choices":["changed-use"]
                }
        }
 
@@ -5602,7 +5991,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
 
@@ -5685,10 +6073,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
@@ -5818,12 +6202,11 @@ def emerge_main():
                validate_ebuild_environment(trees)
                if "--pretend" not in myopts:
                        display_news_notification(trees)
-               action_build(settings, trees, mtimedb,
+               retval = action_build(settings, trees, mtimedb,
                        myopts, myaction, myfiles, spinner)
-               if "--pretend" not in myopts:
-                       post_emerge(trees, mtimedb, os.EX_OK)
-               else:
+               if "--pretend" in myopts:
                        display_news_notification(trees)
+               return retval
 
 if __name__ == "__main__":
        retval = emerge_main()