]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/queue.py
Merge branch 'master' into dbtests
[dak.git] / daklib / queue.py
index 0433a9648883163f81fe5f0d4be448ed8456bf6f..646d89c508a349193ee9ab2033af30f56098249a 100755 (executable)
@@ -6,7 +6,7 @@ Queue utility functions for dak
 
 @contact: Debian FTP Master <ftpmaster@debian.org>
 @copyright: 2001 - 2006 James Troup <james@nocrew.org>
-@copyright: 2009  Joerg Jaspert <joerg@debian.org>
+@copyright: 2009, 2010  Joerg Jaspert <joerg@debian.org>
 @license: GNU General Public License version 2 or later
 """
 
@@ -53,9 +53,18 @@ from dbconn import *
 from summarystats import SummaryStats
 from utils import parse_changes, check_dsc_files
 from textutils import fix_maintainer
-from binary import Binary
 from lintian import parse_lintian_output, generate_reject_messages
 
+# suppress some deprecation warnings in squeeze related to apt_pkg
+# module
+import warnings
+warnings.filterwarnings('ignore', \
+    "apt_pkg.ParseSection\(\) is deprecated. Please see apt_pkg\.TagSection\(\) for the replacement\.", \
+    DeprecationWarning)
+warnings.filterwarnings('ignore', \
+    "Attribute 'Find' of the 'apt_pkg\.TagSection' object is deprecated, use 'find' instead\.", \
+    DeprecationWarning)
+
 ###############################################################################
 
 def get_type(f, session):
@@ -78,6 +87,7 @@ def get_type(f, session):
     elif re_source_ext.match(f["type"]):
         file_type = "dsc"
     else:
+        file_type = f["type"]
         utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
 
     # Validate the override type
@@ -91,10 +101,13 @@ def get_type(f, session):
 
 # Determine what parts in a .changes are NEW
 
-def determine_new(changes, files, warn=1):
+def determine_new(filename, changes, files, warn=1, session = None):
     """
     Determine what parts in a C{changes} file are NEW.
 
+    @type filename: str
+    @param filename: changes filename
+
     @type changes: Upload.Pkg.changes dict
     @param changes: Changes dictionary
 
@@ -108,15 +121,22 @@ def determine_new(changes, files, warn=1):
     @return: dictionary of NEW components.
 
     """
+    # TODO: This should all use the database instead of parsing the changes
+    # file again
     new = {}
+    byhand = {}
 
-    session = DBConn().session()
+    dbchg = get_dbchange(filename, session)
+    if dbchg is None:
+        print "Warning: cannot find changes file in database; won't check byhand"
 
     # Build up a list of potentially new things
     for name, f in files.items():
-        # Skip byhand elements
-#        if f["type"] == "byhand":
-#            continue
+        # Keep a record of byhand elements
+        if f["section"] == "byhand":
+            byhand[name] = 1
+            continue
+
         pkg = f["package"]
         priority = f["priority"]
         section = f["section"]
@@ -151,19 +171,40 @@ def determine_new(changes, files, warn=1):
     # Fix up the list of target suites
     cnf = Config()
     for suite in changes["suite"].keys():
-        override = cnf.Find("Suite::%s::OverrideSuite" % (suite))
-        if override:
-            (olderr, newerr) = (get_suite(suite, session) == None,
-                                get_suite(override, session) == None)
-            if olderr or newerr:
-                (oinv, newinv) = ("", "")
-                if olderr: oinv = "invalid "
-                if newerr: ninv = "invalid "
-                print "warning: overriding %ssuite %s to %ssuite %s" % (
-                        oinv, suite, ninv, override)
-            del changes["suite"][suite]
-            changes["suite"][override] = 1
+        oldsuite = get_suite(suite, session)
+        if not oldsuite:
+            print "WARNING: Invalid suite %s found" % suite
+            continue
+
+        if oldsuite.overridesuite:
+            newsuite = get_suite(oldsuite.overridesuite, session)
+
+            if newsuite:
+                print "INFORMATION: Using overrides from suite %s instead of suite %s" % (
+                    oldsuite.overridesuite, suite)
+                del changes["suite"][suite]
+                changes["suite"][oldsuite.overridesuite] = 1
+            else:
+                print "WARNING: Told to use overridesuite %s for %s but it doesn't exist.  Bugger" % (
+                    oldsuite.overridesuite, suite)
 
+    # Check for unprocessed byhand files
+    if dbchg is not None:
+        for b in byhand.keys():
+            # Find the file entry in the database
+            found = False
+            for f in dbchg.files:
+                if f.filename == b:
+                    found = True
+                    # If it's processed, we can ignore it
+                    if f.processed:
+                        del byhand[b]
+                    break
+
+            if not found:
+                print "Warning: Couldn't find BYHAND item %s in the database; assuming unprocessed"
+
+    # Check for new stuff
     for suite in changes["suite"].keys():
         for pkg in new.keys():
             ql = get_override(pkg, suite, new[pkg]["component"], new[pkg]["type"], session)
@@ -181,13 +222,11 @@ def determine_new(changes, files, warn=1):
             if new[pkg].has_key("othercomponents"):
                 print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"])
 
-    session.close()
-
-    return new
+    return new, byhand
 
 ################################################################################
 
-def check_valid(new):
+def check_valid(new, session = None):
     """
     Check if section and priority for NEW packages exist in database.
     Additionally does sanity checks:
