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