]> git.decadent.org.uk Git - dak.git/commitdiff
Merge branch 'merge'
authorJoerg Jaspert <joerg@debian.org>
Sat, 18 Apr 2009 21:59:41 +0000 (23:59 +0200)
committerJoerg Jaspert <joerg@debian.org>
Sat, 18 Apr 2009 21:59:41 +0000 (23:59 +0200)
* merge:
  skip arches we should skip
  i think contents are actually working on separate thread now?
  possibly working multithread contents writer that has udebs disabled
  change reject email template to solicit a reply if new files are uploaded
  convert binaries.type to a enum
  some tweaking to the contents generation queries
  strip ./ from front of paths during contents.bootstrap

12 files changed:
config/debian/cron.dinstall
config/debian/dak.conf
dak/dakdb/update11.py [new file with mode: 0755]
dak/dakdb/update12.py [new file with mode: 0755]
dak/process_new.py
dak/queue_report.py
dak/transitions.py
dak/update_db.py
daklib/dak_exceptions.py
daklib/database.py
daklib/queue.py
templates/transition.removed [new file with mode: 0644]

index c730a116c28120978e8bc040f79ec574150de43b..eb9f37d5ca5944500d225a7c9314fd9f701b4218 100755 (executable)
@@ -269,6 +269,12 @@ function expire() {
     $scriptsdir/expire_dumps -d . -p -f "dump_*"
 }
 