@@ -204,13 +243,13 @@ def check_valid(new):
         priority_name = new[pkg]["priority"]
         file_type = new[pkg]["type"]
 
-        section = get_section(section_name)
+        section = get_section(section_name, session)
         if section is None:
             new[pkg]["section id"] = -1
         else:
             new[pkg]["section id"] = section.section_id
 
-        priority = get_priority(priority_name)
+        priority = get_priority(priority_name, session)
         if priority is None:
             new[pkg]["priority id"] = -1
         else:
@@ -231,17 +270,6 @@ def check_valid(new):
 
 ###############################################################################
 
-def check_status(files):
-    new = byhand = 0
-    for f in files.keys():
-        if files[f].has_key("byhand"):
-            byhand = 1
-        elif files[f].has_key("new"):
-            new = 1
-    return (new, byhand)
-
-###############################################################################
-
 # Used by Upload.check_timestamps
 class TarTime(object):
     def __init__(self, future_cutoff, past_cutoff):
@@ -261,6 +289,134 @@ class TarTime(object):
 
 ###############################################################################
 
+def prod_maintainer(notes, upload):
+    cnf = Config()
+
+    # Here we prepare an editor and get them ready to prod...
+    (fd, temp_filename) = utils.temp_filename()
+    temp_file = os.fdopen(fd, 'w')
+    for note in notes:
+        temp_file.write(note.comment)
+    temp_file.close()
+    editor = os.environ.get("EDITOR","vi")
+    answer = 'E'
+    while answer == 'E':
+        os.system("%s %s" % (editor, temp_filename))
+        temp_fh = utils.open_file(temp_filename)
+        prod_message = "".join(temp_fh.readlines())
+        temp_fh.close()
+        print "Prod message:"
+        print utils.prefix_multi_line_string(prod_message,"  ",include_blank_lines=1)
+        prompt = "[P]rod, 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()
+    os.unlink(temp_filename)
+    if answer == 'A':
+        return
+    elif answer == 'Q':
+        end()
+        sys.exit(0)
+    # Otherwise, do the proding...
+    user_email_address = utils.whoami() + " <%s>" % (
+        cnf["Dinstall::MyAdminAddress"])
+
+    Subst = upload.Subst
+
+    Subst["__FROM_ADDRESS__"] = user_email_address
+    Subst["__PROD_MESSAGE__"] = prod_message
+    Subst["__CC__"] = "Cc: " + cnf["Dinstall::MyEmailAddress"]
+
+    prod_mail_message = utils.TemplateSubst(
+        Subst,cnf["Dir::Templates"]+"/process-new.prod")
+
+    # Send the prod mail
+    utils.send_mail(prod_mail_message)
+
+    print "Sent prodding message"
+
+################################################################################
+
+def edit_note(note, upload, session, trainee=False):
+    # Write the current data to a temporary file
+    (fd, temp_filename) = utils.temp_filename()
+    editor = os.environ.get("EDITOR","vi")
+    answer = 'E'
+    while answer == 'E':
+        os.system("%s %s" % (editor, temp_filename))
+        temp_file = utils.open_file(temp_filename)
+        newnote = temp_file.read().rstrip()
+        temp_file.close()
+        print "New Note:"
+        print utils.prefix_multi_line_string(newnote,"  ")
+        prompt = "[D]one, 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()
+    os.unlink(temp_filename)
+    if answer == 'A':
+        return
+    elif answer == 'Q':
+        end()
+        sys.exit(0)
+
+    comment = NewComment()
+    comment.package = upload.pkg.changes["source"]
+    comment.version = upload.pkg.changes["version"]
+    comment.comment = newnote
+    comment.author  = utils.whoami()
+    comment.trainee = trainee
+    session.add(comment)
+    session.commit()
+
+###############################################################################
+
+# suite names DMs can upload to
+dm_suites = ['unstable', 'experimental']
+
+def get_newest_source(source, session):
+    'returns the newest DBSource object in dm_suites'
+    ## the most recent version of the package uploaded to unstable or
+    ## experimental includes the field "DM-Upload-Allowed: yes" in the source
+    ## section of its control file
+    q = session.query(DBSource).filter_by(source = source). \
+        filter(DBSource.suites.any(Suite.suite_name.in_(dm_suites))). \
+        order_by(desc('source.version'))
+    return q.first()
+
+def get_suite_version_by_source(source, session):
+    'returns a list of tuples (suite_name, version) for source package'
+    q = session.query(Suite.suite_name, DBSource.version). \
+        join(Suite.sources).filter_by(source = source)
+    return q.all()
+
+def get_source_by_package_and_suite(package, suite_name, session):
+    '''
+    returns a DBSource query filtered by DBBinary.package and this package's
+    suite_name
+    '''
+    return session.query(DBSource). \
+        join(DBSource.binaries).filter_by(package = package). \
+        join(DBBinary.suites).filter_by(suite_name = suite_name)
+
+def get_suite_version_by_package(package, arch_string, session):
+    '''
+    returns a list of tuples (suite_name, version) for binary package and
+    arch_string
+    '''
+    return session.query(Suite.suite_name, DBBinary.version). \
+        join(Suite.binaries).filter_by(package = package). \
+        join(DBBinary.architecture). \
+        filter(Architecture.arch_string.in_([arch_string, 'all'])).all()
+
 class Upload(object):
     """
     Everything that has to do with an upload processed.
@@ -288,6 +444,8 @@ class Upload(object):
         self.warnings = []
         self.notes = []
 
+        self.later_check_files = []
+
         self.pkg.reset()
 
     def package_info(self):
@@ -305,7 +463,7 @@ class Upload(object):
         for title, messages in msgs:
             if messages:
                 msg += '\n\n%s:\n%s' % (title, '\n'.join(messages))
-        msg += '\n'
+        msg += '\n\n'
 
         return msg
 
@@ -341,8 +499,18 @@ class Upload(object):
             self.Subst["__MAINTAINER_TO__"] = self.pkg.changes["maintainer2047"]
             self.Subst["__MAINTAINER__"] = self.pkg.changes.get("maintainer", "Unknown")
 
-        if "sponsoremail" in self.pkg.changes:
-            self.Subst["__MAINTAINER_TO__"] += ", %s" % self.pkg.changes["sponsoremail"]
+        # Process policy doesn't set the fingerprint field and I don't want to make it
+        # do it for now as I don't want to have to deal with the case where we accepted
+        # the package into PU-NEW, but the fingerprint has gone away from the keyring in
+        # the meantime so the package will be remarked as rejectable.  Urgh.
+        # TODO: Fix this properly
+        if self.pkg.changes.has_key('fingerprint'):
+            session = DBConn().session()
+            fpr = get_fingerprint(self.pkg.changes['fingerprint'], session)
+            if fpr and self.check_if_upload_is_sponsored("%s@debian.org" % fpr.uid.uid, fpr.uid.name):
+                if self.pkg.changes.has_key("sponsoremail"):
+                    self.Subst["__MAINTAINER_TO__"] += ", %s" % self.pkg.changes["sponsoremail"]
+            session.close()
 
         if cnf.has_key("Dinstall::TrackingServer") and self.pkg.changes.has_key("source"):
             self.Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (self.pkg.changes["source"], cnf["Dinstall::TrackingServer"])
@@ -355,12 +523,19 @@ class Upload(object):
         self.Subst["__REJECT_MESSAGE__"] = self.package_info()
         self.Subst["__SOURCE__"] = self.pkg.changes.get("source", "Unknown")
         self.Subst["__VERSION__"] = self.pkg.changes.get("version", "Unknown")
+        self.Subst["__SUITE__"] = ", ".join(self.pkg.changes["distribution"])
 
     ###########################################################################
     def load_changes(self, filename):
         """
