]> git.decadent.org.uk Git - dak.git/blobdiff - katie.py
* katie.py (source_exists): expand the list of distributionsthe source may exist...
[dak.git] / katie.py
index 8c31a2dd556a714c138515eb23d584593887be1b..8f76c7d513753b1376089146a587fb04c799fc7d 100644 (file)
--- a/katie.py
+++ b/katie.py
@@ -1,8 +1,8 @@
 #!/usr/bin/env python
 
 # Utility functions for katie
-# Copyright (C) 2001, 2002  James Troup <james@nocrew.org>
-# $Id: katie.py,v 1.25 2002-07-12 14:59:08 troup Exp $
+# Copyright (C) 2001, 2002, 2003  James Troup <james@nocrew.org>
+# $Id: katie.py,v 1.38 2003-08-18 15:04:17 ajt 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
@@ -25,7 +25,6 @@ import utils, db_access;
 import apt_inst, apt_pkg;
 
 from types import *;
-from string import lower;
 
 ###############################################################################
 
@@ -57,7 +56,7 @@ class nmu_p:
             filename = Cnf["Dir::Override"] + Cnf["Dinstall::GroupOverrideFilename"];
             file = utils.open_file(filename);
             for line in file.readlines():
-                line = lower(string.strip(utils.re_comments.sub('', line)));
+                line = utils.re_comments.sub('', line).lower().strip();
                 if line != "":
                     self.group_maint[line] = 1;
             file.close();
@@ -67,23 +66,23 @@ class nmu_p:
         changes = pkg.changes;
         dsc = pkg.dsc;
 
-        (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (lower(dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"])));
+        (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]).lower());
         # changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry
-        if dsc_name == lower(changes["maintainername"]) and \
-           (changes["changedby822"] == "" or lower(changes["changedbyname"]) == dsc_name):
+        if dsc_name == changes["maintainername"].lower() and \
+           (changes["changedby822"] == "" or changes["changedbyname"].lower() == dsc_name):
             return 0;
 
         if dsc.has_key("uploaders"):
-            uploaders = string.split(lower(dsc["uploaders"]), ",");
+            uploaders = dsc["uploaders"].lower().split(",");
             uploadernames = {};
             for i in uploaders:
-                (rfc822, name, email) = utils.fix_maintainer (string.strip(i));
+                (rfc822, name, email) = utils.fix_maintainer (i.strip());
                 uploadernames[name] = "";
-            if uploadernames.has_key(lower(changes["changedbyname"])):
+            if uploadernames.has_key(changes["changedbyname"].lower()):
                 return 0;
 
         # Some group maintained packages (e.g. Debian QA) are never NMU's
-        if self.group_maint.has_key(lower(changes["maintaineremail"])):
+        if self.group_maint.has_key(changes["maintaineremail"].lower()):
             return 0;
 
         return 1;
@@ -212,7 +211,7 @@ class Katie:
         if not changes.has_key("maintainer822"):
             changes["maintainer822"] = self.Cnf["Dinstall::MyEmailAddress"];
 
-        Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
+        Subst["__ARCHITECTURE__"] = " ".join(changes["architecture"].keys());
         Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file);
         Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
 
@@ -226,7 +225,7 @@ class Katie:
             Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
             Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
         if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
-            Subst["__MAINTAINER_TO__"] = Subst["__MAINTAINER_TO__"] + "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
+            Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
 
         # Apply any global override of the Maintainer field
         if self.Cnf.get("Dinstall::OverrideMaintainer"):
@@ -255,18 +254,18 @@ class Katie:
         for file in file_keys:
             if files[file].has_key("byhand"):
                 byhand = 1
-                summary = summary + file + " byhand\n"
+                summary += file + " byhand\n"
             elif files[file].has_key("new"):
                 new = 1
-                summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
+                summary += "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
                 if files[file].has_key("othercomponents"):
-                    summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
+                    summary += "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
                 if files[file]["type"] == "deb":
-                    summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)))["Description"] + '\n';
+                    summary += apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)))["Description"] + '\n';
             else:
                 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
                 destination = self.Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
-                summary = summary + file + "\n  to " + destination + "\n"
+                summary += file + "\n  to " + destination + "\n"
 
         short_summary = summary;
 