+function transitionsclean() {
+    log "Removing out of date transitions..."
+    cd $base
+    dak transitions -c -a
+}
+
 function reports() {
     # Send a report on NEW/BYHAND packages
     log "Nagging ftpteam about NEW/BYHAND packages"
@@ -729,6 +735,14 @@ GO=(
 )
 stage $GO
 
+GO=(
+    FUNC="transitionsclean"
+    TIME="transitionsclean"
+    ARGS=""
+    ERR=""
+)
+stage $GO
+
 GO=(
     FUNC="reports"
     TIME="reports"
@@ -807,7 +821,7 @@ GO=(
     FUNC="aptftpcleanup"
     TIME="apt-ftparchive cleanup"
     ARGS=""
-    ERR=""
+    ERR="false"
 )
 stage $GO
 
index 24282855466fc334e6177303fed695192d82ed91..756a37c3c11ce249ba6fa1f9d1f8cbc4d21ed1b9 100644 (file)
@@ -47,6 +47,7 @@ Dinstall
 
 Transitions
 {
+   Notifications "team@release.debian.org";
    TempPath "/srv/ftp.debian.org/tmp/";
 };
 
@@ -173,6 +174,7 @@ Clean-Suites
 Process-New
 {
   AcceptedLockFile "/srv/ftp.debian.org/lock/unchecked.lock";
+  LockDir "/srv/ftp.debian.org/lock/new/";
 };
 
 Check-Overrides
diff --git a/dak/dakdb/update11.py b/dak/dakdb/update11.py
new file mode 100755 (executable)
index 0000000..53f1572
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# coding=utf8
+
+"""
+Adding process-new comments to the DB
+
+@contact: Debian FTP Master <ftpmaster@debian.org>
+@copyright: 2009  Joerg Jaspert <joerg@debian.org>
+@license: GNU General Public License version 2 or later
+"""
+
+# 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 psycopg2
+import time
+
+################################################################################
+
+def do_update(self):
+    print "Adding process-new comments to the DB"
+
+    try:
+        c = self.db.cursor()
+        c.execute("""CREATE TABLE new_comments (
+                      id SERIAL PRIMARY KEY NOT NULL,
+                      package TEXT NOT NULL,
+                      version TEXT NOT NULL,
+                      comment TEXT NOT NULL,
+                      author TEXT NOT NULL
+                   )""")
+
+        c.execute("GRANT SELECT ON new_comments TO ftptrainee;")
+        c.execute("GRANT INSERT ON new_comments TO ftptrainee;")
+        c.execute("GRANT UPDATE ON new_comments TO ftptrainee;")
+        c.execute("GRANT SELECT ON new_comments TO ftpteam;")
+        c.execute("GRANT INSERT ON new_comments TO ftpteam;")
+        c.execute("GRANT UPDATE ON new_comments TO ftpteam;")
+        c.execute("GRANT ALL ON new_comments TO ftpmaster;")
+
+        c.execute("UPDATE config SET value = '11' WHERE name = 'db_revision'")
+        self.db.commit()
+
+    except psycopg2.ProgrammingError, msg:
+        self.db.rollback()
+        raise DBUpdateError, "Unable to apply process-new comments update, rollback issued. Error message : %s" % (str(msg))
diff --git a/dak/dakdb/update12.py b/dak/dakdb/update12.py
new file mode 100755 (executable)
index 0000000..70a9e18
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# coding=utf8
+
+"""
+Adding a date field to the process-new notes
+
+@contact: Debian FTP Master <ftpmaster@debian.org>
+@copyright: 2009  Joerg Jaspert <joerg@debian.org>
+@license: GNU General Public License version 2 or later
+"""
+
+# 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 psycopg2
+import time
+
+################################################################################
+
+def do_update(self):
+    print "Adding a date field to the process-new notes"
+
+    try:
+        c = self.db.cursor()
+        c.execute("ALTER TABLE new_comments ADD COLUMN notedate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()")
+
+        c.execute("UPDATE config SET value = '12' WHERE name = 'db_revision'")
+        self.db.commit()
+
+    except psycopg2.ProgrammingError, msg:
+        self.db.rollback()
+        raise DBUpdateError, "Unable to apply process-new update 12, rollback issued. Error message : %s" % (str(msg))
index 9ecfcdc6bbc2ade5ca50079f3880319d7fa23756..52e1f4e9780350d722e533a0a179b0f290a4c577 100755 (executable)
@@ -5,6 +5,7 @@
 
 @contact: Debian FTP Master <ftpmaster@debian.org>
 @copyright: 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+@copyright: 2009 Joerg Jaspert <joerg@debian.org>
 @license: GNU General Public License version 2 or later
 """
 # This program is free software; you can redistribute it and/or modify
@@ -40,6 +41,8 @@
 
 ################################################################################
 
+from __future__ import with_statement
+
 import copy
 import errno
 import os
@@ -47,6 +50,8 @@ import readline
 import stat
 import sys
 import time
+import contextlib
+import pwd
 import apt_pkg, apt_inst
 import examine_package
 from daklib import database
@@ -54,6 +59,7 @@ from daklib import logging
 from daklib import queue
 from daklib import utils
 from daklib.regexes import re_no_epoch, re_default_answer, re_isanum
+from daklib.dak_exceptions import CantOpenError, AlreadyLockedError
 
 # Globals
 Cnf = None       #: Configuration, apt_pkg.Configuration
@@ -112,7 +118,7 @@ def recheck():
 
     if reject_message.find("Rejected") != -1:
         answer = "XXX"
-        if Options["No-Action"] or Options["Automatic"]:
+        if Options["No-Action"] or Options["Automatic"] or Options["Trainee"]:
             answer = 'S'
 
         print "REJECT\n" + reject_message,
@@ -219,7 +225,7 @@ def sort_changes(changes_files):
             mtime = os.stat(d["filename"])[stat.ST_MTIME]
             if mtime < oldest:
                 oldest = mtime
-            have_note += (d.has_key("process-new note"))
+            have_note += (database.has_new_comment(d["source"], d["version"]))
         per_source[source]["oldest"] = oldest
         if not have_note:
             per_source[source]["note_state"] = 0; # none
@@ -301,11 +307,10 @@ def print_new (new, indexed, file=sys.stdout):
             line = "%-20s %-20s %-20s" % (pkg, priority, section)
         line = line.strip()+'\n'
         file.write(line)
-    note = Upload.pkg.changes.get("process-new note")
-    if note:
-        print "*"*75
-        print note
-        print "*"*75
+    note = database.get_new_comments(Upload.pkg.changes.get("source"))
+    if len(note) > 0:
+        for line in note:
+            print line
     return broken, note
 
 ################################################################################
@@ -468,18 +473,15 @@ def edit_overrides (new):
 def edit_note(note):
     # Write the current data to a temporary file
     (fd, temp_filename) = utils.temp_filename()
-    temp_file = os.fdopen(fd, 'w')
-    temp_file.write(note)
-    temp_file.close()
     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)
-        note = temp_file.read().rstrip()
+        newnote = temp_file.read().rstrip()
         temp_file.close()
-        print "Note:"
-        print utils.prefix_multi_line_string(note,"  ")
+        print "New Note:"
+        print utils.prefix_multi_line_string(newnote,"  ")
         prompt = "[D]one, Edit, Abandon, Quit ?"
         answer = "XXX"
         while prompt.find(answer) == -1:
@@ -494,8 +496,7 @@ def edit_note(note):
     elif answer == 'Q':
         end()
         sys.exit(0)
-    Upload.pkg.changes["process-new note"] = note
-    Upload.dump_vars(Cnf["Dir::Queue::New"])
+    database.add_new_comment(Upload.pkg.changes["source"], Upload.pkg.changes["version"], newnote, utils.whoami())
 
 ################################################################################
 
@@ -572,16 +573,21 @@ def add_overrides (new):
 
 ################################################################################
 
-def prod_maintainer ():
+def prod_maintainer (note):
     # Here we prepare an editor and get them ready to prod...
     (fd, temp_filename) = utils.temp_filename()
+    temp_file = os.fdopen(fd, 'w')
+    if len(note) > 0:
+        for line in note:
+            temp_file.write(line)
+    temp_file.close()
     editor = os.environ.get("EDITOR","vi")
     answer = 'E'
     while answer == 'E':
         os.system("%s %s" % (editor, temp_filename))
-        f = os.fdopen(fd)
-        prod_message = "".join(f.readlines())
-        f.close()
+        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 ?"
@@ -592,12 +598,12 @@ def prod_maintainer ():
             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)
+    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"])
@@ -680,25 +686,27 @@ def do_new():
                 answer = m.group(1)
             answer = answer[:1].upper()
 
-        if answer == 'A':
+        if answer == 'A' and not Options["Trainee"]:
             done = add_overrides (new)
         elif answer == 'C':
             check_pkg()
-        elif answer == 'E':
+        elif answer == 'E' and not Options["Trainee"]:
             new = edit_overrides (new)
-        elif answer == 'M':
-            aborted = Upload.do_reject(1, Options["Manual-Reject"])
+        elif answer == 'M' and not Options["Trainee"]:
+            aborted = Upload.do_reject(manual=1,
+                                       reject_message=Options["Manual-Reject"],
+                                       note=database.get_new_comments(changes.get("source", "")))
             if not aborted:
                 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
                 done = 1
         elif answer == 'N':
-            edit_note(changes.get("process-new note", ""))
-        elif answer == 'P':
-            prod_maintainer()
-        elif answer == 'R':
+            edit_note(database.get_new_comments(changes.get("source", "")))
+        elif answer == 'P' and not Options["Trainee"]:
+            prod_maintainer(database.get_new_comments(changes.get("source", "")))
+        elif answer == 'R' and not Options["Trainee"]:
             confirm = utils.our_raw_input("Really clear note (y/N)? ").lower()
             if confirm == "y":
-                del changes["process-new note"]
+                database.delete_new_comments(changes.get("source"), changes.get("version"))
         elif answer == 'S':
             done = 1
         elif answer == 'Q':
@@ -716,6 +724,7 @@ def usage (exit_code=0):
   -C, --comments-dir=DIR    use DIR as comments-dir, for [o-]p-u-new
   -m, --manual-reject=MSG   manual reject with `msg'
   -n, --no-action           don't do anything
+  -t, --trainee             FTP Trainee mode
   -V, --version             display the version number and exit"""
     sys.exit(exit_code)
 
@@ -730,9 +739,10 @@ def init():
                  ('h',"help","Process-New::Options::Help"),
                  ('C',"comments-dir","Process-New::Options::Comments-Dir", "HasArg"),
                  ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
+                 ('t',"trainee","Process-New::Options::Trainee"),
                  ('n',"no-action","Process-New::Options::No-Action")]
 
-    for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir"]:
+    for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir", "trainee"]:
         if not Cnf.has_key("Process-New::Options::%s" % (i)):
             Cnf["Process-New::Options::%s" % (i)] = ""
 
@@ -748,7 +758,10 @@ def init():
     Upload = queue.Upload(Cnf)
 
     if not Options["No-Action"]:
-        Logger = Upload.Logger = logging.Logger(Cnf, "process-new")
+        try:
+            Logger = Upload.Logger = logging.Logger(Cnf, "process-new")
+        except CantOpenError, e:
+            Options["Trainee"] = "Oh yes"
 
     projectB = Upload.projectB
 
@@ -825,6 +838,29 @@ def get_accept_lock():
             else:
                 raise
 
+
+@contextlib.contextmanager
+def lock_package(package):
+    """
+    Lock C{package} so that noone else jumps in processing it.
+
+    @type package: string
+    @param package: source package name to lock
+    """
+
+    path = os.path.join(Cnf["Process-New::LockDir"], package)
+    try:
+        fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDONLY)
+    except OSError, e:
+        if e.errno == errno.EEXIST or e.errno == errno.EACCES:
+            user = pwd.getpwuid(os.stat(path)[stat.ST_UID])[4].split(',')[0].replace('.', '')
+            raise AlreadyLockedError, user
+
+    try:
+        yield fd
+    finally:
+        os.unlink(path)
+
 def move_to_dir (dest, perms=0660, changesperms=0664):
     utils.move (Upload.pkg.changes_file, dest, perms=changesperms)
     file_keys = Upload.pkg.files.keys()