+        Load a changes file and setup a dictionary around it. Also checks for mandantory
+        fields  within.
+
+        @type filename: string
+        @param filename: Changes filename, full path.
+
         @rtype: boolean
-        @rvalue: whether the changes file was valid or not.  We may want to
+        @return: whether the changes file was valid or not.  We may want to
                  reject even if this is True (see what gets put in self.rejects).
                  This is simply to prevent us even trying things later which will
                  fail because we couldn't properly parse the file.
@@ -457,7 +632,7 @@ class Upload(object):
 
         # Check the .changes is non-empty
         if not self.pkg.files:
-            self.rejects.append("%s: nothing to do (Files field is empty)." % (base_filename))
+            self.rejects.append("%s: nothing to do (Files field is empty)." % (os.path.basename(self.pkg.changes_file)))
             return False
 
         # Changes was syntactically valid even if we'll reject
@@ -572,8 +747,8 @@ class Upload(object):
         architecture = control.Find("Architecture")
         upload_suite = self.pkg.changes["distribution"].keys()[0]
 
-        if      architecture not in [a.arch_string for a in get_suite_architectures(default_suite, session)] \
-            and architecture not in [a.arch_string for a in get_suite_architectures(upload_suite, session)]:
+        if      architecture not in [a.arch_string for a in get_suite_architectures(default_suite, session = session)] \
+            and architecture not in [a.arch_string for a in get_suite_architectures(upload_suite, session = session)]:
             self.rejects.append("Unknown architecture '%s'." % (architecture))
 
         # Ensure the architecture of the .deb is one of the ones