@@ -274,9 +273,9 @@ class Katie:
         f = re_fdnic.sub("\n .\n", changes.get("changes",""));
 
         if byhand or new:
-            summary = summary + "Changes: " + f;
+            summary += "Changes: " + f;
 
-        summary = summary + self.announce(short_summary, 0)
+        summary += self.announce(short_summary, 0)
 
         return (summary, short_summary);
 
@@ -294,37 +293,54 @@ class Katie:
 
         bugs.sort();
         if not self.nmu.is_an_nmu(self.pkg):
-            summary = summary + "Closing bugs: ";
-            for bug in bugs:
-                summary = summary + "%s " % (bug);
-                if action:
-                    Subst["__BUG_NUMBER__"] = bug;
-                    if changes["distribution"].has_key("stable"):
-                        Subst["__STABLE_WARNING__"] = """
+            if changes["distribution"].has_key("experimental"):
+               # tag bugs as fixed-in-experimental for uploads to experimental
+               summary += "Setting bugs to severity fixed: ";
+               control_message = "";
+               for bug in bugs:
+                   summary += "%s " % (bug);
+                   control_message += "tag %s + fixed-in-experimental\n" % (bug);
+               if action and control_message != "":
+                   Subst["__CONTROL_MESSAGE__"] = control_message;
+                   mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/jennifer.bug-experimental-fixed");
+                   utils.send_mail (mail_message);
+               if action:
+                   self.Logger.log(["setting bugs to fixed"]+bugs);
+
+
+           else:
+               summary += "Closing bugs: ";
+               for bug in bugs:
+                   summary += "%s " % (bug);
+                   if action:
+                       Subst["__BUG_NUMBER__"] = bug;
+                       if changes["distribution"].has_key("stable"):
+                           Subst["__STABLE_WARNING__"] = """
 Note that this package is not part of the released stable Debian
 distribution.  It may have dependencies on other unreleased software,
 or other instabilities.  Please take care if you wish to install it.
 The update will eventually make its way into the next released Debian
 distribution.""";
-                    else:
-                        Subst["__STABLE_WARNING__"] = "";
-                    mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/jennifer.bug-close");
-                    utils.send_mail (mail_message, "");
-            if action:
-                self.Logger.log(["closing bugs"]+bugs);
-        else:                     # NMU
-            summary = summary + "Setting bugs to severity fixed: ";
+                       else:
+                           Subst["__STABLE_WARNING__"] = "";
+                           mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/jennifer.bug-close");
+                           utils.send_mail (mail_message);
+                       if action:
+                           self.Logger.log(["closing bugs"]+bugs);
+
+       else:                     # NMU
+            summary += "Setting bugs to severity fixed: ";
             control_message = "";
             for bug in bugs:
-                summary = summary + "%s " % (bug);
-                control_message = control_message + "tag %s + fixed\n" % (bug);
+                summary += "%s " % (bug);
+                control_message += "tag %s + fixed\n" % (bug);
             if action and control_message != "":
                 Subst["__CONTROL_MESSAGE__"] = control_message;
                 mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/jennifer.bug-nmu-fixed");
-                utils.send_mail (mail_message, "");
+                utils.send_mail (mail_message);
             if action:
                 self.Logger.log(["setting bugs to fixed"]+bugs);
-        summary = summary + "\n";
+        summary += "\n";
         return summary;
 
     ###########################################################################
@@ -347,14 +363,14 @@ distribution.""";
             if list == "" or lists_done.has_key(list):
                 continue;
             lists_done[list] = 1;
-            summary = summary + "Announcing to %s\n" % (list);
+            summary += "Announcing to %s\n" % (list);
 
             if action:
                 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
                 if Cnf.get("Dinstall::TrackingServer") and changes["architecture"].has_key("source"):
                     Subst["__ANNOUNCE_LIST_ADDRESS__"] = Subst["__ANNOUNCE_LIST_ADDRESS__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"]);
                 mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/jennifer.announce");
-                utils.send_mail (mail_message, "");
+                utils.send_mail (mail_message);
 
         if Cnf.FindB("Dinstall::CloseBugs"):
             summary = self.close_bugs(summary, action);
