Add a new --deselect action which removes atoms from the world file. This
authorZac Medico <zmedico@gentoo.org>
Thu, 30 Apr 2009 07:19:02 +0000 (07:19 -0000)
committerZac Medico <zmedico@gentoo.org>
Thu, 30 Apr 2009 07:19:02 +0000 (07:19 -0000)
action is implied by uninstall actions, including --depclean, --prune and
--unmerge. Use --deselect=n in order to prevent uninstall actions from
removing atoms from the world file. This solves bug #259994 and bug #265206.
(trunk r13363)

svn path=/main/branches/2.1.6/; revision=13519

man/emerge.1
pym/_emerge/__init__.py
pym/_emerge/help.py
pym/portage/dep.py

index 8b2e7c16760ecdf23858c7177f52e27d57ebe9c3..a085f8a253285b212335e02f7d4a432670ea5dff 100644 (file)
@@ -130,6 +130,13 @@ unmerge matched packages that have no reverse dependencies. Use
 \fB\-\-depclean\fR together with \fB\-\-verbose\fR to show reverse
 dependencies.
 .TP
+.BR "\-\-deselect[=n]"
+Remove atoms from the world file. This action is implied
+by uninstall actions, including \fB-\-depclean\fR,
+\fB-\-prune\fR and \fB-\-unmerge\fR. Use \fB-\-deselect=n\fR
+in order to prevent uninstall actions from removing
+atoms from the world file.
+.TP
 .BR "\-\-help " (\fB\-h\fR)
 Displays help information for emerge.  Adding one of the additional
 arguments listed above will give you more specific help information
index 03cef509ebf9ad75c27ce00794aaa4d10359cdd0..3ba88eccc158148c60f0e546d0d0c0fcbebbf5d1 100644 (file)
@@ -11837,6 +11837,8 @@ def unmerge(root_config, myopts, unmerge_action,
        clean_world=1, clean_delay=1, ordered=0, raise_on_error=0,
        scheduler=None, writemsg_level=portage.util.writemsg_level):
 
+       if clean_world:
+               clean_world = myopts.get('--deselect') != 'n'
        quiet = "--quiet" in myopts
        settings = root_config.settings
        sets = root_config.sets