@@ -667,7 +842,8 @@ class Upload(object):
                                     (source_version, f, self.pkg.changes["version"]))
         else:
             # Check in the SQL database
-            if not source_exists(source_package, source_version, self.pkg.changes["distribution"].keys(), session):
+            if not source_exists(source_package, source_version, suites = \
+                self.pkg.changes["distribution"].keys(), session = session):
                 # Check in one of the other directories
                 source_epochless_version = re_no_epoch.sub('', source_version)
                 dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
@@ -689,13 +865,6 @@ class Upload(object):
         # Check the version and for file overwrites
         self.check_binary_against_db(f, session)
 
-        # Temporarily disable contents generation until we change the table storage layout
-        #b = Binary(f)
-        #b.scan_package()
-        #if len(b.rejects) > 0:
-        #    for j in b.rejects:
-        #        self.rejects.append(j)
-
     def source_file_checks(self, f, session):
         entry = self.pkg.files[f]
 
@@ -782,7 +951,7 @@ class Upload(object):
         location = cnf["Dir::Pool"]
         l = get_location(location, entry["component"], session=session)
         if l is None:
-            self.rejects.append("[INTERNAL ERROR] couldn't determine location (Component: %)" % entry["component"])
+            self.rejects.append("[INTERNAL ERROR] couldn't determine location (Component: %s)" % entry["component"])
             entry["location id"] = -1
         else:
             entry["location id"] = l.location_id
@@ -805,9 +974,11 @@ class Upload(object):
 
         # Check for packages that have moved from one component to another
         entry['suite'] = suite
-        res = get_binary_components(self.pkg.files[f]['package'], suite, entry["architecture"], session)
-        if res.rowcount > 0:
-            entry["othercomponents"] = res.fetchone()[0]
+        arch_list = [entry["architecture"], 'all']
+        component = get_component_by_package_suite(self.pkg.files[f]['package'], \
+            [suite], arch_list = arch_list, session = session)
+        if component is not None:
+            entry["othercomponents"] = component
 
     def check_files(self, action=True):
         file_keys = self.pkg.files.keys()
@@ -820,8 +991,7 @@ class Upload(object):
             for f in file_keys:
                 ret = holding.copy_to_holding(f)
                 if ret is not None:
-                    # XXX: Should we bail out here or try and continue?
-                    self.rejects.append(ret)
+                    self.warnings.append('Could not copy %s to holding; will attempt to find in DB later' % f)
 
             os.chdir(cwd)
 
@@ -863,7 +1033,9 @@ class Upload(object):
                     if os.path.exists(f):
                         self.rejects.append("Can't read `%s'. [permission denied]" % (f))
                     else:
-                        self.rejects.append("Can't read `%s'. [file not found]" % (f))
+                        # Don't directly reject, mark to check later to deal with orig's
+                        # we can find in the pool
+                        self.later_check_files.append(f)
                 entry["type"] = "unreadable"
                 continue
 
@@ -931,7 +1103,7 @@ class Upload(object):
 
         # Parse the .dsc file
         try:
-            self.pkg.dsc.update(utils.parse_changes(dsc_filename, signing_rules=1))
+            self.pkg.dsc.update(utils.parse_changes(dsc_filename, signing_rules=1, dsc_file=1))
         except CantOpenError:
             # if not -n copy_to_holding() will have done this for us...
             if not action:
@@ -1006,6 +1178,23 @@ class Upload(object):
         session = DBConn().session()
         self.check_source_against_db(dsc_filename, session)
         self.check_dsc_against_db(dsc_filename, session)
+
+        dbchg = get_dbchange(self.pkg.changes_file, session)
+
+        # Finally, check if we're missing any files
+        for f in self.later_check_files:
+            print 'XXX: %s' % f
+            # Check if we've already processed this file if we have a dbchg object
+            ok = False
+            if dbchg:
+                for pf in dbchg.files:
+                    if pf.filename == f and pf.processed:
+                        self.notes.append('%s was already processed so we can go ahead' % f)
+                        ok = True
+                        del self.pkg.files[f]
+            if not ok:
+                self.rejects.append("Could not find file %s references in changes" % f)
+
         session.close()
 
         return True