@@ -378,8 +394,8 @@ distribution.""";
         file_keys = files.keys();
         for file in file_keys:
             utils.move(file, Cnf["Dir::Queue::Accepted"]);
-            self.accept_bytes = self.accept_bytes + float(files[file]["size"])
-        self.accept_count = self.accept_count + 1;
+            self.accept_bytes += float(files[file]["size"])
+        self.accept_count += 1;
 
         # Send accept mail, announce to lists, close bugs and check for
         # override disparities
@@ -387,7 +403,7 @@ distribution.""";
             Subst["__SUITE__"] = "";
             Subst["__SUMMARY__"] = summary;
             mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/jennifer.accepted");
-            utils.send_mail(mail_message, "")
+            utils.send_mail(mail_message)
             self.announce(short_summary, 1)
 
         # Special support to enable clean auto-building of accepted packages
@@ -415,7 +431,7 @@ distribution.""";
             if self.pkg.orig_tar_id:
                 # Determine the .orig.tar.gz file name
                 for dsc_file in self.pkg.dsc_files.keys():
-                    if dsc_file[-12:] == ".orig.tar.gz":
+                    if dsc_file.endswith(".orig.tar.gz"):
                         filename = dsc_file;
                 dest = os.path.join(dest_dir, filename);
                 # If it doesn't exist, create a symlink
@@ -457,29 +473,30 @@ distribution.""";
             if not files[file].has_key("new") and files[file]["type"] == "deb":
                 section = files[file]["section"];
                 override_section = files[file]["override section"];
-                if lower(section) != lower(override_section) and section != "-":
+                if section.lower() != override_section.lower() and section != "-":
                     # Ignore this; it's a common mistake and not worth whining about
-                    if lower(section) == "non-us/main" and lower(override_section) == "non-us":
+                    if section.lower() == "non-us/main" and override_section.lower() == "non-us":
                         continue;
-                    summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
+                    summary += "%s: package says section is %s, override says %s.\n" % (file, section, override_section);
                 priority = files[file]["priority"];
                 override_priority = files[file]["override priority"];
                 if priority != override_priority and priority != "-":
-                    summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
+                    summary += "%s: package says priority is %s, override says %s.\n" % (file, priority, override_priority);
 
         if summary == "":
             return;
 
         Subst["__SUMMARY__"] = summary;
         mail_message = utils.TemplateSubst(Subst,self.Cnf["Dir::Templates"]+"/jennifer.override-disparity");
-        utils.send_mail (mail_message, "");
+        utils.send_mail(mail_message);
 
     ###########################################################################
 
-    def force_move (self, files):
-        """Forcefully move files from the current directory to the reject
-           directory.  If any file already exists it will be moved to the
-           morgue to make way for the new file."""
+    def force_reject (self, files):
+        """Forcefully move files from the current directory to the
+           reject directory.  If any file already exists in the reject
+           directory it will be moved to the morgue to make way for
+           the new file."""
 
         Cnf = self.Cnf
 
@@ -528,18 +545,18 @@ distribution.""";
             while answer == 'E':
                 os.system("%s %s" % (editor, temp_filename))
                 file = utils.open_file(temp_filename);
-                reject_message = string.join(file.readlines());
+                reject_message = "".join(file.readlines());
                 file.close();
                 print "Reject message:";
-                print utils.prefix_multi_line_string(reject_message,"  ");
+                print utils.prefix_multi_line_string(reject_message,"  ",include_blank_lines=1);
                 prompt = "[R]eject, Edit, Abandon, Quit ?"
                 answer = "XXX";
-                while string.find(prompt, answer) == -1:
+                while prompt.find(answer) == -1:
                     answer = utils.our_raw_input(prompt);
                     m = re_default_answer.search(prompt);
                     if answer == "":
                         answer = m.group(1);
-                    answer = string.upper(answer[:1]);
+                    answer = answer[:1].upper();
             os.unlink(temp_filename);
             if answer == 'A':
                 return 1;
@@ -557,7 +574,7 @@ distribution.""";
 
         # Move all the files into the reject directory
         reject_files = pkg.files.keys() + [pkg.changes_file];
