]> git.decadent.org.uk Git - dak.git/blob - dak/process_new.py
Convert exception handling to Python3 syntax.
[dak.git] / dak / process_new.py
1 #!/usr/bin/env python
2 # vim:set et ts=4 sw=4:
3
4 """ Handles NEW and BYHAND packages
5
6 @contact: Debian FTP Master <ftpmaster@debian.org>
7 @copyright: 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
8 @copyright: 2009 Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009 Frank Lichtenheld <djpig@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
16
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
26 ################################################################################
27
28 # 23:12|<aj> I will not hush!
29 # 23:12|<elmo> :>
30 # 23:12|<aj> Where there is injustice in the world, I shall be there!
31 # 23:13|<aj> I shall not be silenced!
32 # 23:13|<aj> The world shall know!
33 # 23:13|<aj> The world *must* know!
34 # 23:13|<elmo> oh dear, he's gone back to powerpuff girls... ;-)
35 # 23:13|<aj> yay powerpuff girls!!
36 # 23:13|<aj> buttercup's my favourite, who's yours?
37 # 23:14|<aj> you're backing away from the keyboard right now aren't you?
38 # 23:14|<aj> *AREN'T YOU*?!
39 # 23:15|<aj> I will not be treated like this.
40 # 23:15|<aj> I shall have my revenge.
41 # 23:15|<aj> I SHALL!!!
42
43 ################################################################################
44
45 from __future__ import with_statement
46
47 import copy
48 import errno
49 import os
50 import readline
51 import stat
52 import sys
53 import time
54 import contextlib
55 import pwd
56 import apt_pkg, apt_inst
57 import examine_package
58
59 from daklib.dbconn import *
60 from daklib.queue import *
61 from daklib import daklog
62 from daklib import utils
63 from daklib.regexes import re_no_epoch, re_default_answer, re_isanum, re_package
64 from daklib.dak_exceptions import CantOpenError, AlreadyLockedError, CantGetLockError
65 from daklib.summarystats import SummaryStats
66 from daklib.config import Config
67 from daklib.changesutils import *
68
69 # Globals
70 Options = None
71 Logger = None
72
73 Priorities = None
74 Sections = None
75
76 ################################################################################
77 ################################################################################
78 ################################################################################
79
80 def recheck(upload, session):
81 # STU: I'm not sure, but I don't thin kthis is necessary any longer:    upload.recheck(session)
82     if len(upload.rejects) > 0:
83         answer = "XXX"
84         if Options["No-Action"] or Options["Automatic"] or Options["Trainee"]:
85             answer = 'S'
86
87         print "REJECT\n%s" % '\n'.join(upload.rejects)
88         prompt = "[R]eject, Skip, Quit ?"
89
90         while prompt.find(answer) == -1:
91             answer = utils.our_raw_input(prompt)
92             m = re_default_answer.match(prompt)
93             if answer == "":
94                 answer = m.group(1)
95             answer = answer[:1].upper()
96
97         if answer == 'R':
98             upload.do_reject(manual=0, reject_message='\n'.join(upload.rejects))
99             upload.pkg.remove_known_changes(session=session)
100             session.commit()
101             return 0
102         elif answer == 'S':
103             return 0
104         elif answer == 'Q':
105             end()
106             sys.exit(0)
107
108     return 1
109
110 ################################################################################
111
112 class Section_Completer:
113     def __init__ (self, session):
114         self.sections = []
115         self.matches = []
116         for s, in session.query(Section.section):
117             self.sections.append(s)
118
119     def complete(self, text, state):
120         if state == 0:
121             self.matches = []
122             n = len(text)
123             for word in self.sections:
124                 if word[:n] == text:
125                     self.matches.append(word)
126         try:
127             return self.matches[state]
128         except IndexError:
129             return None
130
131 ############################################################
132
133 class Priority_Completer:
134     def __init__ (self, session):
135         self.priorities = []
136         self.matches = []
137         for p, in session.query(Priority.priority):
138             self.priorities.append(p)
139
140     def complete(self, text, state):
141         if state == 0:
142             self.matches = []
143             n = len(text)
144             for word in self.priorities:
145                 if word[:n] == text:
146                     self.matches.append(word)
147         try:
148             return self.matches[state]
149         except IndexError:
150             return None
151
152 ################################################################################
153
154 def print_new (new, upload, indexed, file=sys.stdout):
155     check_valid(new)
156     broken = False
157     index = 0
158     for pkg in new.keys():
159         index += 1
160         section = new[pkg]["section"]
161         priority = new[pkg]["priority"]
162         if new[pkg]["section id"] == -1:
163             section += "[!]"
164             broken = True
165         if new[pkg]["priority id"] == -1:
166             priority += "[!]"
167             broken = True
168         if indexed:
169             line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
170         else:
171             line = "%-20s %-20s %-20s" % (pkg, priority, section)
172         line = line.strip()+'\n'
173         file.write(line)
174     notes = get_new_comments(upload.pkg.changes.get("source"))
175     for note in notes:
176         print "\nAuthor: %s\nVersion: %s\nTimestamp: %s\n\n%s" \
177               % (note.author, note.version, note.notedate, note.comment)
178         print "-" * 72
179     return broken, len(notes) > 0
180
181 ################################################################################
182
183 def index_range (index):
184     if index == 1:
185         return "1"
186     else:
187         return "1-%s" % (index)
188
189 ################################################################################
190 ################################################################################
191
192 def edit_new (new, upload):
193     # Write the current data to a temporary file
194     (fd, temp_filename) = utils.temp_filename()
195     temp_file = os.fdopen(fd, 'w')
196     print_new (new, upload, indexed=0, file=temp_file)
197     temp_file.close()
198     # Spawn an editor on that file
199     editor = os.environ.get("EDITOR","vi")
200     result = os.system("%s %s" % (editor, temp_filename))
201     if result != 0:
202         utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
203     # Read the edited data back in
204     temp_file = utils.open_file(temp_filename)
205     lines = temp_file.readlines()
206     temp_file.close()
207     os.unlink(temp_filename)
208     # Parse the new data
209     for line in lines:
210         line = line.strip()
211         if line == "":
212             continue
213         s = line.split()
214         # Pad the list if necessary
215         s[len(s):3] = [None] * (3-len(s))
216         (pkg, priority, section) = s[:3]
217         if not new.has_key(pkg):
218             utils.warn("Ignoring unknown package '%s'" % (pkg))
219         else:
220             # Strip off any invalid markers, print_new will readd them.
221             if section.endswith("[!]"):
222                 section = section[:-3]
223             if priority.endswith("[!]"):
224                 priority = priority[:-3]
225             for f in new[pkg]["files"]:
226                 upload.pkg.files[f]["section"] = section
227                 upload.pkg.files[f]["priority"] = priority
228             new[pkg]["section"] = section
229             new[pkg]["priority"] = priority
230
231 ################################################################################
232
233 def edit_index (new, upload, index):
234     priority = new[index]["priority"]
235     section = new[index]["section"]
236     ftype = new[index]["type"]
237     done = 0
238     while not done:
239         print "\t".join([index, priority, section])
240
241         answer = "XXX"
242         if ftype != "dsc":
243             prompt = "[B]oth, Priority, Section, Done ? "
244         else:
245             prompt = "[S]ection, Done ? "
246         edit_priority = edit_section = 0
247
248         while prompt.find(answer) == -1:
249             answer = utils.our_raw_input(prompt)
250             m = re_default_answer.match(prompt)
251             if answer == "":
252                 answer = m.group(1)
253             answer = answer[:1].upper()
254
255         if answer == 'P':
256             edit_priority = 1
257         elif answer == 'S':
258             edit_section = 1
259         elif answer == 'B':
260             edit_priority = edit_section = 1
261         elif answer == 'D':
262             done = 1
263
264         # Edit the priority
265         if edit_priority:
266             readline.set_completer(Priorities.complete)
267             got_priority = 0
268             while not got_priority:
269                 new_priority = utils.our_raw_input("New priority: ").strip()
270                 if new_priority not in Priorities.priorities:
271                     print "E: '%s' is not a valid priority, try again." % (new_priority)
272                 else:
273                     got_priority = 1
274                     priority = new_priority
275
276         # Edit the section
277         if edit_section:
278             readline.set_completer(Sections.complete)
279             got_section = 0
280             while not got_section:
281                 new_section = utils.our_raw_input("New section: ").strip()
282                 if new_section not in Sections.sections:
283                     print "E: '%s' is not a valid section, try again." % (new_section)
284                 else:
285                     got_section = 1
286                     section = new_section
287
288         # Reset the readline completer
289         readline.set_completer(None)
290
291     for f in new[index]["files"]:
292         upload.pkg.files[f]["section"] = section
293         upload.pkg.files[f]["priority"] = priority
294     new[index]["priority"] = priority
295     new[index]["section"] = section
296     return new
297
298 ################################################################################
299
300 def edit_overrides (new, upload, session):
301     print
302     done = 0
303     while not done:
304         print_new (new, upload, indexed=1)
305         new_index = {}
306         index = 0
307         for i in new.keys():
308             index += 1
309             new_index[index] = i
310
311         prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
312
313         got_answer = 0
314         while not got_answer:
315             answer = utils.our_raw_input(prompt)
316             if not answer.isdigit():
317                 answer = answer[:1].upper()
318             if answer == "E" or answer == "D":
319                 got_answer = 1
320             elif re_isanum.match (answer):
321                 answer = int(answer)
322                 if (answer < 1) or (answer > index):
323                     print "%s is not a valid index (%s).  Please retry." % (answer, index_range(index))
324                 else:
325                     got_answer = 1
326
327         if answer == 'E':
328             edit_new(new, upload)
329         elif answer == 'D':
330             done = 1
331         else:
332             edit_index (new, upload, new_index[answer])
333
334     return new
335
336
337 ################################################################################
338
339 def check_pkg (upload):
340     save_stdout = sys.stdout
341     try:
342         sys.stdout = os.popen("less -R -", 'w', 0)
343         changes = utils.parse_changes (upload.pkg.changes_file)
344         print examine_package.display_changes(changes['distribution'], upload.pkg.changes_file)
345         files = upload.pkg.files
346         for f in files.keys():
347             if files[f].has_key("new"):
348                 ftype = files[f]["type"]
349                 if ftype == "deb":
350                     print examine_package.check_deb(changes['distribution'], f)
351                 elif ftype == "dsc":
352                     print examine_package.check_dsc(changes['distribution'], f)
353         print examine_package.output_package_relations()
354     except IOError as e:
355         if e.errno == errno.EPIPE:
356             utils.warn("[examine_package] Caught EPIPE; skipping.")
357         else:
358             sys.stdout = save_stdout
359             raise
360     except KeyboardInterrupt:
361         utils.warn("[examine_package] Caught C-c; skipping.")
362     sys.stdout = save_stdout
363
364 ################################################################################
365
366 ## FIXME: horribly Debian specific
367
368 def do_bxa_notification(upload):
369     files = upload.pkg.files
370     summary = ""
371     for f in files.keys():
372         if files[f]["type"] == "deb":
373             control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(f)))
374             summary += "\n"
375             summary += "Package: %s\n" % (control.Find("Package"))
376             summary += "Description: %s\n" % (control.Find("Description"))
377     upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
378     bxa_mail = utils.TemplateSubst(upload.Subst,Config()["Dir::Templates"]+"/process-new.bxa_notification")
379     utils.send_mail(bxa_mail)
380
381 ################################################################################
382
383 def add_overrides (new, upload, session):
384     changes = upload.pkg.changes
385     files = upload.pkg.files
386     srcpkg = changes.get("source")
387
388     for suite in changes["suite"].keys():
389         suite_id = get_suite(suite).suite_id
390         for pkg in new.keys():
391             component_id = get_component(new[pkg]["component"]).component_id
392             type_id = get_override_type(new[pkg]["type"]).overridetype_id
393             priority_id = new[pkg]["priority id"]
394             section_id = new[pkg]["section id"]
395             Logger.log(["%s (%s) overrides" % (pkg, srcpkg), suite, new[pkg]["component"], new[pkg]["type"], new[pkg]["priority"], new[pkg]["section"]])
396             session.execute("INSERT INTO override (suite, component, type, package, priority, section, maintainer) VALUES (:sid, :cid, :tid, :pkg, :pid, :sectid, '')",
397                             { 'sid': suite_id, 'cid': component_id, 'tid':type_id, 'pkg': pkg, 'pid': priority_id, 'sectid': section_id})
398             for f in new[pkg]["files"]:
399                 if files[f].has_key("new"):
400                     del files[f]["new"]
401             del new[pkg]
402
403     session.commit()
404
405     if Config().FindB("Dinstall::BXANotify"):
406         do_bxa_notification(upload)
407
408 ################################################################################
409
410 def do_new(upload, session):
411     print "NEW\n"
412     files = upload.pkg.files
413     upload.check_files(not Options["No-Action"])
414     changes = upload.pkg.changes
415     cnf = Config()
416
417     # Check for a valid distribution
418     upload.check_distributions()
419
420     # Make a copy of distribution we can happily trample on
421     changes["suite"] = copy.copy(changes["distribution"])
422
423     # Try to get an included dsc
424     dsc = None
425     (status, _) = upload.load_dsc()
426     if status:
427         dsc = upload.pkg.dsc
428
429     # The main NEW processing loop
430     done = 0
431     new = {}
432     while not done:
433         # Find out what's new
434         new, byhand = determine_new(upload.pkg.changes_file, changes, files, dsc=dsc, session=session, new=new)
435
436         if not new:
437             break
438
439         answer = "XXX"
440         if Options["No-Action"] or Options["Automatic"]:
441             answer = 'S'
442
443         (broken, note) = print_new(new, upload, indexed=0)
444         prompt = ""
445
446         if not broken and not note:
447             prompt = "Add overrides, "
448         if broken:
449             print "W: [!] marked entries must be fixed before package can be processed."
450         if note:
451             print "W: note must be removed before package can be processed."
452             prompt += "RemOve all notes, Remove note, "
453
454         prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
455
456         while prompt.find(answer) == -1:
457             answer = utils.our_raw_input(prompt)
458             m = re_default_answer.search(prompt)
459             if answer == "":
460                 answer = m.group(1)
461             answer = answer[:1].upper()
462
463         if answer in ( 'A', 'E', 'M', 'O', 'R' ) and Options["Trainee"]:
464             utils.warn("Trainees can't do that")
465             continue
466
467         if answer == 'A' and not Options["Trainee"]:
468             try:
469                 check_daily_lock()
470                 done = add_overrides (new, upload, session)
471                 new_accept(upload, Options["No-Action"], session)
472                 Logger.log(["NEW ACCEPT: %s" % (upload.pkg.changes_file)])
473             except CantGetLockError:
474                 print "Hello? Operator! Give me the number for 911!"
475                 print "Dinstall in the locked area, cant process packages, come back later"
476         elif answer == 'C':
477             check_pkg(upload)
478         elif answer == 'E' and not Options["Trainee"]:
479             new = edit_overrides (new, upload, session)
480         elif answer == 'M' and not Options["Trainee"]:
481             aborted = upload.do_reject(manual=1,
482                                        reject_message=Options["Manual-Reject"],
483                                        notes=get_new_comments(changes.get("source", ""), session=session))
484             if not aborted:
485                 upload.pkg.remove_known_changes(session=session)
486                 session.commit()
487                 Logger.log(["NEW REJECT: %s" % (upload.pkg.changes_file)])
488                 done = 1
489         elif answer == 'N':
490             edit_note(get_new_comments(changes.get("source", ""), session=session),
491                       upload, session, bool(Options["Trainee"]))
492         elif answer == 'P' and not Options["Trainee"]:
493             prod_maintainer(get_new_comments(changes.get("source", ""), session=session),
494                             upload)
495             Logger.log(["NEW PROD: %s" % (upload.pkg.changes_file)])
496         elif answer == 'R' and not Options["Trainee"]:
497             confirm = utils.our_raw_input("Really clear note (y/N)? ").lower()
498             if confirm == "y":
499                 for c in get_new_comments(changes.get("source", ""), changes.get("version", ""), session=session):
500                     session.delete(c)
501                 session.commit()
502         elif answer == 'O' and not Options["Trainee"]:
503             confirm = utils.our_raw_input("Really clear all notes (y/N)? ").lower()
504             if confirm == "y":
505                 for c in get_new_comments(changes.get("source", ""), session=session):
506                     session.delete(c)
507                 session.commit()
508
509         elif answer == 'S':
510             done = 1
511         elif answer == 'Q':
512             end()
513             sys.exit(0)
514
515 ################################################################################
516 ################################################################################
517 ################################################################################
518
519 def usage (exit_code=0):
520     print """Usage: dak process-new [OPTION]... [CHANGES]...
521   -a, --automatic           automatic run
522   -b, --no-binaries         do not sort binary-NEW packages first
523   -c, --comments            show NEW comments
524   -h, --help                show this help and exit.
525   -m, --manual-reject=MSG   manual reject with `msg'
526   -n, --no-action           don't do anything
527   -t, --trainee             FTP Trainee mode
528   -V, --version             display the version number and exit"""
529     sys.exit(exit_code)
530
531 ################################################################################
532
533 def do_byhand(upload, session):
534     done = 0
535     while not done:
536         files = upload.pkg.files
537         will_install = True
538         byhand = []
539
540         for f in files.keys():
541             if files[f]["section"] == "byhand":
542                 if os.path.exists(f):
543                     print "W: %s still present; please process byhand components and try again." % (f)
544                     will_install = False
545                 else:
546                     byhand.append(f)
547
548         answer = "XXXX"
549         if Options["No-Action"]:
550             answer = "S"
551         if will_install:
552             if Options["Automatic"] and not Options["No-Action"]:
553                 answer = 'A'
554             prompt = "[A]ccept, Manual reject, Skip, Quit ?"
555         else:
556             prompt = "Manual reject, [S]kip, Quit ?"
557
558         while prompt.find(answer) == -1:
559             answer = utils.our_raw_input(prompt)
560             m = re_default_answer.search(prompt)
561             if answer == "":
562                 answer = m.group(1)
563             answer = answer[:1].upper()
564
565         if answer == 'A':
566             dbchg = get_dbchange(upload.pkg.changes_file, session)
567             if dbchg is None:
568                 print "Warning: cannot find changes file in database; can't process BYHAND"
569             else:
570                 try:
571                     check_daily_lock()
572                     done = 1
573                     for b in byhand:
574                         # Find the file entry in the database
575                         found = False
576                         for f in dbchg.files:
577                             if f.filename == b:
578                                 found = True
579                                 f.processed = True
580                                 break
581
582                         if not found:
583                             print "Warning: Couldn't find BYHAND item %s in the database to mark it processed" % b
584
585                     session.commit()
586                     Logger.log(["BYHAND ACCEPT: %s" % (upload.pkg.changes_file)])
587                 except CantGetLockError:
588                     print "Hello? Operator! Give me the number for 911!"
589                     print "Dinstall in the locked area, cant process packages, come back later"
590         elif answer == 'M':
591             aborted = upload.do_reject(manual=1,
592                                        reject_message=Options["Manual-Reject"],
593                                        notes=get_new_comments(changes.get("source", ""), session=session))
594             if not aborted:
595                 upload.pkg.remove_known_changes(session=session)
596                 session.commit()
597                 Logger.log(["BYHAND REJECT: %s" % (upload.pkg.changes_file)])
598                 done = 1
599         elif answer == 'S':
600             done = 1
601         elif answer == 'Q':
602             end()
603             sys.exit(0)
604
605 ################################################################################
606
607 def check_daily_lock():
608     """
609     Raises CantGetLockError if the dinstall daily.lock exists.
610     """
611
612     cnf = Config()
613     try:
614         lockfile = cnf.get("Process-New::DinstallLockFile",
615                            os.path.join(cnf['Dir::Lock'], 'processnew.lock'))
616
617         os.open(lockfile,
618                 os.O_RDONLY | os.O_CREAT | os.O_EXCL)
619     except OSError as e:
620         if e.errno == errno.EEXIST or e.errno == errno.EACCES:
621             raise CantGetLockError
622
623     os.unlink(lockfile)
624
625
626 @contextlib.contextmanager
627 def lock_package(package):
628     """
629     Lock C{package} so that noone else jumps in processing it.
630
631     @type package: string
632     @param package: source package name to lock
633     """
634
635     cnf = Config()
636
637     path = os.path.join(cnf.get("Process-New::LockDir", cnf['Dir::Lock']), package)
638
639     try:
640         fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDONLY)
641     except OSError as e:
642         if e.errno == errno.EEXIST or e.errno == errno.EACCES:
643             user = pwd.getpwuid(os.stat(path)[stat.ST_UID])[4].split(',')[0].replace('.', '')
644             raise AlreadyLockedError, user
645
646     try:
647         yield fd
648     finally:
649         os.unlink(path)
650
651 class clean_holding(object):
652     def __init__(self,pkg):
653         self.pkg = pkg
654
655     def __enter__(self):
656         pass
657
658     def __exit__(self, type, value, traceback):
659         h = Holding()
660
661         for f in self.pkg.files.keys():
662             if os.path.exists(os.path.join(h.holding_dir, f)):
663                 os.unlink(os.path.join(h.holding_dir, f))
664
665
666 def do_pkg(changes_full_path, session):
667     changes_dir = os.path.dirname(changes_full_path)
668     changes_file = os.path.basename(changes_full_path)
669
670     u = Upload()
671     u.pkg.changes_file = changes_file
672     (u.pkg.changes["fingerprint"], rejects) = utils.check_signature(changes_file)
673     u.load_changes(changes_file)
674     u.pkg.directory = changes_dir
675     u.update_subst()
676     u.logger = Logger
677     origchanges = os.path.abspath(u.pkg.changes_file)
678
679     # Try to get an included dsc
680     dsc = None
681     (status, _) = u.load_dsc()
682     if status:
683         dsc = u.pkg.dsc
684
685     cnf = Config()
686     bcc = "X-DAK: dak process-new"
687     if cnf.has_key("Dinstall::Bcc"):
688         u.Subst["__BCC__"] = bcc + "\nBcc: %s" % (cnf["Dinstall::Bcc"])
689     else:
690         u.Subst["__BCC__"] = bcc
691
692     files = u.pkg.files
693     u.check_distributions()
694     for deb_filename, f in files.items():
695         if deb_filename.endswith(".udeb") or deb_filename.endswith(".deb"):
696             u.binary_file_checks(deb_filename, session)
697             u.check_binary_against_db(deb_filename, session)
698         else:
699             u.source_file_checks(deb_filename, session)
700             u.check_source_against_db(deb_filename, session)
701
702         u.pkg.changes["suite"] = copy.copy(u.pkg.changes["distribution"])
703
704     try:
705         with lock_package(u.pkg.changes["source"]):
706             with clean_holding(u.pkg):
707                 if not recheck(u, session):
708                     return
709
710                 new, byhand = determine_new(u.pkg.changes_file, u.pkg.changes, files, dsc=dsc, session=session)
711                 if byhand:
712                     do_byhand(u, session)
713                 elif new:
714                     do_new(u, session)
715                 else:
716                     try:
717                         check_daily_lock()
718                         new_accept(u, Options["No-Action"], session)
719                     except CantGetLockError:
720                         print "Hello? Operator! Give me the number for 911!"
721                         print "Dinstall in the locked area, cant process packages, come back later"
722
723     except AlreadyLockedError as e:
724         print "Seems to be locked by %s already, skipping..." % (e)
725
726 def show_new_comments(changes_files, session):
727     sources = set()
728     query = """SELECT package, version, comment, author
729                FROM new_comments
730                WHERE package IN ('"""
731
732     for changes in changes_files:
733         sources.add(os.path.basename(changes).split("_")[0])
734
735     query += "%s') ORDER BY package, version" % "', '".join(sources)
736     r = session.execute(query)
737
738     for i in r:
739         print "%s_%s\n%s\n(%s)\n\n\n" % (i[0], i[1], i[2], i[3])
740
741     session.commit()
742
743 ################################################################################
744
745 def end():
746     accept_count = SummaryStats().accept_count
747     accept_bytes = SummaryStats().accept_bytes
748
749     if accept_count:
750         sets = "set"
751         if accept_count > 1:
752             sets = "sets"
753         sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))))
754         Logger.log(["total",accept_count,accept_bytes])
755
756     if not Options["No-Action"] and not Options["Trainee"]:
757         Logger.close()
758
759 ################################################################################
760
761 def main():
762     global Options, Logger, Sections, Priorities
763
764     cnf = Config()
765     session = DBConn().session()
766
767     Arguments = [('a',"automatic","Process-New::Options::Automatic"),
768                  ('b',"no-binaries","Process-New::Options::No-Binaries"),
769                  ('c',"comments","Process-New::Options::Comments"),
770                  ('h',"help","Process-New::Options::Help"),
771                  ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
772                  ('t',"trainee","Process-New::Options::Trainee"),
773                  ('n',"no-action","Process-New::Options::No-Action")]
774
775     for i in ["automatic", "no-binaries", "comments", "help", "manual-reject", "no-action", "version", "trainee"]:
776         if not cnf.has_key("Process-New::Options::%s" % (i)):
777             cnf["Process-New::Options::%s" % (i)] = ""
778
779     changes_files = apt_pkg.ParseCommandLine(cnf.Cnf,Arguments,sys.argv)
780     if len(changes_files) == 0:
781         new_queue = get_policy_queue('new', session );
782         changes_paths = [ os.path.join(new_queue.path, j) for j in utils.get_changes_files(new_queue.path) ]
783     else:
784         changes_paths = [ os.path.abspath(j) for j in changes_files ]
785
786     Options = cnf.SubTree("Process-New::Options")
787
788     if Options["Help"]:
789         usage()
790
791     if not Options["No-Action"]:
792         try:
793             Logger = daklog.Logger("process-new")
794         except CantOpenError as e:
795             Options["Trainee"] = "True"
796
797     Sections = Section_Completer(session)
798     Priorities = Priority_Completer(session)
799     readline.parse_and_bind("tab: complete")
800
801     if len(changes_paths) > 1:
802         sys.stderr.write("Sorting changes...\n")
803     changes_files = sort_changes(changes_paths, session, Options["No-Binaries"])
804
805     if Options["Comments"]:
806         show_new_comments(changes_files, session)
807     else:
808         for changes_file in changes_files:
809             changes_file = utils.validate_changes_file_arg(changes_file, 0)
810             if not changes_file:
811                 continue
812             print "\n" + os.path.basename(changes_file)
813
814             do_pkg (changes_file, session)
815
816     end()
817
818 ################################################################################
819
820 if __name__ == '__main__':
821     main()