@@ -1089,9 +1278,7 @@ class Upload(object):
     def check_source(self):
         # Bail out if:
         #    a) there's no source
-        # or c) the orig files are MIA
-        if not self.pkg.changes["architecture"].has_key("source") \
-           or len(self.pkg.orig_files) == 0:
+        if not self.pkg.changes["architecture"].has_key("source"):
             return
 
         tmpdir = utils.temp_dirname()
@@ -1225,7 +1412,7 @@ class Upload(object):
             found = False
 
             # Look in the pool
-            for poolfile in get_poolfile_like_name('/%s' % filename, session_):
+            for poolfile in get_poolfile_like_name('%s' % filename, session_):
                 poolfile_path = os.path.join(
                     poolfile.location.path, poolfile.filename
                 )
@@ -1536,22 +1723,13 @@ class Upload(object):
         if rej:
             return
 
-        ## the most recent version of the package uploaded to unstable or
-        ## experimental includes the field "DM-Upload-Allowed: yes" in the source
-        ## section of its control file
-        q = session.query(DBSource).filter_by(source=self.pkg.changes["source"])
-        q = q.join(SrcAssociation)
-        q = q.join(Suite).filter(Suite.suite_name.in_(['unstable', 'experimental']))
-        q = q.order_by(desc('source.version')).limit(1)
+        r = get_newest_source(self.pkg.changes["source"], session)
 
-        r = q.all()
-
-        if len(r) != 1:
+        if r is None:
             rej = "Could not find existing source package %s in unstable or experimental and this is a DM upload" % self.pkg.changes["source"]
             self.rejects.append(rej)
             return
 
-        r = r[0]
         if not r.dm_upload_allowed:
             rej = "Source package %s does not have 'DM-Upload-Allowed: yes' in its most recent version (%s)" % (self.pkg.changes["source"], r.version)
             self.rejects.append(rej)
@@ -1583,11 +1761,7 @@ class Upload(object):
         ## none of the packages are being taken over from other source packages
         for b in self.pkg.changes["binary"].keys():
             for suite in self.pkg.changes["distribution"].keys():
-                q = session.query(DBSource)
-                q = q.join(DBBinary).filter_by(package=b)
-                q = q.join(BinAssociation).join(Suite).filter_by(suite_name=suite)
-
-                for s in q.all():
+                for s in get_source_by_package_and_suite(b, suite, session):
                     if s.source != self.pkg.changes["source"]:
                         self.rejects.append("%s may not hijack %s from source package %s in suite %s" % (fpr.uid.uid, b, s, suite))
 
@@ -1773,7 +1947,9 @@ distribution."""
         self.Subst["__SHORT_SUMMARY__"] = short_summary
 
         for dist in self.pkg.changes["distribution"].keys():
-            announce_list = cnf.Find("Suite::%s::Announce" % (dist))
+            suite = get_suite(dist)
+            if suite is None: continue
+            announce_list = suite.announce
             if announce_list == "" or lists_done.has_key(announce_list):
                 continue
 
@@ -1830,7 +2006,7 @@ distribution."""
         # Add the .dsc file to the DB first
         for newfile, entry in self.pkg.files.items():
             if entry["type"] == "dsc":
-                dsc_component, dsc_location_id, pfs = add_dsc_to_db(self, newfile, session)
+                source, dsc_component, dsc_location_id, pfs = add_dsc_to_db(self, newfile, session)
                 for j in pfs:
                     poolfiles.append(j)
 
@@ -1842,6 +2018,7 @@ distribution."""
         # If this is a sourceful diff only upload that is moving
         # cross-component we need to copy the .orig files into the new
         # component too for the same reasons as above.
+        # XXX: mhy: I think this should be in add_dsc_to_db
         if self.pkg.changes["architecture"].has_key("source"):
             for orig_file in self.pkg.orig_files.keys():
                 if not self.pkg.orig_files[orig_file].has_key("id"):
@@ -1859,21 +2036,48 @@ distribution."""
                 new_filename = os.path.join(utils.poolify(self.pkg.changes["source"], dsc_component), os.path.basename(old_filename))
 
                 # TODO: Care about size/md5sum collisions etc
-                (found, newf) = check_poolfile(new_filename, file_size, file_md5sum, dsc_location_id, session)
+                (found, newf) = check_poolfile(new_filename, old_dat['size'], old_dat['md5sum'], dsc_location_id, session)
 