-        self.force_move(reject_files);
+        self.force_reject(reject_files);
 
         # If we fail here someone is probably trying to exploit the race
         # so let's just raise an exception ...
@@ -587,7 +604,7 @@ distribution.""";
 
         # Send the rejection mail if appropriate
         if not Cnf["Dinstall::Options::No-Mail"]:
-            utils.send_mail (reject_mail_message, "");
+            utils.send_mail(reject_mail_message);
 
         self.Logger.log(["rejected", pkg.changes_file]);
         return 0;
@@ -601,28 +618,49 @@ distribution.""";
     # (2) Bin-only NMU of an MU            => 1.0-3.0.1
     # (3) Bin-only NMU of a sourceful-NMU  => 1.0-3.1.1
 
-    def source_exists (self, package, source_version):
-        q = self.projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (package));
-
-        # Reduce the query results to a list of version numbers
-        ql = map(lambda x: x[0], q.getresult());
-
-        # Try (1)
-        if ql.count(source_version):
-            return 1;
-
-        # Try (2)
-        orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version);
-        if ql.count(orig_source_version):
-            return 1;
-
-        # Try (3)
-        orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version);
-        if ql.count(orig_source_version):
-            return 1;
-
-        # No source found...
-        return 0;
+    def source_exists (self, package, source_version, suites = ["any"]):
+       okay = 1
+       for suite in suites:
+           if suite == "any":
+               que = "SELECT s.version FROM source s WHERE s.source = '%s'" % \
+                   (package)
+           else:
+               # source must exist in suite X, or in some other suite that's
+               # mapped to X, recursively... silent-maps are counted too,
+               # unreleased-maps aren't.
+               maps = self.Cnf.ValueList("SuiteMappings")[:]
+               maps.reverse()
+               maps = [ m.split() for m in maps ]
+               maps = [ (x[1], x[2]) for x in maps 
+                               if x[0] == "map" or x[0] == "silent-map" ]
+               s = [suite]
+               for x in maps:
+                       if x[1] in s and x[0] not in s:
+                               s.append(x[0])
+               
+               que = "SELECT s.version FROM source s JOIN src_associations sa ON (s.id = sa.source) JOIN suite su ON (sa.suite = su.id) WHERE s.source = '%s' AND (%s)" % (package, string.join(["su.suite_name = '%s'" % a for a in s], " OR "));
+            q = self.projectB.query(que)
+
+            # Reduce the query results to a list of version numbers
+            ql = map(lambda x: x[0], q.getresult());
+
+            # Try (1)
+            if ql.count(source_version):
+                continue
+
+            # Try (2)
+            orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version)
+            if ql.count(orig_source_version):
+                continue
+
+            # Try (3)
+            orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version)
+            if ql.count(orig_source_version):
+                continue
+
+            # No source found...
+            okay = 0
+       return okay
 
     ################################################################################
 
@@ -646,7 +684,7 @@ distribution.""";
         type_id = db_access.get_override_type_id(type);
 
         # FIXME: nasty non-US speficic hack
-        if lower(component[:7]) == "non-us/":
+        if component[:7].lower() == "non-us/":
             component = component[7:];
 
         q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND type = %s AND o.section = s.id AND o.priority = p.id"
@@ -654,9 +692,10 @@ distribution.""";
         result = q.getresult();
         # If checking for a source package fall back on the binary override type
         if type == "dsc" and not result:
-            type_id = db_access.get_override_type_id("deb");
-            q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND type = %s AND o.section = s.id AND o.priority = p.id"
-                               % (package, suite_id, component_id, type_id));
+            deb_type_id = db_access.get_override_type_id("deb");
+            udeb_type_id = db_access.get_override_type_id("udeb");
+            q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND (type = %s OR type = %s) AND o.section = s.id AND o.priority = p.id"
+                               % (package, suite_id, component_id, deb_type_id, udeb_type_id));
             result = q.getresult();
 
         # Remember the section and priority so we can check them later if appropriate
