def get_reject_reason(reason=''): """get reason for rejection @rtype: str @return: string giving the reason for the rejection or C{None} if the rejection should be cancelled """ answer = 'E' if Options['Automatic']: answer = 'R' while answer == 'E': reason = utils.call_editor(reason) print "Reject message:" print utils.prefix_multi_line_string(reason, " ", include_blank_lines=1) prompt = "[R]eject, Edit, Abandon, Quit ?" answer = "XXX" while prompt.find(answer) == -1: answer = utils.our_raw_input(prompt) m = re_default_answer.search(prompt) if answer == "": answer = m.group(1) answer = answer[:1].upper() if answer == 'Q': sys.exit(0) if answer == 'R': return reason return None
def main(): global Options cnf = Config() Arguments = [ ('h', "help", "Rm::Options::Help"), ('A', 'no-arch-all-rdeps', 'Rm::Options::NoArchAllRdeps'), ('a', "architecture", "Rm::Options::Architecture", "HasArg"), ('b', "binary", "Rm::Options::Binary"), ('B', "binary-only", "Rm::Options::Binary-Only"), ('c', "component", "Rm::Options::Component", "HasArg"), ('C', "carbon-copy", "Rm::Options::Carbon-Copy", "HasArg"), # Bugs to Cc ('d', "done", "Rm::Options::Done", "HasArg"), # Bugs fixed ('D', "do-close", "Rm::Options::Do-Close"), ('R', "rdep-check", "Rm::Options::Rdep-Check"), ( 'm', "reason", "Rm::Options::Reason", "HasArg" ), # Hysterical raisins; -m is old-dinstall option for rejection reason ('n', "no-action", "Rm::Options::No-Action"), ('o', "outdated", "Rm::Options::Outdated"), ('p', "partial", "Rm::Options::Partial"), ('s', "suite", "Rm::Options::Suite", "HasArg"), ('S', "source-only", "Rm::Options::Source-Only"), ] for i in [ 'NoArchAllRdeps', "architecture", "binary", "binary-only", "carbon-copy", "component", "done", "help", "no-action", "outdated", "partial", "rdep-check", "reason", "source-only", "Do-Close" ]: key = "Rm::Options::%s" % (i) if key not in cnf: cnf[key] = "" if "Rm::Options::Suite" not in cnf: cnf["Rm::Options::Suite"] = "unstable" arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) Options = cnf.subtree("Rm::Options") if Options["Help"]: usage() session = DBConn().session() # Sanity check options if not arguments: utils.fubar("need at least one package name as an argument.") if Options["Architecture"] and Options["Source-Only"]: utils.fubar( "can't use -a/--architecture and -S/--source-only options simultaneously." ) actions = [ Options["Binary"], Options["Binary-Only"], Options["Source-Only"] ] nr_actions = len([act for act in actions if act]) if nr_actions > 1: utils.fubar( "Only one of -b/--binary, -B/--binary-only and -S/--source-only can be used." ) if "Carbon-Copy" not in Options and "Done" not in Options: utils.fubar( "can't use -C/--carbon-copy without also using -d/--done option.") if Options["Architecture"] and not Options["Partial"]: utils.warn("-a/--architecture implies -p/--partial.") Options["Partial"] = "true" if Options["Outdated"] and not Options["Partial"]: utils.warn("-o/--outdated implies -p/--partial.") Options["Partial"] = "true" if Options["Do-Close"] and not Options["Done"]: utils.fubar("-D/--do-close needs -d/--done (bugnr).") if (Options["Do-Close"] and (Options["Binary"] or Options["Binary-Only"] or Options["Source-Only"])): utils.fubar( "-D/--do-close cannot be used with -b/--binary, -B/--binary-only or -S/--source-only." ) # Force the admin to tell someone if we're not doing a 'dak # cruft-report' inspired removal (or closing a bug, which counts # as telling someone). if not Options["No-Action"] and not Options["Carbon-Copy"] \ and not Options["Done"] and Options["Reason"].find("[auto-cruft]") == -1: utils.fubar( "Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal." ) if Options["Binary"]: field = "b.package" else: field = "s.source" con_packages = "AND %s IN (%s)" % (field, ", ".join( [repr(i) for i in arguments])) (con_suites, con_architectures, con_components, check_source) = \ utils.parse_args(Options) # Additional suite checks suite_ids_list = [] whitelists = [] suites = utils.split_args(Options["Suite"]) suites_list = utils.join_with_commas_and(suites) if not Options["No-Action"]: for suite in suites: s = get_suite(suite, session=session) if s is not None: suite_ids_list.append(s.suite_id) whitelists.append(s.mail_whitelist) if suite in ("oldstable", "stable"): print( "**WARNING** About to remove from the (old)stable suite!") print( "This should only be done just prior to a (point) release and not at" ) print("any other time.") game_over() elif suite == "testing": print("**WARNING About to remove from the testing suite!") print( "There's no need to do this normally as removals from unstable will" ) print("propogate to testing automagically.") game_over() # Additional architecture checks if Options["Architecture"] and check_source: utils.warn("'source' in -a/--argument makes no sense and is ignored.") # Don't do dependency checks on multiple suites if Options["Rdep-Check"] and len(suites) > 1: utils.fubar( "Reverse dependency check on multiple suites is not implemented.") q_outdated = "TRUE" if Options["Outdated"]: q_outdated = "s.version < newest_source.version" to_remove = [] maintainers = {} # We have 3 modes of package selection: binary, source-only, binary-only # and source+binary. # XXX: TODO: This all needs converting to use placeholders or the object # API. It's an SQL injection dream at the moment if Options["Binary"]: # Removal by binary package name q = session.execute(""" SELECT b.package, b.version, a.arch_string, b.id, b.maintainer, s.source, s.version as source_version, newest_source.version as newest_sversion FROM binaries b JOIN source s ON s.id = b.source JOIN bin_associations ba ON ba.bin = b.id JOIN architecture a ON a.id = b.architecture JOIN suite su ON su.id = ba.suite JOIN files f ON f.id = b.file JOIN files_archive_map af ON af.file_id = f.id AND af.archive_id = su.archive_id JOIN component c ON c.id = af.component_id JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite WHERE %s %s %s %s %s """ % (q_outdated, con_packages, con_suites, con_components, con_architectures)) to_remove.extend(q) else: # Source-only if not Options["Binary-Only"]: q = session.execute(""" SELECT s.source, s.version, 'source', s.id, s.maintainer, s.source, s.version as source_version, newest_source.version as newest_sversion FROM source s JOIN src_associations sa ON sa.source = s.id JOIN suite su ON su.id = sa.suite JOIN archive ON archive.id = su.archive_id JOIN files f ON f.id = s.file JOIN files_archive_map af ON af.file_id = f.id AND af.archive_id = su.archive_id JOIN component c ON c.id = af.component_id JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite WHERE %s %s %s %s """ % (q_outdated, con_packages, con_suites, con_components)) to_remove.extend(q) if not Options["Source-Only"]: # Source + Binary q = session.execute(""" SELECT b.package, b.version, a.arch_string, b.id, b.maintainer, s.source, s.version as source_version, newest_source.version as newest_sversion FROM binaries b JOIN bin_associations ba ON b.id = ba.bin JOIN architecture a ON b.architecture = a.id JOIN suite su ON ba.suite = su.id JOIN archive ON archive.id = su.archive_id JOIN files_archive_map af ON b.file = af.file_id AND af.archive_id = archive.id JOIN component c ON af.component_id = c.id JOIN source s ON b.source = s.id JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite WHERE %s %s %s %s %s """ % (q_outdated, con_packages, con_suites, con_components, con_architectures)) to_remove.extend(q) if not to_remove: print("Nothing to do.") sys.exit(0) # Process -C/--carbon-copy # # Accept 3 types of arguments (space separated): # 1) a number - assumed to be a bug number, i.e. [email protected] # 2) the keyword 'package' - cc's [email protected] for every argument # 3) contains a '@' - assumed to be an email address, used unmodified # carbon_copy = [] for copy_to in utils.split_args(Options.get("Carbon-Copy")): if copy_to.isdigit(): if "Dinstall::BugServer" in cnf: carbon_copy.append(copy_to + "@" + cnf["Dinstall::BugServer"]) else: utils.fubar( "Asked to send mail to #%s in BTS but Dinstall::BugServer is not configured" % copy_to) elif copy_to == 'package': for package in set([s[5] for s in to_remove]): if "Dinstall::PackagesServer" in cnf: carbon_copy.append(package + "@" + cnf["Dinstall::PackagesServer"]) elif '@' in copy_to: carbon_copy.append(copy_to) else: utils.fubar( "Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." % (copy_to)) # If we don't have a reason; spawn an editor so the user can add one # Write the rejection email out as the <foo>.reason file if not Options["Reason"] and not Options["No-Action"]: Options["Reason"] = utils.call_editor() # Generate the summary of what's to be removed d = {} for i in to_remove: package = i[0] version = i[1] architecture = i[2] maintainer = i[4] maintainers[maintainer] = "" source = i[5] source_version = i[6] source_newest = i[7] if package not in d: d[package] = {} if version not in d[package]: d[package][version] = [] if architecture not in d[package][version]: d[package][version].append(architecture) maintainer_list = [] for maintainer_id in maintainers.keys(): maintainer_list.append(get_maintainer(maintainer_id).name) summary = "" removals = sorted(d) for package in removals: versions = sorted(d[package], key=functools.cmp_to_key(apt_pkg.version_compare)) for version in versions: d[package][version].sort(key=utils.ArchKey) summary += "%10s | %10s | %s\n" % (package, version, ", ".join( d[package][version])) print("Will remove the following packages from %s:" % (suites_list)) print() print(summary) print("Maintainer: %s" % ", ".join(maintainer_list)) if Options["Done"]: print("Will also close bugs: " + Options["Done"]) if carbon_copy: print("Will also send CCs to: " + ", ".join(carbon_copy)) if Options["Do-Close"]: print("Will also close associated bug reports.") print() print("------------------- Reason -------------------") print(Options["Reason"]) print("----------------------------------------------") print() if Options["Rdep-Check"]: arches = utils.split_args(Options["Architecture"]) include_arch_all = Options['NoArchAllRdeps'] == '' if include_arch_all and 'all' in arches: # when arches is None, rdeps are checked on all arches in the suite arches = None reverse_depends_check(removals, suites[0], arches, session, include_arch_all=include_arch_all) # If -n/--no-action, drop out here if Options["No-Action"]: sys.exit(0) print("Going to remove the packages now.") game_over() # Do the actual deletion print("Deleting...", end=' ') sys.stdout.flush() try: bugs = utils.split_args(Options["Done"]) remove(session, Options["Reason"], suites, to_remove, partial=Options["Partial"], components=utils.split_args(Options["Component"]), done_bugs=bugs, carbon_copy=carbon_copy, close_related_bugs=Options["Do-Close"]) except ValueError as ex: utils.fubar(ex.message) else: print("done.")