+                # TODO: Uhm, what happens if newf isn't None - something has gone badly and we should cope
                 if newf is None:
                     utils.copy(old_filename, os.path.join(cnf["Dir::Pool"], new_filename))
                     newf = add_poolfile(new_filename, old_dat, dsc_location_id, session)
 
-                    # TODO: Check that there's only 1 here
-                    source = get_sources_from_name(self.pkg.changes["source"], self.pkg.changes["version"])[0]
-                    dscf = get_dscfiles(source_id=source.source_id, poolfile_id=orig_file_id, session=session)[0]
-                    dscf.poolfile_id = newf.file_id
-                    session.add(dscf)
                     session.flush()
 
+                    # Don't reference the old file from this changes
+                    for p in poolfiles:
+                        if p.file_id == oldf.file_id:
+                            poolfiles.remove(p)
+
                     poolfiles.append(newf)
 
+                    # Fix up the DSC references
+                    toremove = []
+
+                    for df in source.srcfiles:
+                        if df.poolfile.file_id == oldf.file_id:
+                            # Add a new DSC entry and mark the old one for deletion
+                            # Don't do it in the loop so we don't change the thing we're iterating over
+                            newdscf = DSCFile()
+                            newdscf.source_id = source.source_id
+                            newdscf.poolfile_id = newf.file_id
+                            session.add(newdscf)
+
+                            toremove.append(df)
+
+                    for df in toremove:
+                        session.delete(df)
+
+                    # Flush our changes
+                    session.flush()
+
+                    # Make sure that our source object is up-to-date
+                    session.expire(source)
+
+        # Add changelog information to the database
+        self.store_changelog()
+
         # Install the files into the pool
         for newfile, entry in self.pkg.files.items():
             destination = os.path.join(cnf["Dir::Pool"], entry["pool name"], newfile)
@@ -1882,10 +2086,9 @@ distribution."""
             stats.accept_bytes += float(entry["size"])
 
         # Copy the .changes file across for suite which need it.
-        copy_changes = {}
-        for suite_name in self.pkg.changes["distribution"].keys():
-            if cnf.has_key("Suite::%s::CopyChanges" % (suite_name)):
-                copy_changes[cnf["Suite::%s::CopyChanges" % (suite_name)]] = ""
+        copy_changes = dict([(x.copychanges, '')
+                             for x in session.query(Suite).filter(Suite.suite_name.in_(self.pkg.changes["distribution"].keys())).all()
+                             if x.copychanges is not None])
 
         for dest in copy_changes.keys():
             utils.copy(self.pkg.changes_file, os.path.join(cnf["Dir::Root"], dest))
@@ -1902,27 +2105,16 @@ distribution."""
         if self.pkg.changes["architecture"].has_key("source") and cnf.get("Dir::UrgencyLog"):
             UrgencyLog().log(self.pkg.dsc["source"], self.pkg.dsc["version"], self.pkg.changes["urgency"])
 
-        # Send accept mail, announce to lists, close bugs and check for
-        # override disparities
-        if not cnf["Dinstall::Options::No-Mail"]:
-            self.update_subst()
-            self.Subst["__SUITE__"] = ""
-            self.Subst["__SUMMARY__"] = summary
-            mail_message = utils.TemplateSubst(self.Subst,
-                                               os.path.join(cnf["Dir::Templates"], 'process-unchecked.accepted'))
-            utils.send_mail(mail_message)
-            self.announce(short_summary, 1)
+        self.update_subst()
+        self.Subst["__SUMMARY__"] = summary
+        mail_message = utils.TemplateSubst(self.Subst,
+                                           os.path.join(cnf["Dir::Templates"], 'process-unchecked.accepted'))
+        utils.send_mail(mail_message)
+        self.announce(short_summary, 1)
 
         ## Helper stuff for DebBugs Version Tracking
         if cnf.Find("Dir::Queue::BTSVersionTrack"):