@@ -673,8 +712,8 @@ distribution.""";
             # Unlike other rejects we add new lines first to avoid trailing
             # new lines when this message is passed back up to a caller.
             if self.reject_message:
-                self.reject_message = self.reject_message + "\n";
-            self.reject_message = self.reject_message + prefix + str;
+                self.reject_message += "\n";
+            self.reject_message += prefix + str;
 
     ################################################################################
 
@@ -685,8 +724,8 @@ distribution.""";
 
         # Check versions for each target suite
         for target_suite in self.pkg.changes["distribution"].keys():
-            must_be_newer_than = map(lower, self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeNewerThan" % (target_suite)));
-            must_be_older_than = map(lower, self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeOlderThan" % (target_suite)));
+            must_be_newer_than = map(string.lower, self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeNewerThan" % (target_suite)));
+            must_be_older_than = map(string.lower, self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeOlderThan" % (target_suite)));
             # Enforce "must be newer than target suite" even if conffile omits it
             if target_suite not in must_be_newer_than:
                 must_be_newer_than.append(target_suite);
@@ -745,6 +784,14 @@ SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
 
     ################################################################################
 
+    # **WARNING**
+    # NB: this function can remove entries from the 'files' index [if
+    # the .orig.tar.gz is a duplicate of the one in the archive]; if
+    # you're iterating over 'files' and call this function as part of
+    # the loop, be sure to add a check to the top of the loop to
+    # ensure you haven't just tried to derefernece the deleted entry.
+    # **WARNING**
+
     def check_dsc_against_db(self, file):
         self.reject_message = "";
         files = self.pkg.files;
@@ -762,9 +809,16 @@ SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
                 actual_size = int(files[dsc_file]["size"]);
                 found = "%s in incoming" % (dsc_file)
                 # Check the file does not already exist in the archive
-                q = self.projectB.query("SELECT f.size, f.md5sum FROM files f, location l WHERE (f.filename ~ '/%s$' OR f.filename = '%s') AND l.id = f.location" % (utils.regex_safe(dsc_file), dsc_file));
+                q = self.projectB.query("SELECT size, md5sum, filename FROM files WHERE filename LIKE '%%%s%%'" % (dsc_file));
 
-                # "It has not broken them.  It has fixed a
+                ql = q.getresult();
+                # Strip out anything that isn't '%s' or '/%s$'
+                for i in ql:
+                    if i[2] != dsc_file and i[2][-(len(dsc_file)+1):] != '/'+dsc_file:
+                        self.Logger.log(["check_dsc_against_db",i[2],dsc_file]);
+                        ql.remove(i);
+
+                # "[katie] has not broken them.  [katie] has fixed a
                 # brokenness.  Your crappy hack exploited a bug in
                 # the old dinstall.
                 #
@@ -773,11 +827,10 @@ SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
                 # the same name and version.)"
                 #                        -- ajk@ on d-devel@l.d.o
 
-                ql = q.getresult();
                 if ql:
                     # Ignore exact matches for .orig.tar.gz
                     match = 0;
-                    if dsc_file[-12:] == ".orig.tar.gz":
+                    if dsc_file.endswith(".orig.tar.gz"):
                         for i in ql:
                             if files.has_key(dsc_file) and \
                                int(files[dsc_file]["size"]) == int(i[0]) and \
@@ -788,10 +841,15 @@ SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
 
                     if not match:
                         self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file));
-            elif dsc_file[-12:] == ".orig.tar.gz":
+            elif dsc_file.endswith(".orig.tar.gz"):
                 # Check in the pool
-                q = self.projectB.query("SELECT l.path, f.filename, l.type, f.id, l.id FROM files f, location l WHERE (f.filename ~ '/%s$' OR f.filename = '%s') AND l.id = f.location" % (utils.regex_safe(dsc_file), dsc_file));
+                q = self.projectB.query("SELECT l.path, f.filename, l.type, f.id, l.id FROM files f, location l WHERE f.filename LIKE '%%%s%%' AND l.id = f.location" % (dsc_file));
                 ql = q.getresult();
+                # Strip out anything that isn't '%s' or '/%s$'
+                for i in ql:
+                    if i[1] != dsc_file and i[1][-(len(dsc_file)+1):] != '/'+dsc_file:
+                        self.Logger.log(["check_dsc_against_db",i[1],dsc_file]);
+                        ql.remove(i);
 
                 if ql:
                     # Unfortunately, we make get more than one