@@ -13873,12 +13875,50 @@ def action_uninstall(settings, trees, ldpath_mtimes,
                unmerge(trees[settings["ROOT"]]['root_config'], opts, action,
                        valid_atoms, ldpath_mtimes, ordered=ordered)
                rval = os.EX_OK
+       elif action == 'deselect':
+               rval = action_deselect(settings, trees, opts, valid_atoms)
        else:
                rval = action_depclean(settings, trees, ldpath_mtimes,
                        opts, action, valid_atoms, spinner)
 
        return rval
 
+def action_deselect(settings, trees, opts, atoms):
+       world_set = trees[settings['ROOT']]['root_config'].sets['world']
+       if not hasattr(world_set, 'update'):
+               writemsg_level("World set does not appear to be mutable.\n",
+                       level=logging.ERROR, noiselevel=-1)
+               return 1
+       locked = False
+       if not hasattr(world_set, 'lock'):
+               world_set.lock()
+               locked = True
+       try:
+               discard_atoms = set()
+               world_set.load()
+               from portage.dep import Atom
+               for atom in world_set:
+                       if not isinstance(atom, Atom):
+                               # nested set
+                               continue
+                       for arg_atom in atoms:
+                               if arg_atom.intersects(atom):
+                                       discard_atoms.add(atom)
+                               break
+               if discard_atoms:
+                       for atom in sorted(discard_atoms):
+                               print ">>> Removing %s from \"world\" favorites file..." % \
+                                       colorize("INFORM", str(atom))
+                       remaining = set(world_set)
+                       remaining.difference_update(discard_atoms)
+                       world_set.replace(remaining)
+               else:
+                       print ">>> No matching atoms found in \"world\" favorites file..."
+       finally:
+               if locked:
+                       world_set.unlock()
+       return os.EX_OK
+
 def action_depclean(settings, trees, ldpath_mtimes,
        myopts, action, myfiles, spinner):
        # Kill packages that aren't explicitly merged or are required as a
@@ -13921,6 +13961,7 @@ def action_depclean(settings, trees, ldpath_mtimes,
        root_config = trees[myroot]["root_config"]
        getSetAtoms = root_config.setconfig.getSetAtoms
        vardb = trees[myroot]["vartree"].dbapi
+       deselect = myopts.get('--deselect') != 'n'
 
        required_set_names = ("system", "world")
        required_sets = {}
@@ -13978,11 +14019,13 @@ def action_depclean(settings, trees, ldpath_mtimes,
        if action == "depclean":
 
                if args_set:
+
+                       if deselect:
+                               world_temp_set.clear()
+
                        # Pull in everything that's installed but not matched
                        # by an argument atom since we don't want to clean any
                        # package if something depends on it.
-
-                       world_temp_set.clear()
                        for pkg in vardb:
                                spinner.update()
 
@@ -13999,9 +14042,11 @@ def action_depclean(settings, trees, ldpath_mtimes,
 
        elif action == "prune":
 
+               if deselect:
+                       world_temp_set.clear()
+
                # Pull in everything that's installed since we don't
                # to prune a package if something depends on it.
-               world_temp_set.clear()
                world_temp_set.update(vardb.cp_all())
 
                if not args_set:
@@ -14820,16 +14865,19 @@ def insert_optional_args(args):
 
        new_args = []
        jobs_opts = ("-j", "--jobs")
-       root_deps_opt = '--root-deps'
-       root_deps_choices = ('True', 'rdeps')
+       default_arg_opts = {
+               '--deselect'   : ('n',),
+               '--root-deps'  : ('rdeps',),
+       }
        arg_stack = args[:]
        arg_stack.reverse()
        while arg_stack:
                arg = arg_stack.pop()
 
-               if arg == root_deps_opt:
+               default_arg_choices = default_arg_opts.get(arg)
+               if default_arg_choices is not None:
                        new_args.append(arg)
-                       if arg_stack and arg_stack[-1] in root_deps_choices:
+                       if arg_stack and arg_stack[-1] in default_arg_choices:
                                new_args.append(arg_stack.pop())
                        else:
                                # insert default argument
@@ -14897,6 +14945,12 @@ def parse_opts(tmpcmdline, silent=False):
                        "choices":("y", "n")
                },
 
+               "--deselect": {
+                       "help"    : "remove atoms from the world file",
+                       "type"    : "choice",
+                       "choices" : ("True", "n")
+               },
+
                "--jobs": {
 
                        "help"   : "Specifies the number of packages to build " + \
@@ -14962,6 +15016,9 @@ def parse_opts(tmpcmdline, silent=False):
 
        myoptions, myargs = parser.parse_args(args=tmpcmdline)
 
+       if myoptions.deselect == "True":
+               myoptions.deselect = True
+
        if myoptions.root_deps == "True":
                myoptions.root_deps = True
 
@@ -15019,6 +15076,9 @@ def parse_opts(tmpcmdline, silent=False):
                                sys.exit(1)
                        myaction = action_opt
 
+       if myaction is None and myoptions.deselect is True:
+               myaction = 'deselect'
+
        myfiles += myargs
 
        return myaction, myopts, myfiles
@@ -15649,11 +15709,11 @@ def emerge_main():
                action_search(trees[settings["ROOT"]]["root_config"],
                        myopts, myfiles, spinner)
 
-       elif myaction in ('clean', 'depclean', 'prune', 'unmerge'):
+       elif myaction in ('clean', 'depclean', 'deselect', 'prune', 'unmerge'):
                validate_ebuild_environment(trees)
                rval = action_uninstall(settings, trees, mtimedb["ldpath"],
                        myopts, myaction, myfiles, spinner)
-               if not (buildpkgonly or fetchonly or pretend):
+               if not (myaction == 'deselect' or buildpkgonly or fetchonly or pretend):
                        post_emerge(root_config, myopts, mtimedb, rval)
                return rval
 
index f1afef8b466ee181bebb9e1d1fc6507d21088b85..66bc2f33b41acb33b87c934bc0ed363a013ccd91 100644 (file)
@@ -106,6 +106,18 @@ def help(myaction,myopts,havecolor=1):
                        "matched packages that have no reverse dependencies. Use " + \
                        "--depclean together with --verbose to show reverse dependencies."
 
+               for line in wrap(paragraph, desc_width):
+                       print desc_indent + line
+               print
+               print "       " + green("--deselect") + "[=%s]" % turquoise("n")
+
+               paragraph = \
+                       "Remove atoms from the world file. This action is implied " + \
+                       "by uninstall actions, including --depclean, " + \
+                       "--prune and --unmerge. Use --deselect=n " + \
+                       "in order to prevent uninstall actions from removing " + \
+                       "atoms from the world file."
+
                for line in wrap(paragraph, desc_width):
                        print desc_indent + line
                print
index 86625871511cae5dddd3ad622e9a726c540101cc..80ebdadbcdae463140b882e11d8296672dabd506 100644 (file)
@@ -558,6 +558,36 @@ class Atom(object):
                raise AttributeError("Atom instances are immutable",
                        self.__class__, name, value)
 
+       def intersects(self, other):
+               """
+               Atoms with different operator or cpv attributes cause this method to
+               return False. TODO: Detect intersection when operators are present.
+               @param other: The package atom to match
+               @type other: Atom
+               @rtype: Boolean
+               @return: True if this atom and the other atom intersect,
+                       False otherwise.
+               """
+               if not isinstance(other, Atom):
+                       raise TypeError("expected %s, got %s" % \
+                               (Atom, type(other)))
+
+               if self == other:
+                       return True
+
+               if self.cp != other.cp or \
+                       self.use != other.use or \
+                       self.operator != other.operator or \
+                       self.cpv != other.cpv:
+                       return False
+
+               if self.slot is None or \
+                       other.slot is None or \
+                       self.slot == other.slot:
+                       return True
+
+               return False
+
        # Implement some common str methods.
 
        def __eq__(self, other):