-            # ??? once queue/* is cleared on *.d.o and/or reprocessed
-            # the conditionalization on dsc["bts changelog"] should be
-            # dropped.
-
-            # Write out the version history from the changelog
-            if self.pkg.changes["architecture"].has_key("source") and \
-               self.pkg.dsc.has_key("bts changelog"):
-
+            if self.pkg.changes["architecture"].has_key("source"):
                 (fd, temp_filename) = utils.temp_filename(cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
                 version_history = os.fdopen(fd, 'w')
                 version_history.write(self.pkg.dsc["bts changelog"])
@@ -1973,11 +2165,8 @@ distribution."""
 
         cnf = Config()
 
-        # Abandon the check if:
-        #  a) override disparity checks have been disabled
-        #  b) we're not sending mail
-        if not cnf.FindB("Dinstall::OverrideDisparityCheck") or \
-           cnf["Dinstall::Options::No-Mail"]:
+        # Abandon the check if override disparity checks have been disabled
+        if not cnf.FindB("Dinstall::OverrideDisparityCheck"):
             return
 
         summary = self.pkg.check_override()
@@ -2035,8 +2224,8 @@ distribution."""
         directory it will be moved to the morgue to make way for
         the new file.
 
-        @type files: dict
-        @param files: file dictionary
+        @type reject_files: dict
+        @param reject_files: file dictionary
 
         """
 
@@ -2052,17 +2241,17 @@ distribution."""
             try:
                 dest_fd = os.open(dest_file, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0644)
             except OSError, e:
-                # File exists?  Let's try and move it to the morgue
+                # File exists?  Let's find a new name by adding a number
                 if e.errno == errno.EEXIST:
-                    morgue_file = os.path.join(cnf["Dir::Morgue"], cnf["Dir::MorgueReject"], file_entry)
                     try:
-                        morgue_file = utils.find_next_free(morgue_file)
+                        dest_file = utils.find_next_free(dest_file, 255)
                     except NoFreeFilenameError:
                         # Something's either gone badly Pete Tong, or
                         # someone is trying to exploit us.
-                        utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file_entry))
+                        utils.warn("**WARNING** failed to find a free filename for %s in %s." % (file_entry, cnf["Dir::Queue::Reject"]))
                         return
-                    utils.move(dest_file, morgue_file, perms=0660)
+
+                    # Make sure we really got it
                     try:
                         dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
                     except OSError, e:
@@ -2167,9 +2356,8 @@ distribution."""
 
         os.close(reason_fd)
 
-        # Send the rejection mail if appropriate
-        if not cnf["Dinstall::Options::No-Mail"]:
-            utils.send_mail(reject_mail_message)
+        # Send the rejection mail
+        utils.send_mail(reject_mail_message)
 
         if self.logger:
             self.logger.log(["rejected", self.pkg.changes_file])
@@ -2208,8 +2396,9 @@ distribution."""
             file_type = binary_type
 
         # Override suite name; used for example with proposed-updates
-        if cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
-            suite = cnf["Suite::%s::OverrideSuite" % (suite)]
+        oldsuite = get_suite(suite, session)
+        if (not oldsuite is None) and oldsuite.overridesuite:
+            suite = oldsuite.overridesuite
 
         result = get_override(package, suite, component, file_type, session)
 
@@ -2329,12 +2518,10 @@ distribution."""
     ################################################################################
     def check_binary_against_db(self, filename, session):
         # Ensure version is sane
-        q = session.query(BinAssociation)
-        q = q.join(DBBinary).filter(DBBinary.package==self.pkg.files[filename]["package"])
-        q = q.join(Architecture).filter(Architecture.arch_string.in_([self.pkg.files[filename]["architecture"], 'all']))
-
-        self.cross_suite_version_check([ (x.suite.suite_name, x.binary.version) for x in q.all() ],
-                                       filename, self.pkg.files[filename]["version"], sourceful=False)
+        self.cross_suite_version_check( \
+            get_suite_version_by_package(self.pkg.files[filename]["package"], \
+                self.pkg.files[filename]["architecture"], session),
+            filename, self.pkg.files[filename]["version"], sourceful=False)
 
         # Check for any existing copies of the file
         q = session.query(DBBinary).filter_by(package=self.pkg.files[filename]["package"])
@@ -2351,11 +2538,9 @@ distribution."""
         version = self.pkg.dsc.get("version")
 
         # Ensure version is sane
-        q = session.query(SrcAssociation)
-        q = q.join(DBSource).filter(DBSource.source==source)
-
-        self.cross_suite_version_check([ (x.suite.suite_name, x.source.version) for x in q.all() ],
-                                       filename, version, sourceful=True)
+        self.cross_suite_version_check( \
+            get_suite_version_by_source(source, session), filename, version,
+            sourceful=True)
 
     ################################################################################
     def check_dsc_against_db(self, filename, session):
@@ -2421,6 +2606,13 @@ distribution."""
                                 orig_files[dsc_name]["path"] = os.path.join(i.location.path, i.filename)
                                 match = 1
 
+                                # Don't bitch that we couldn't find this file later
+                                try:
+                                    self.later_check_files.remove(dsc_name)
+                                except ValueError:
+                                    pass
+
+
                     if not match:
                         self.rejects.append("can not overwrite existing copy of '%s' already in the archive." % (dsc_name))
 
@@ -2515,7 +2707,8 @@ distribution."""
                 source_version = entry["source version"]
                 source_package = entry["source package"]
                 if not self.pkg.changes["architecture"].has_key("source") \
