#!/usr/bin/env python
# vim:set et ts=4 sw=4:
-# Handles NEW and BYHAND packages
-# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org>
+""" Handles NEW and BYHAND packages
+@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
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
################################################################################
-import copy, errno, os, readline, stat, sys, time
+from __future__ import with_statement
+
+import copy
+import errno
+import os
+import readline
+import stat
+import sys
+import time
+import contextlib
+import pwd
import apt_pkg, apt_inst
import examine_package
from daklib import database
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
+Cnf = None #: Configuration, apt_pkg.Configuration
Options = None
Upload = None
-projectB = None
+projectB = None #: database connection, pgobject
Logger = None
Priorities = None
source_package = files[f]["source package"]
if not Upload.pkg.changes["architecture"].has_key("source") \
and not Upload.source_exists(source_package, source_version, Upload.pkg.changes["distribution"].keys()):
- source_epochless_version = utils.re_no_epoch.sub('', source_version)
+ source_epochless_version = re_no_epoch.sub('', source_version)
dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
found = 0
for q in ["Accepted", "Embargoed", "Unembargoed"]:
# Version and file overwrite checks
if files[f]["type"] == "deb":
- reject(Upload.check_binary_against_db(f))
+ reject(Upload.check_binary_against_db(f), "")
elif files[f]["type"] == "dsc":
- reject(Upload.check_source_against_db(f))
+ reject(Upload.check_source_against_db(f), "")
(reject_msg, is_in_incoming) = Upload.check_dsc_against_db(f)
reject(reject_msg, "")
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,
while prompt.find(answer) == -1:
answer = utils.our_raw_input(prompt)
- m = queue.re_default_answer.match(prompt)
+ m = re_default_answer.match(prompt)
if answer == "":
answer = m.group(1)
answer = answer[:1].upper()
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
class Section_Completer:
def __init__ (self):
self.sections = []
+ self.matches = []
q = projectB.query("SELECT section FROM section")
for i in q.getresult():
self.sections.append(i[0])
class Priority_Completer:
def __init__ (self):
self.priorities = []
+ self.matches = []
q = projectB.query("SELECT priority FROM priority")
for i in q.getresult():
self.priorities.append(i[0])
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
################################################################################
def edit_new (new):
# Write the current data to a temporary file
- temp_filename = utils.temp_filename()
- temp_file = utils.open_file(temp_filename, 'w')
+ (fd, temp_filename) = utils.temp_filename()
+ temp_file = os.fdopen(fd, 'w')
print_new (new, 0, temp_file)
temp_file.close()
# Spawn an editor on that file
while prompt.find(answer) == -1:
answer = utils.our_raw_input(prompt)
- m = queue.re_default_answer.match(prompt)
+ m = re_default_answer.match(prompt)
if answer == "":
answer = m.group(1)
answer = answer[:1].upper()
answer = answer[:1].upper()
if answer == "E" or answer == "D":
got_answer = 1
- elif queue.re_isanum.match (answer):
+ elif re_isanum.match (answer):
answer = int(answer)
if (answer < 1) or (answer > index):
print "%s is not a valid index (%s). Please retry." % (answer, index_range(index))
def edit_note(note):
# Write the current data to a temporary file
- temp_filename = utils.temp_filename()
- temp_file = utils.open_file(temp_filename, 'w')
- temp_file.write(note)
- temp_file.close()
+ (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)
- 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:
answer = utils.our_raw_input(prompt)
- m = queue.re_default_answer.search(prompt)
+ m = re_default_answer.search(prompt)
if answer == "":
answer = m.group(1)
answer = answer[:1].upper()
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())
################################################################################
stdout_fd = sys.stdout
try:
sys.stdout = less_fd
- examine_package.display_changes(Upload.pkg.changes_file)
+ changes = utils.parse_changes (Upload.pkg.changes_file)
+ examine_package.display_changes(changes['distribution'], Upload.pkg.changes_file)
files = Upload.pkg.files
for f in files.keys():
if files[f].has_key("new"):
ftype = files[f]["type"]
if ftype == "deb":
- examine_package.check_deb(f)
+ examine_package.check_deb(changes['distribution'], f)
elif ftype == "dsc":
- examine_package.check_dsc(f)
+ examine_package.check_dsc(changes['distribution'], f)
finally:
+ examine_package.output_package_relations()
sys.stdout = stdout_fd
except IOError, e:
if e.errno == errno.EPIPE:
################################################################################
-def prod_maintainer ():
+def prod_maintainer (note):
# Here we prepare an editor and get them ready to prod...
- temp_filename = utils.temp_filename()
+ (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 = utils.open_file(temp_filename)
- 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 ?"
answer = "XXX"
while prompt.find(answer) == -1:
answer = utils.our_raw_input(prompt)
- m = queue.re_default_answer.search(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)
+ 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"])
while prompt.find(answer) == -1:
answer = utils.our_raw_input(prompt)
- m = queue.re_default_answer.search(prompt)
+ m = re_default_answer.search(prompt)
if answer == "":
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':
-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)
('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)] = ""
changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+ if len(changes_files) == 0 and not Cnf.get("Process-New::Options::Comments-Dir",""):
+ changes_files = utils.get_changes_files(Cnf["Dir::Queue::New"])
+
Options = Cnf.SubTree("Process-New::Options")
if Options["Help"]:
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
while prompt.find(answer) == -1:
answer = utils.our_raw_input(prompt)
- m = queue.re_default_answer.search(prompt)
+ m = re_default_answer.search(prompt)
if answer == "":
answer = m.group(1)
answer = answer[:1].upper()
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()
u = queue.Upload(Cnf)
u.pkg.changes_file = os.path.join(qdir, entry)
u.update_vars()
- if not Upload.pkg.changes["architecture"].has_key("source"):
+ if not u.pkg.changes["architecture"].has_key("source"):
# another binary upload, ignore
continue
if Upload.pkg.changes["version"] != u.pkg.changes["version"]:
return
Logger.log(["Moving to %s" % (suite,), Upload.pkg.changes_file])
Upload.dump_vars(queue_dir)
- move_to_dir(queue_dir)
+ move_to_dir(queue_dir, perms=0664)
os.unlink(Upload.pkg.changes_file[:-8]+".dak")
def _accept():
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)
################################################################################
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()
################################################################################