]> git.decadent.org.uk Git - dak.git/commitdiff
Add new scripts; remove old ones.
authorJames Troup <james@nocrew.org>
Wed, 10 Jan 2001 05:58:26 +0000 (05:58 +0000)
committerJames Troup <james@nocrew.org>
Wed, 10 Jan 2001 05:58:26 +0000 (05:58 +0000)
alyson [new file with mode: 0755]
contrib/hack.1 [new file with mode: 0755]
denise [new file with mode: 0755]
docs/README.names [new file with mode: 0644]
melanie [new file with mode: 0755]
natalie.py [new file with mode: 0755]
sortover.pl [deleted file]

diff --git a/alyson b/alyson
new file mode 100755 (executable)
index 0000000..5e602ec
--- /dev/null
+++ b/alyson
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+
+# Sync the ISC configuartion file and the SQL database
+# Copyright (C) 2000  James Troup <james@nocrew.org>
+# $Id: alyson,v 1.1 2001-01-10 05:58:26 troup Exp $
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import pg, sys
+import utils, db_access
+import apt_pkg;
+
+################################################################################
+
+Cnf = None;
+projectB = None;
+
+################################################################################
+
+def main ():
+    global Cnf, projectB;
+
+    apt_pkg.init();
+    
+    Cnf = apt_pkg.newConfiguration();
+    apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
+    Arguments = [('D',"debug","Alyson::Options::Debug", "IntVal"),
+                 ('h',"help","Alyson::Options::Help"),
+                 ('v',"version","Alyson::Options::Version")];
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
+
+    projectB = pg.connect('projectb', 'localhost');
+    db_access.init(Cnf, projectB);
+
+    # Quick hack to populate section, priority and bin_type; the rest todo later
+
+    projectB.query("BEGIN WORK");
+    projectB.query("DELETE FROM override_type");
+    for type in Cnf.SubTree("OverrideType").List():
+        projectB.query("INSERT INTO override_type (type) VALUES ('%s')" % (type));
+    projectB.query("COMMIT WORK");
+    
+    projectB.query("BEGIN WORK");
+    projectB.query("DELETE FROM priority");
+    for priority in Cnf.SubTree("Priority").List():
+        projectB.query("INSERT INTO priority (priority, level) VALUES ('%s', %s)" % (priority, Cnf["Priority::%s" % (priority)]));
+    projectB.query("COMMIT WORK");
+
+    projectB.query("BEGIN WORK");
+    projectB.query("DELETE FROM section");
+    for component in Cnf.SubTree("Component").List():
+        if component != 'main':
+            prefix = component + '/';
+        else:
+            prefix = "";
+        for section in Cnf.SubTree("Section").List():
+            projectB.query("INSERT INTO section (section) VALUES ('%s%s')" % (prefix, section));
+    projectB.query("COMMIT WORK");
+
+
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
+
diff --git a/contrib/hack.1 b/contrib/hack.1
new file mode 100755 (executable)
index 0000000..6a123fd
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+# Quick hack to import override files
+# Copyright (C) 2000  James Troup <james@nocrew.org>
+# $Id: hack.1,v 1.1 2001-01-10 05:58:26 troup Exp $
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, pg, sys, string
+import utils, db_access
+import apt_pkg;
+
+################################################################################
+
+Cnf = None;
+projectB = None;
+
+################################################################################
+
+def main ():
+    global Cnf, projectB;
+
+    apt_pkg.init();
+    
+    Cnf = apt_pkg.newConfiguration();
+    apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
+    Arguments = [('D',"debug","Alyson::Options::Debug", "IntVal"),
+                 ('h',"help","Alyson::Options::Help"),
+                 ('v',"version","Alyson::Options::Version")];
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
+
+    projectB = pg.connect('projectb', 'localhost');
+    db_access.init(Cnf, projectB);
+
+    for filename in os.listdir('.'):
+        if not os.path.isfile(filename) or filename[:9] != 'override.':
+            continue;
+        x = string.split(filename, '.')
+        suite = x[1];
+        type = "deb";
+        if suite == "experimental":
+            continue;
+        else:
+            component = x[2];
+            if suite == "woody":
+                suite = "unstable";
+            elif suite == "potato":
+                suite = "stable";
+            else:
+                print "say what?";
+                sys.exit(3);
+            if len(x) == 4:
+                type = x[3];
+                if type == "debian-installer":
+                    type = "udeb";
+                elif type == "src":
+                    type = "dsc";
+                else:
+                    print "say WHAT?";
+                    sys.exit(4);
+        print "cat %s | natalie --set --suite=%s --component=%s --type=%s" % (filename, suite, component, type);
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
+
diff --git a/denise b/denise
new file mode 100755 (executable)
index 0000000..9e8eebb
--- /dev/null
+++ b/denise
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+
+# Output override files for apt-ftparchive and indices/
+# Copyright (C) 2000  James Troup <james@nocrew.org>
+# $Id: denise,v 1.1 2001-01-10 05:58:26 troup Exp $
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+# This is seperate because it's horribly Debian specific and I don't
+# want that kind of horribleness in the otherwise generic natalie.  It
+# does duplicate code tho.
+
+################################################################################
+
+import pg, sys, string
+import utils, db_access, natalie
+import apt_pkg;
+
+################################################################################
+
+Cnf = None;
+projectB = None;
+override = {}
+
+################################################################################
+
+def list(suite, component, type):
+    global override;
+    
+    suite_id = db_access.get_suite_id(suite);
+    if suite_id == -1:
+        sys.stderr.write("Suite '%s' not recognised.\n" % (suite));
+        sys.exit(2);
+
+    component_id = db_access.get_component_id(component);
+    if component_id == -1:
+        sys.stderr.write("Component '%s' not recognised.\n" % (component));
+        sys.exit(2);
+
+    type_id = db_access.get_override_type_id(type);
+    if type_id == -1:
+        sys.stderr.write("Type '%s' not recognised. (Valid types are deb, udeb and dsc.)\n" % (type));
+        sys.exit(2);
+
+    if not override.has_key(suite):
+        override[suite] = {};
+    if not override[suite].has_key(component):
+        override[suite][component] = {};
+    if not override[suite][component].has_key(type):
+        override[suite][component][type] = {};
+
+    if type == "dsc":
+        q = projectB.query("SELECT o.package, s.section, o.maintainer FROM override o, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.section = s.id ORDER BY s.section, o.package" % (suite_id, component_id, type_id));
+        for i in q.getresult():
+            override[suite][component][type][i[0]] = i;
+            print string.join(i, '\t');
+    else:
+        q = projectB.query("SELECT o.package, p.priority, s.section, o.maintainer, p.level FROM override o, priority p, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.priority = p.id AND o.section = s.id ORDER BY s.section, p.level, o.package" % (suite_id, component_id, type_id));
+        for i in q.getresult():
+            i = i[:-1]; # Strip the priority level
+            override[suite][component][type][i[0]] = i;
+            print string.join(i, '\t');
+
+################################################################################
+
+def main ():
+    global Cnf, projectB, override;
+
+    apt_pkg.init();
+    
+    Cnf = apt_pkg.newConfiguration();
+    apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
+    Arguments = [('D',"debug","Denise::Options::Debug", "IntVal"),
+                 ('h',"help","Denise::Options::Help"),
+                 ('V',"version","Denise::Options::Version")];
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
+
+    projectB = pg.connect('projectb', 'localhost');
+    db_access.init(Cnf, projectB);
+
+    natalie.init();
+
+    for suite in [ "stable", "unstable" ]:
+        sys.stderr.write("Processing %s...\n" % (suite));
+        override_suite = Cnf["Suite::%s::OverrideCodeName" % (suite)];
+        for component in Cnf.SubTree("Component").List():
+            if component == "mixed":
+                continue; # Ick
+            for type in Cnf.SubTree("OverrideType").List():
+                if type == "deb":
+                    override_type = "";
+                elif type == "udeb":
+                    if suite != "unstable" or component != "main":
+                        continue; # Ick2
+                    override_type = ".debian-installer";
+                elif type == "dsc":
+                    override_type = ".src";
+                filename = "override.%s.%s%s" % (override_suite, component, override_type);
+                file = utils.open_file(filename, 'w');
+                sys.stdout = file;
+                list(suite, component, type);
+                sys.stdout.close();
+
+    # Munge the override file for testing by using unstable's where
+    # possible and falling back on stable's where it's not.
+
+    sys.stderr.write("Processing testing...\n");
+    suite = "testing";
+    suite_id = db_access.get_suite_id(suite);
+    for component in Cnf.SubTree("Component").List():
+        if component == "mixed":
+            continue;
+        component_id = db_access.get_component_id(component);
+        for type in Cnf.SubTree("OverrideType").List():
+            if type == "deb":
+                override_type = "";
+                q = projectB.query("SELECT DISTINCT b.package FROM bin_associations ba, binaries b, files f, location l WHERE ba.suite = %s AND l.component = %s AND ba.bin = b.id AND b.file = f.id AND f.location = l.id" % (suite_id, component_id));
+            elif type == "dsc":
+                q = projectB.query("SELECT DISTINCT s.source FROM src_associations sa, source s, files f, location l WHERE sa.suite = %s AND l.component = %s AND sa.source = s.id AND s.file = f.id AND f.location = l.id" % (suite_id, component_id));
+                override_type = ".src";
+            elif type == "udeb":
+                continue;
+            filename = "override.testing.%s%s" % (component, override_type);
+            file = utils.open_file(filename, 'w');
+            sys.stdout = file;
+            for i in q.getresult():
+                package = i[0];
+                if override["unstable"][component][type].has_key(package):
+                    print string.join(override["unstable"][component][type][package], '\t');
+                elif override["stable"][component][type].has_key(package):
+                    print string.join(override["stable"][component][type][package], '\t');
+                else:
+                    if type == "dsc" and (override["unstable"][component]["deb"].has_key(package) or override["stable"][component]["deb"].has_key(package)):
+                        continue; # source falls back on binary; so accept silently
+                    sys.stderr.write("W: Can't find override entry for testing package '%s' (component %s, type %s).\n" % (package, component, type));
+            sys.stdout.close();
+    
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
+
diff --git a/docs/README.names b/docs/README.names
new file mode 100644 (file)
index 0000000..d5db95c
--- /dev/null
@@ -0,0 +1,19 @@
+alyson - sync's the ISC-style configuration file with the SQL DB
+catherine - poolifies packages; i.e. moves them from legacy dists/ locations into the pool
+charisma - generates Maintainers files used by e.g. the Debian BTS
+claire - generates compatability symlink tree for legacy dists/ locations
+heidi - manipulates suite tags; i.e. removes/adds packages from any given suite
+jenna - generates lists of files in suites which are then fed to apt-ftparchive
+katie - installs packages into the pool
+madison - shows which suites a binary package is in
+melanie - archive maintenance tool; removes packages from the distribution
+natalie - manpiulates override entries
+neve - populates the SQL database
+rhona - cleans old packages out of the pool and the database
+shania - cleans cruft from incoming
+tea - sanity checks the database
+
+With apologies to Alyson Hannigan, Catherine Zeta Jones, Charisma
+Carpenter, Claire Daines, Heidi Klum, Jenna Elfman, Katie Holmes,
+Madison Michele, Melanie Sykes, Natalie Portman, Neve Campbell, Rhona
+Mitre, Shania Twain and Tea Leoni.
diff --git a/melanie b/melanie
new file mode 100755 (executable)
index 0000000..a4ec6b0
--- /dev/null
+++ b/melanie
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+
+# General purpose archive tool for ftpmaster
+# Copyright (C) 2000  James Troup <james@nocrew.org>
+# $Id: melanie,v 1.1 2001-01-10 05:58:26 troup Exp $
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+# X-Listening-To: Astronomy, Metallica - Garage Inc.
+
+################################################################################
+
+import commands, os, pg, pwd, re, string, sys, tempfile
+import utils, db_access
+import apt_pkg, apt_inst;
+
+################################################################################
+
+re_strip_source_version = re.compile (r'\s+.*$');
+
+################################################################################
+
+Cnf = None;
+projectB = None;
+
+################################################################################
+
+def game_over():
+    print "Continue (y/N)? ",
+    answer = string.lower(utils.our_raw_input());
+    if answer != "y":
+        print "Aborted."
+        sys.exit(1);
+
+################################################################################
+
+def main ():
+    global Cnf, projectB;
+
+    apt_pkg.init();
+    
+    Cnf = apt_pkg.newConfiguration();
+    apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
+
+    Arguments = [('D',"debug","Melanie::Options::Debug", "IntVal"),
+                 ('h',"help","Melanie::Options::Help"),
+                 ('V',"version","Melanie::Options::Version"),
+                 ('a',"architecture","Melanie::Options::Architecture", "HasArg"),
+                 ('b',"binary", "Melanie::Options::Binary-Only"),
+                 ('c',"component", "Melanie::Options::Component", "HasArg"),
+                 ('d',"done","Melanie::Options::Done", "HasArg"), # Bugs fixed
+                 ('m',"reason", "Melanie::Options::Reason", "HasArg"), # Hysterical raisins; -m is old-dinstall option for rejection reason
+                 ('n',"no-action","Melanie::Options::No-Action"),
+                 ('o',"orphan", "Melanie::Options::Orphan"),
+                 ('p',"partial", "Melanie::Options::Partial"),
+                 ('s',"suite","Melanie::Options::Suite", "HasArg"),
+                 ('S',"source-only", "Melanie::Options::Source-Only"),
+                 ];
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
+    projectB = pg.connect('projectb', 'localhost');
+    db_access.init(Cnf, projectB);
+
+    # Sanity check options
+    if arguments == []:
+        sys.stderr.write("E: need at least one package name as an argument.\n");
+        sys.exit(1);
+    if Cnf["Melanie::Options::Architecture"] and Cnf["Melanie::Options::Source-Only"]:
+        sys.stderr.write("E: can't use -a/--architecutre and -S/--source-only options simultaneously.\n");
+        sys.exit(1);
+    if Cnf["Melanie::Options::Binary-Only"] and Cnf["Melanie::Options::Source-Only"]:
+        sys.stderr.write("E: can't use -b/--binary-only and -S/--source-only options simultaneously.\n");
+        sys.exit(1);
+    if Cnf["Melanie::Options::Architecture"] and not Cnf["Melanie::Options::Partial"]:
+        sys.stderr.write("W: -a/--architecture implies -p/--partial.\n");
+        Cnf["Melanie::Options::Partial"] = "true";
+
+    packages = {};
+    if Cnf["Melanie::Options::Binary-Only"]:
+        field = "b.package";
+    else:
+        field = "s.source";
+    con_packages = "AND (";
+    for package in arguments:
+        con_packages = con_packages + "%s = '%s' OR " % (field, package)
+        packages[package] = "";
+    con_packages = con_packages[:-3] + ")"
+
+    suites_list = "";
+    suite_ids_list = [];
+    con_suites = "AND (";
+    for suite in string.split(Cnf["Melanie::Options::Suite"]):
+        
+        if suite == "stable":
+            print "**WARNING** About to remove from the 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();
+            
+        suite_id = db_access.get_suite_id(suite);
+        if suite_id == -1:
+            sys.stderr.write("W: suite '%s' not recognised.\n" % (suite));
+        else:
+            con_suites = con_suites + "su.id = %s OR " % (suite_id)
+
+        suites_list = suites_list + suite + ", "
+        suite_ids_list.append(suite_id);
+    con_suites = con_suites[:-3] + ")"
+    suites_list = suites_list[:-2];
+
+    if Cnf["Melanie::Options::Component"]:
+        con_components = "AND (";
+        over_con_components = "AND (";
+        for component in string.split(Cnf["Melanie::Options::Component"]):
+            component_id = db_access.get_component_id(component);
+            if component_id == -1:
+                sys.stderr.write("W: component '%s' not recognised.\n" % (component));
+            else:
+                con_components = con_components + "c.id = %s OR " % (component_id);
+                over_con_components = over_con_components + "component = %s OR " % (component_id);
+        con_components = con_components[:-3] + ")"
+        over_con_components = over_con_components[:-3] + ")";
+    else:
+        con_components = "";    
+        over_con_components = "";
+
+    if Cnf["Melanie::Options::Architecture"]:
+        con_architectures = "AND (";
+        for architecture in string.split(Cnf["Melanie::Options::Architecture"]):
+            architecture_id = db_access.get_architecture_id(architecture);
+            if architecture_id == -1:
+                sys.stderr.write("W: architecture '%s' not recognised.\n" % (architecture));
+            else:
+                con_architectures = con_architectures + "a.id = %s OR " % (architecture_id)
+        con_architectures = con_architectures[:-3] + ")"
+    else:
+        con_architectures = "";
+
+
+    print "Working...",
+    sys.stdout.flush();
+    to_remove = [];
+    # We have 3 modes of package selection: binary-only, source-only
+    # and source+binary.  The first two are trivial and obvious; the
+    # latter is a nasty mess, but very nice from a UI perspective so
+    # we try to support it.
+
+    if Cnf["Melanie::Options::Binary-Only"]:
+        # Binary-only
+        q = projectB.query("SELECT b.package, b.version, a.arch_string, b.id FROM binaries b, bin_associations ba, architecture a, suite su, files f, location l, component c WHERE ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id AND b.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s %s" % (con_packages, con_suites, con_components, con_architectures));
+        for i in q.getresult():
+            to_remove.append(i);
+    else:
+        # Source-only
+        source_packages = {};
+        q = projectB.query("SELECT l.path, f.filename, s.source, s.version, 'source', s.id FROM source s, src_associations sa, suite su, files f, location l, component c WHERE sa.source = s.id AND sa.suite = su.id AND s.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s" % (con_packages, con_suites, con_components));
+        for i in q.getresult():
+            source_packages[i[2]] = i[:2];
+            to_remove.append(i[2:]);
+        if not Cnf["Melanie::Options::Source-Only"]:
+            # Source + Binary
+            binary_packages = {};
+            # First get a list of binary package names we suspect are linked to the source
+            q = projectB.query("SELECT DISTINCT package FROM binaries WHERE EXISTS (SELECT s.source, s.version, l.path, f.filename FROM source s, src_associations sa, suite su, files f, location l, component c WHERE binaries.source = s.id AND sa.source = s.id AND sa.suite = su.id AND s.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s)" % (con_packages, con_suites, con_components));
+            for i in q.getresult():
+                binary_packages[i[0]] = "";
+            # Then parse each .dsc that we found earlier to see what binary packages it thinks it produces
+            for i in source_packages.keys():
+                filename = string.join(source_packages[i], '/');
+                try:
+                    dsc = utils.parse_changes(filename);
+                except utils.cant_open_exc:
+                    sys.stderr.write("W: couldn't open '%s'.\n" % (filename));
+                    continue;
+                for package in string.split(dsc.get("binary"), ','):
+                    package = string.strip(package);
+                    binary_packages[package] = "";
+            # Then for each binary package: find any version in
+            # unstable, check the Source: field in the deb matches our
+            # source package and if so add it to the list of packages
+            # to be removed.
+            for package in binary_packages.keys():
+                q = projectB.query("SELECT l.path, f.filename, b.package, b.version, a.arch_string, b.id FROM binaries b, bin_associations ba, architecture a, suite su, files f, location l, component c WHERE ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id AND b.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s AND b.package = '%s'" % (con_suites, con_components, con_architectures, package));
+                for i in q.getresult():
+                    filename = string.join(i[:2], '/');
+                    control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(filename,"r")))
+                    source = control.Find("Source", control.Find("Package"));
+                    source = re_strip_source_version.sub('', source);
+                    if source_packages.has_key(source):
+                        to_remove.append(i[2:]);
+                    #else:
+                        #sys.stderr.write("W: skipping '%s' as it's source ('%s') isn't one of the source packages.\n" % (filename, source));
+    print "done."
+
+    # 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 Cnf["Melanie::Options::Reason"]:
+        temp_filename = tempfile.mktemp();
+        fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
+        os.close(fd);
+        result = os.system("vi %s" % (temp_filename))
+        if result != 0:
+            sys.stderr.write ("vi invocation failed for `%s'!" % (temp_filename))
+            sys.exit(result)
+        file = utils.open_file(temp_filename, 'r');
+        for line in file.readlines():
+            Cnf["Melanie::Options::Reason"] = Cnf["Melanie::Options::Reason"] + line;
+        os.unlink(temp_filename);
+
+    # Generate the summary of what's to be removed
+    d = {};
+    for i in to_remove:
+        package = i[0];
+        version = i[1];
+        architecture = i[2];
+        if not d.has_key(package):
+            d[package] = {};
+        if not d[package].has_key(version):
+            d[package][version] = [];
+        d[package][version].append(architecture);
+
+    summary = "";
+    packages = d.keys();
+    packages.sort();
+    for package in packages:
+        versions = d[package].keys();
+        versions.sort();
+        for version in versions:
+            summary = summary + "%10s | %10s | " % (package, version);
+            for architecture in d[package][version]:
+                summary = "%s%s, " % (summary, architecture);
+            summary = summary[:-2] + '\n';
+
+    print "Will remove the following packages from %s:" % (suites_list);
+    print
+    print summary
+    if Cnf["Melanie::Options::Done"]:
+        print "Will also close bugs: "+Cnf["Melanie::Options::Done"];
+    print
+    print "------------------- Reason -------------------"
+    print Cnf["Melanie::Options::Reason"];
+    print "----------------------------------------------"
+    print
+
+    # If -n/--no-action, drop out here
+    if Cnf["Melanie::Options::No-Action"]:
+        sys.exit(0);
+        
+    game_over();
+
+    whoami = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '');
+    date = commands.getoutput('date -R');
+
+    # Log first; if it all falls apart I want a record that we at least tried.
+    logfile = utils.open_file(Cnf["Melanie::LogFile"], 'a');
+    logfile.write("=========================================================================\n");
+    logfile.write("[Date: %s] [ftpmaster: %s]\n" % (date, whoami));
+    logfile.write("Removed the following packages from %s:\n\n%s" % (suites_list, summary));
+    if Cnf["Melanie::Options::Done"]:
+        logfile.write("Closed bugs: %s\n" % (Cnf["Melanie::Options::Done"]));
+    logfile.write("\n------------------- Reason -------------------\n%s\n" % (Cnf["Melanie::Options::Reason"]));
+    logfile.write("----------------------------------------------\n");
+    logfile.flush();
+        
+    dsc_type_id = db_access.get_override_type_id('dsc');
+    deb_type_id = db_access.get_override_type_id('deb');
+    
+    # Do the actual deletion
+    print "Deleting...",
+    sys.stdout.flush();
+    projectB.query("BEGIN WORK");
+    for i in to_remove:
+        package = i[0];
+        architecture = i[2];
+        package_id = i[3];
+        for suite_id in suite_ids_list:
+            if architecture == "source":
+                projectB.query("DELETE FROM src_associations WHERE source = %s AND suite = %s" % (package_id, suite_id));
+                #print "DELETE FROM src_associations WHERE source = %s AND suite = %s" % (package_id, suite_id);
+            else:
+                projectB.query("DELETE FROM bin_associations WHERE bin = %s AND suite = %s" % (package_id, suite_id));
+                #print "DELETE FROM bin_associations WHERE bin = %s AND suite = %s" % (package_id, suite_id);
+            # Delete from the override file
+            if not Cnf["Melanie::Options::Partial"]:
+                if architecture == "source":
+                    type_id = dsc_type_id;
+                else:
+                    type_id = deb_type_id;
+                projectB.query("DELETE FROM override WHERE package = '%s' AND type = %s AND suite = %s %s" % (package, type_id, suite_id, over_con_components));
+    projectB.query("COMMIT WORK");
+    print "done."
+
+    # Send the bug closing messages
+    if Cnf["Melanie::Options::Done"]:
+        for bug in string.split(Cnf["Melanie::Options::Done"]):
+            mail_message = """Return-Path: %s
+From: %s
+To: %s-close@bugs.debian.org
+Bcc: troup@auric.debian.org
+Subject: Bug#%s: fixed
+
+We believe that the bug you reported is now fixed; the following
+package(s) have been removed from %s:
+
+%s
+Note that the package(s) have simply been removed from the tag
+database and may (or may not) still be in the pool; this is not a bug.
+The package(s) will be physically removed automatically when no suite
+references them (and in the case of source, when no binary references
+it).  Please also remember that the changes have been done on the
+master archive (ftp-master.debian.org) and will not propagate to any
+mirrors (ftp.debian.org included) until the next cron.daily run at the
+earliest.
+
+Thank you for reporting the bug, which will now be closed.  If you
+have further comments please address them to %s@bugs.debian.org.
+
+This message was generated automatically; if you believe that there is
+a problem with it please contact the archive administrators by mailing
+ftpmaster@debian.org.
+
+Debian distribution maintenance software
+pp.
+%s (the ftpmaster behind the curtain)
+""" % (Cnf["Melanie::MyEmailAddress"], Cnf["Melanie::MyEmailAddress"], bug, bug, suites_list, summary, bug, whoami);
+            utils.send_mail (mail_message, "")
+            
+    logfile.write("=========================================================================\n");
+    logfile.close();
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
+
diff --git a/natalie.py b/natalie.py
new file mode 100755 (executable)
index 0000000..8206ddf
--- /dev/null
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+
+# Manipulate override files
+# Copyright (C) 2000  James Troup <james@nocrew.org>
+# $Id: natalie.py,v 1.1 2001-01-10 05:58:26 troup Exp $
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import pg, string, sys, time
+import utils, db_access
+import apt_pkg;
+
+################################################################################
+
+Cnf = None;
+projectB = None;
+
+################################################################################
+
+def init():
+    global projectB;
+    
+    projectB = pg.connect('projectb', 'localhost');
+    db_access.init(Cnf, projectB);
+
+def process_file (file, suite, component, type, action):
+    suite_id = db_access.get_suite_id(suite);
+    if suite_id == -1:
+        sys.stderr.write("Suite '%s' not recognised.\n" % (suite));
+        sys.exit(2);
+
+    component_id = db_access.get_component_id(component);
+    if component_id == -1:
+        sys.stderr.write("Component '%s' not recognised.\n" % (component));
+        sys.exit(2);
+
+    type_id = db_access.get_override_type_id(type);
+    if type_id == -1:
+        sys.stderr.write("Type '%s' not recognised. (Valid types are deb, udeb and dsc.)\n" % (type));
+        sys.exit(2);
+
+    # --set is done mostly internal for performance reasons; most
+    # invocations of --set will be updates and making people wait 2-3
+    # minutes while 6000 select+inserts are run needlessly isn't cool.
+    
+    original = {};
+    new = {};
+    c_skipped = 0;
+    c_added = 0;
+    c_updated = 0;
+    c_removed = 0;
+    c_error = 0;
+    
+    q = projectB.query("SELECT package, priority, section, maintainer FROM override WHERE suite = %s AND component = %s AND type = %s"
+                       % (suite_id, component_id, type_id));
+    for i in q.getresult():
+        original[i[0]] = i[1:];
+
+    start_time = time.time();
+    projectB.query("BEGIN WORK");
+    for line in file.readlines():
+        line = string.strip(utils.re_comments.sub('', line[:-1]))
+        if line == "":
+            continue;
+        
+        maintainer_override = "";
+        if type == "dsc":
+            split_line = string.split(line, None, 2);
+            if len(split_line) == 2:
+                (package, section) = split_line;
+            elif len(split_line) == 3:
+                (package, section, maintainer_override) = split_line;
+            else:
+                sys.stderr.write("W: '%s' does not break into 'package section [maintainer override]'.\n" % (line));
+                c_error = c_error + 1;
+                continue;
+            priority = "source";
+        else: # binary or udeb
+            split_line = string.split(line, None, 3);
+            if len(split_line) == 3:
+                (package, priority, section) = split_line;
+            elif len(split_line) == 4:
+                (package, priority, section, maintainer_override) = split_line;
+            else:
+                sys.stderr.write("W: '%s' does not break into 'package priority section [maintainer override]'.\n" % (line));
+                c_error = c_error + 1;
+                continue;
+
+        section_id = db_access.get_section_id(section);
+        if section_id == -1:
+            sys.stderr.write("W: '%s' is not a valid section. ['%s' in suite %s, component %s].\n" % (section, package, suite, component));
+            c_error = c_error + 1;
+            continue;
+        priority_id = db_access.get_priority_id(priority);
+        if priority_id == -1:
+            sys.stderr.write("W: '%s' is not a valid priority. ['%s' in suite %s, component %s].\n" % (priority, package, suite, component));
+            c_error = c_error + 1;
+            continue;
+
+        if new.has_key(package):
+            sys.stderr.write("W: Can't insert duplicate entry for '%s'; ignoring all but the first. [suite %s, component %s]\n" % (package, suite, component));
+            c_error = c_error + 1;
+            continue;
+        new[package] = "";
+        if original.has_key(package):
+            (old_priority_id, old_section_id, old_maintainer_override) = original[package];
+            if old_priority_id == priority_id and old_section_id == section_id and old_maintainer_override == maintainer_override:
+                # Same?  Ignore it
+                c_skipped = c_skipped + 1;
+                continue; 
+            else:
+                # Changed?  Delete the old one so we can reinsert it with the new information
+                c_updated = c_updated + 1;
+                projectB.query("DELETE FROM override WHERE suite = %s AND component = %s AND package = '%s' AND type = %s"
+                               % (suite_id, component_id, package, type_id));
+        else:
+            c_added = c_added + 1;
+            
+        if maintainer_override:
+            projectB.query("INSERT INTO override (suite, component, type, package, priority, section, maintainer) VALUES (%s, %s, %s, '%s', %s, %s, '%s')"
+                           % (suite_id, component_id, type_id, package, priority_id, section_id, maintainer_override));
+        else:
+            projectB.query("INSERT INTO override (suite, component, type, package, priority, section) VALUES (%s, %s, %s, '%s', %s, %s)"
+                           % (suite_id, component_id, type_id, package, priority_id, section_id));
+
+
+    # Delete any packages which were removed
+    for package in original.keys():
+        if not new.has_key(package):
+            projectB.query("DELETE FROM override WHERE suite = %s AND component = %s AND package = '%s' AND type = %s"
+                           % (suite_id, component_id, package, type_id));
+            c_removed = c_removed + 1;
+
+    projectB.query("COMMIT WORK");
+    print "Done in %d seconds. [Updated = %d, Added = %d, Removed = %d, Skipped = %d, Errors = %d]" % (int(time.time()-start_time), c_updated, c_added, c_removed, c_skipped, c_error);
+
+################################################################################
+
+def list(suite, component, type):
+    suite_id = db_access.get_suite_id(suite);
+    if suite_id == -1:
+        sys.stderr.write("Suite '%s' not recognised.\n" % (suite));
+        sys.exit(2);
+
+    component_id = db_access.get_component_id(component);
+    if component_id == -1:
+        sys.stderr.write("Component '%s' not recognised.\n" % (component));
+        sys.exit(2);
+
+    type_id = db_access.get_override_type_id(type);
+    if type_id == -1:
+        sys.stderr.write("Type '%s' not recognised. (Valid types are deb, udeb and dsc.)\n" % (type));
+        sys.exit(2);
+
+    if type == "dsc":
+        q = projectB.query("SELECT o.package, s.section, o.maintainer FROM override o, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.section = s.id ORDER BY s.section, o.package" % (suite_id, component_id, type_id));
+        for i in q.getresult():
+            print string.join(i, '\t');
+    else:
+        q = projectB.query("SELECT o.package, p.priority, s.section, o.maintainer, p.level FROM override o, priority p, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.priority = p.id AND o.section = s.id ORDER BY s.section, p.level, o.package" % (suite_id, component_id, type_id));
+        for i in q.getresult():
+            print string.join(i[:-1], '\t');
+
+################################################################################
+
+def main ():
+    global Cnf, projectB;
+
+    apt_pkg.init();
+    
+    Cnf = apt_pkg.newConfiguration();
+    apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
+    Arguments = [('D',"debug","Natalie::Options::Debug", "IntVal"),
+                 ('h',"help","Natalie::Options::Help"),
+                 ('V',"version","Natalie::Options::Version"),
+                 ('c',"component", "Natalie::Options::Component", "HasArg"),
+                 ('l',"list", "Natalie::Options::List"),
+                 ('s',"suite","Natalie::Options::Suite", "HasArg"),
+                 ('S',"set","Natalie::Options::Set"),
+                 ('t',"type","Natalie::Options::Type", "HasArg")];
+    file_list = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
+
+    init();
+
+    action = None;
+    for i in [ "list", "set" ]:
+        if Cnf["Natalie::Options::%s" % (i)]:
+            if action != None:
+                sys.stderr.write("Can not perform more than one action at once.\n");
+                sys.exit(2);
+            action = i;
+
+    (suite, component, type) = (Cnf["Natalie::Options::Suite"], Cnf["Natalie::Options::Component"], Cnf["Natalie::Options::Type"])
+
+    if action == "list":
+        list(suite, component, type);
+    else:
+        if file_list != []:
+            for file in file_list:
+                process_file(utils.open_file(file,'r'), suite, component, type, action);
+        else:
+            process_file(sys.stdin, suite, component, type, action);
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
+
diff --git a/sortover.pl b/sortover.pl
deleted file mode 100755 (executable)
index 3328cee..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/perl
-%iv=qw(required 00
-       important 01
-       standard 02
-       optional 03
-       extra 04);
-sub t {
-    return $_[0] if $_[0] =~ m/^\#/;
-    $_[0] =~ m/^(\S+)\s+(\S+)\s+(\S+)\s/ || die "$0: `$_[0]'";
-    return "$3 $iv{$2} $1";
-}
-print(sort { &t($a) cmp &t($b) } <STDIN>) || die $!;
-close(STDOUT) || die $!;