-                   and not source_exists(source_package, source_version, self.pkg.changes["distribution"].keys(), session):
+                   and not source_exists(source_package, source_version, \
+                    suites = self.pkg.changes["distribution"].keys(), session = session):
                     source_epochless_version = re_no_epoch.sub('', source_version)
                     dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
                     found = False
@@ -2562,7 +2755,9 @@ distribution."""
                 source_version = entry["source version"]
                 source_package = entry["source package"]
                 if not self.pkg.changes["architecture"].has_key("source") \
-                   and not source_exists(source_package, source_version,  self.pkg.changes["distribution"].keys()):
+                   and not source_exists(source_package, source_version, \
+                    suites = self.pkg.changes["distribution"].keys(), \
+                    session = session):
                     self.rejects.append("no source found for %s %s (%s)." % (source_package, source_version, checkfile))
 
             # Version and file overwrite checks
@@ -2591,45 +2786,6 @@ distribution."""
                 if not self.in_override_p(entry["package"], entry["component"], suite, entry.get("dbtype",""), checkfile, session):
                     self.rejects.append("%s is NEW for %s." % (checkfile, suite))
 
-    ################################################################################
-    # This is not really a reject, but an unaccept, but since a) the code for
-    # that is non-trivial (reopen bugs, unannounce etc.), b) this should be
-    # extremely rare, for now we'll go with whining at our admin folks...
-
-    def do_unaccept(self):
-        cnf = Config()
-
-        self.update_subst()
-        self.Subst["__REJECTOR_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
-        self.Subst["__REJECT_MESSAGE__"] = self.package_info()
-        self.Subst["__CC__"] = "Cc: " + cnf["Dinstall::MyEmailAddress"]
-        self.Subst["__BCC__"] = "X-DAK: dak process-accepted"
-        if cnf.has_key("Dinstall::Bcc"):
-            self.Subst["__BCC__"] += "\nBcc: %s" % (cnf["Dinstall::Bcc"])
-
-        template = os.path.join(cnf["Dir::Templates"], "process-accepted.unaccept")
-
-        reject_mail_message = utils.TemplateSubst(self.Subst, template)
-
-        # Write the rejection email out as the <foo>.reason file
-        reason_filename = os.path.basename(self.pkg.changes_file[:-8]) + ".reason"
-        reject_filename = os.path.join(cnf["Dir::Queue::Reject"], reason_filename)
-
-        # If we fail here someone is probably trying to exploit the race
-        # so let's just raise an exception ...
-        if os.path.exists(reject_filename):
-            os.unlink(reject_filename)
-
-        fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
-        os.write(fd, reject_mail_message)
-        os.close(fd)
-
-        utils.send_mail(reject_mail_message)
-
-        del self.Subst["__REJECTOR_ADDRESS__"]
-        del self.Subst["__REJECT_MESSAGE__"]
-        del self.Subst["__CC__"]
-
     ################################################################################
     # If any file of an upload has a recent mtime then chances are good
     # the file is still being uploaded.
@@ -2654,3 +2810,35 @@ distribution."""
 
         os.chdir(cwd)
         return too_new
+
+    def store_changelog(self):
+
+        # Skip binary-only upload if it is not a bin-NMU
+        if not self.pkg.changes['architecture'].has_key('source'):
+            from daklib.regexes import re_bin_only_nmu
+            if not re_bin_only_nmu.search(self.pkg.changes['version']):
+                return
+
+        session = DBConn().session()
+
+        # Check if upload already has a changelog entry
+        query = """SELECT changelog_id FROM changes WHERE source = :source
+                   AND version = :version AND architecture = :architecture AND changelog_id != 0"""
+        if session.execute(query, {'source': self.pkg.changes['source'], \
+                                   'version': self.pkg.changes['version'], \
+                                   'architecture': " ".join(self.pkg.changes['architecture'].keys())}).rowcount:
+            session.commit()
+            return
+
+        # Add current changelog text into changelogs_text table, return created ID
+        query = "INSERT INTO changelogs_text (changelog) VALUES (:changelog) RETURNING id"
+        ID = session.execute(query, {'changelog': self.pkg.changes['changes']}).fetchone()[0]
+
+        # Link ID to the upload available in changes table
+        query = """UPDATE changes SET changelog_id = :id WHERE source = :source
+                   AND version = :version AND architecture = :architecture"""
+        session.execute(query, {'id': ID, 'source': self.pkg.changes['source'], \
+                                'version': self.pkg.changes['version'], \
+                                'architecture': " ".join(self.pkg.changes['architecture'].keys())})
+
+        session.commit()