@@ -949,19 +985,23 @@ def do_pkg(changes_file):
     Upload.update_subst()
     files = Upload.pkg.files
 
-    if not recheck():
-        return
-
-    (new, byhand) = check_status(files)
-    if new or byhand:
-        if new:
-            do_new()
-        if byhand:
-            do_byhand()
-        (new, byhand) = check_status(files)
-
-    if not new and not byhand:
-        do_accept()
+    try:
+        with lock_package(Upload.pkg.changes["source"]):
+            if not recheck():
+                return
+
+            (new, byhand) = check_status(files)
+            if new or byhand:
+                if new:
+                    do_new()
+                if byhand:
+                    do_byhand()
+                (new, byhand) = check_status(files)
+
+            if not new and not byhand:
+                do_accept()
+    except AlreadyLockedError, e:
+        print "Seems to be locked by %s already, skipping..." % (e)
 
 ################################################################################
 
@@ -976,7 +1016,7 @@ def end():
         sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))))
         Logger.log(["total",accept_count,accept_bytes])
 
-    if not Options["No-Action"]:
+    if not Options["No-Action"] and not Options["Trainee"]:
         Logger.close()
 
 ################################################################################
index cc59a9f8e2e695784acc6e03b4606d0633a86c58..a4bcea0fbda39303bd0bd37d3701b050f94379f4 100755 (executable)
@@ -38,6 +38,7 @@ import copy, glob, os, stat, sys, time
 import apt_pkg
 import cgi
 from daklib import queue
