From: Zac Medico <zmedico@gentoo.org>
Date: Thu, 30 Apr 2009 07:19:02 +0000 (-0000)
Subject: Add a new --deselect action which removes atoms from the world file. This
X-Git-Tag: v2.1.6.12~54
X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=abd693991aa185c0b2259f814feb9f3df55187ae;p=portage.git

Add a new --deselect action which removes 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. This solves bug #259994 and bug #265206.
(trunk r13363)

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

diff --git a/man/emerge.1 b/man/emerge.1
index 8b2e7c167..a085f8a25 100644
--- a/man/emerge.1
+++ b/man/emerge.1
@@ -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
diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py
index 03cef509e..3ba88eccc 100644
--- a/pym/_emerge/__init__.py
+++ b/pym/_emerge/__init__.py
@@ -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
 
diff --git a/pym/_emerge/help.py b/pym/_emerge/help.py
index f1afef8b4..66bc2f33b 100644
--- a/pym/_emerge/help.py
+++ b/pym/_emerge/help.py
@@ -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
diff --git a/pym/portage/dep.py b/pym/portage/dep.py
index 866258715..80ebdadbc 100644
--- a/pym/portage/dep.py
+++ b/pym/portage/dep.py
@@ -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):