+from daklib import database
 from daklib import utils
 from daklib.dak_exceptions import *
 
@@ -45,6 +46,7 @@ Cnf = None
 Upload = None
 direction = []
 row_number = 0
+projectB = None
 
 ################################################################################
 
@@ -327,7 +329,7 @@ def process_changes_files(changes_files, type, log):
             else:
                 if mtime < oldest:
                     oldest = mtime
-            have_note += (d.has_key("process-new note"))
+            have_note += (database.has_new_comment(d["source"], d["version"]))
         per_source[source]["oldest"] = oldest
         if not have_note:
             per_source[source]["note_state"] = 0; # none
@@ -531,6 +533,7 @@ def main():
         usage()
 
     Upload = queue.Upload(Cnf)
+    projectB = Upload.projectB
 
     if Cnf.has_key("Queue-Report::Options::New"):
         header()
index 90d2f620bbc4c74854dbb91903fd2bb418509f90..00348f2dc842c1e50978b07a4046d19864e0f4d3 100755 (executable)
@@ -66,14 +66,15 @@ def init():
 
     Cnf = utils.get_conf()
 
-    Arguments = [('h',"help","Edit-Transitions::Options::Help"),
+    Arguments = [('a',"automatic","Edit-Transitions::Options::Automatic"),
+                 ('h',"help","Edit-Transitions::Options::Help"),
                  ('e',"edit","Edit-Transitions::Options::Edit"),
                  ('i',"import","Edit-Transitions::Options::Import", "HasArg"),
                  ('c',"check","Edit-Transitions::Options::Check"),
                  ('s',"sudo","Edit-Transitions::Options::Sudo"),
                  ('n',"no-action","Edit-Transitions::Options::No-Action")]
 
-    for i in ["help", "no-action", "edit", "import", "check", "sudo"]:
+    for i in ["automatic", "help", "no-action", "edit", "import", "check", "sudo"]:
         if not Cnf.has_key("Edit-Transitions::Options::%s" % (i)):
             Cnf["Edit-Transitions::Options::%s" % (i)] = ""
 
@@ -107,6 +108,7 @@ Options:
   -i, --import <file>       check and import transitions from file
   -c, --check               check the transitions file, remove outdated entries
   -S, --sudo                use sudo to update transitions file
+  -a, --automatic           don't prompt (only affects check).
   -n, --no-action           don't do anything (only affects check)"""
 
     sys.exit(exit_code)
@@ -389,11 +391,14 @@ def edit_transitions():
 def check_transitions(transitions):
     """
     Check if the defined transitions still apply and remove those that no longer do.
-    @note: Asks the user for confirmation first.
+    @note: Asks the user for confirmation first unless -a has been set.
 
     """
+    global Cnf
+
     to_dump = 0
     to_remove = []
+    info = {}
     # Now look through all defined transitions
     for trans in transitions:
         t = transitions[trans]
@@ -403,7 +408,8 @@ def check_transitions(transitions):
         # Will be None if nothing is in testing.
         current = database.get_suite_version(source, "testing")
 
-        print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
+        info[trans] = get_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
+        print info[trans]
 
         if current == None:
             # No package in testing
@@ -432,6 +438,8 @@ def check_transitions(transitions):
 
         if Options["no-action"]:
             answer="n"
+        elif Options["automatic"]:
+            answer="y"
         else:
             answer = utils.our_raw_input(prompt).lower()
 
@@ -443,9 +451,25 @@ def check_transitions(transitions):
             sys.exit(0)
         elif answer == 'y':
             print "Committing"
+            subst = {}
+            subst['__TRANSITION_MESSAGE__'] = "The following transitions were removed:\n"
             for remove in to_remove:
+                subst['__TRANSITION_MESSAGE__'] += info[remove] + '\n'
                 del transitions[remove]
 
+            # If we have a mail address configured for transitions,
+            # send a notification
+            subst['__TRANSITION_EMAIL__'] = Cnf.get("Transitions::Notifications", "")
+            if subst['__TRANSITION_EMAIL__'] != "":
+                print "Sending notification to %s" % subst['__TRANSITION_EMAIL__']
+                subst['__DAK_ADDRESS__'] = Cnf["Dinstall::MyEmailAddress"]
+                subst['__BCC__'] = 'X-DAK: dak transitions'
+                if Cnf.has_key("Dinstall::Bcc"):
+                    subst["__BCC__"] += '\nBcc: %s' % Cnf["Dinstall::Bcc"]
+                message = utils.TemplateSubst(subst,
+                                              os.path.join(Cnf["Dir::Templates"], 'transition.removed'))
+                utils.send_mail(message)
+
             edit_file = temp_transitions_file(transitions)
             write_transitions_from_file(edit_file)
 
@@ -456,7 +480,7 @@ def check_transitions(transitions):
 
 ################################################################################
 
-def print_info(trans, source, expected, rm, reason, packages):
+def get_info(trans, source, expected, rm, reason, packages):
     """
     Print information about a single transition.
 
@@ -479,21 +503,20 @@ def print_info(trans, source, expected, rm, reason, packages):
     @param packages: list of blocked packages
 
     """
-    print """Looking at transition: %s
+    return """Looking at transition: %s
 Source:      %s
 New Version: %s
 Responsible: %s
 Description: %s
 Blocked Packages (total: %d): %s
 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
-    return
 
 ################################################################################
 
 def transition_info(transitions):
     """
     Print information about all defined transitions.
-    Calls L{print_info} for every transition and then tells user if the transition is
+    Calls L{get_info} for every transition and then tells user if the transition is
     still ongoing or if the expected version already hit testing.
 
     @type transitions: dict
@@ -507,7 +530,7 @@ def transition_info(transitions):
         # Will be None if nothing is in testing.
         current = database.get_suite_version(source, "testing")
 
-        print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
+        print get_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
 
         if current == None:
             # No package in testing
index 4d08721f54cfc20c74bc07a2efce816a27b18748..b559e544dfe57982af3e7799936cb25a056efaa3 100755 (executable)
@@ -45,7 +45,7 @@ from daklib.dak_exceptions import DBUpdateError
 
 Cnf = None
 projectB = None
-required_database_schema = 10
+required_database_schema = 12
 
 ################################################################################
 
index ccd63e5055e28df08af620008e08e1457acddf2d..21fce9be57665a87d870b4ecacc433c4742daa89 100755 (executable)
@@ -60,7 +60,8 @@ dakerrors = {
     "NoSourceFieldError":  """Exception raised - we cant find the source - wtf?""",
     "MissingContents":     """Exception raised - we could not determine contents for this deb""",
     "DBUpdateError":       """Exception raised - could not update the database""",
-    "ChangesUnicodeError": """Exception raised - changes file not properly utf-8 encoded"""
+    "ChangesUnicodeError": """Exception raised - changes file not properly utf-8 encoded""",
+    "AlreadyLockedError":  """Exception raised - package already locked by someone else"""
 } #: All dak exceptions
 
 def construct_dak_exception(name, description):
index 93d9ad5392fcc706a15b38be4a62615b4bf8f319..a52555682624ca8aac8fa693acac8f805eb797f4 100755 (executable)
@@ -4,8 +4,9 @@
 @group readonly: get_suite_id, get_section_id, get_priority_id, get_override_type_id,
                  get_architecture_id, get_archive_id, get_component_id, get_location_id,
                  get_source_id, get_suite_version, get_files_id, get_maintainer, get_suites,
-                 get_suite_architectures
+                 get_suite_architectures, get_new_comments, has_new_comment
 @group read/write: get_or_set*, set_files_id
+@group writeonly: add_new_comment, delete_new_comments
 
 @contact: Debian FTP Master <ftpmaster@debian.org>
 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
@@ -838,6 +839,89 @@ def get_suites(pkgname, src=False):
 
 ################################################################################
 
+def get_new_comments(package):
+    """
+    Returns all the possible comments attached to C{package} in NEW. All versions.
+
+    @type package: string
+    @param package: name of the package
+
+    @rtype: list
+    @return: list of strings containing comments for all versions from all authors for package
+    """
+
+    comments = []
+    query = projectB.query(""" SELECT version, comment, author, notedate
+                               FROM new_comments
+                               WHERE package = '%s'
+                               ORDER BY notedate
+                           """ % (package))
+
+    for row in query.getresult():
+        comments.append("\nAuthor: %s\nVersion: %s\nTimestamp: %s\n\n%s\n" % (row[2], row[0], row[3], row[1]))
+        comments.append("-"*72)
+
+    return comments
+
+def has_new_comment(package, version):
+    """
+    Returns true if the given combination of C{package}, C{version} has a comment.
+
+    @type package: string
+    @param package: name of the package
+
+    @type version: string
+    @param version: package version
+
+    @rtype: boolean
+    @return: true/false
+    """
+
+    exists = projectB.query("""SELECT 1 FROM new_comments
+                               WHERE package='%s'
+                               AND version='%s'
+                               LIMIT 1"""
+                            % (package, version) ).getresult()
+
+    if not exists:
+        return False
+    else:
+        return True
+
+def add_new_comment(package, version, comment, author):
+    """
+    Add a new comment for C{package}, C{version} written by C{author}
+
+    @type package: string
+    @param package: name of the package
+
+    @type version: string
+    @param version: package version
+
+    @type comment: string
+    @param comment: the comment
+
+    @type author: string
+    @param author: the authorname
+    """
+
+    projectB.query(""" INSERT INTO new_comments (package, version, comment, author)
+                       VALUES ('%s', '%s', '%s', '%s')
+    """ % (package, version, comment, author) )
+
+    return
+
+def delete_new_comments(package, version):
+    """
+    Delete a comment for C{package}, C{version}, if one exists
+    """
+
+    projectB.query(""" DELETE FROM new_comments
+                       WHERE package = '%s' AND version = '%s'
+    """ % (package, version))
+    return
+
+################################################################################
 def copy_temporary_contents(package, version, arch, deb, reject):
     """
     copy the previously stored contents from the temp table to the permanant one
index 599b077f5547faae50cba80eb07cb8393071194f..35754c8d738a49d9dab87f80f0a1a57556c9b1ed 100755 (executable)
@@ -803,7 +803,7 @@ distribution."""
 
     ###########################################################################
 
-    def do_reject (self, manual = 0, reject_message = ""):
+    def do_reject (self, manual = 0, reject_message = "", note = ""):
         """
         Reject an upload. If called without a reject message or C{manual} is
         true, spawn an editor so the user can write one.
@@ -821,6 +821,11 @@ distribution."""
         # editor so the user can add one in...
         if manual and not reject_message:
             (fd, temp_filename) = utils.temp_filename()
+            temp_file = os.fdopen(fd, 'w')
+            if len(note) > 0:
+                for line in note:
+                    temp_file.write(line)
+            temp_file.close()
             editor = os.environ.get("EDITOR","vi")
             answer = 'E'
             while answer == 'E':
diff --git a/templates/transition.removed b/templates/transition.removed
new file mode 100644 (file)
index 0000000..32b0aca
--- /dev/null
@@ -0,0 +1,15 @@
+From: __DAK_ADDRESS__
+To: __TRANSITION_EMAIL__
+__BCC__
+X-Debian: DAK
+Precedence: bulk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Transitions Completed
+
+The following transitions are complete and have been removed
+from the transitions list:
+
+__TRANSITION_MESSAGE__
+