]> git.decadent.org.uk Git - dak.git/blob - dak/process_new.py
Merge remote-tracking branch 'ansgar/p-s-from-db' 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 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     try:
341         less_fd = os.popen("less -R -", 'w', 0)
342         stdout_fd = sys.stdout
343         try:
344             sys.stdout = less_fd
345             changes = utils.parse_changes (upload.pkg.changes_file)
346             print examine_package.display_changes(changes['distribution'], upload.pkg.changes_file)
347             files = upload.pkg.files
348             for f in files.keys():
349                 if files[f].has_key("new"):
350                     ftype = files[f]["type"]
351                     if ftype == "deb":
352                         print examine_package.check_deb(changes['distribution'], f)
353                     elif ftype == "dsc":
354                         print examine_package.check_dsc(changes['distribution'], f)
355         finally:
356             print examine_package.output_package_relations()
357             sys.stdout = stdout_fd
358     except IOError, e:
359         if e.errno == errno.EPIPE:
360             utils.warn("[examine_package] Caught EPIPE; skipping.")
361             pass
362         else:
363             raise
364     except KeyboardInterrupt:
365         utils.warn("[examine_package] Caught C-c; skipping.")
366         pass
367
368 ################################################################################
369
370 ## FIXME: horribly Debian specific
371
372 def do_bxa_notification(upload):
373     files = upload.pkg.files
374     summary = ""
375     for f in files.keys():
376         if files[f]["type"] == "deb":
377             control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(f)))
378             summary += "\n"
379             summary += "Package: %s\n" % (control.Find("Package"))
380             summary += "Description: %s\n" % (control.Find("Description"))
381     upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
382     bxa_mail = utils.TemplateSubst(upload.Subst,Config()["Dir::Templates"]+"/process-new.bxa_notification")
383     utils.send_mail(bxa_mail)
384
385 ################################################################################
386
387 def add_overrides (new, upload, session):
388     changes = upload.pkg.changes
389     files = upload.pkg.files
390     srcpkg = changes.get("source")
391
392     for suite in changes["suite"].keys():
393         suite_id = get_suite(suite).suite_id
394         for pkg in new.keys():
395             component_id = get_component(new[pkg]["component"]).component_id
396             type_id = get_override_type(new[pkg]["type"]).overridetype_id
397             priority_id = new[pkg]["priority id"]
398             section_id = new[pkg]["section id"]
399             Logger.log(["%s overrides" % (srcpkg), suite, new[pkg]["component"], new[pkg]["type"], new[pkg]["priority"], new[pkg]["section"]])
400             session.execute("INSERT INTO override (suite, component, type, package, priority, section, maintainer) VALUES (:sid, :cid, :tid, :pkg, :pid, :sectid, '')",
401                             { 'sid': suite_id, 'cid': component_id, 'tid':type_id, 'pkg': pkg, 'pid': priority_id, 'sectid': section_id})
402             for f in new[pkg]["files"]:
403                 if files[f].has_key("new"):
404                     del files[f]["new"]
405             del new[pkg]
406
407     session.commit()
408
409     if Config().FindB("Dinstall::BXANotify"):
410         do_bxa_notification(upload)
411
412 ################################################################################
413
414 def do_new(upload, session):
415     print "NEW\n"
416     files = upload.pkg.files
417     upload.check_files(not Options["No-Action"])
418     changes = upload.pkg.changes
419     cnf = Config()
420
421     # Check for a valid distribution
422     upload.check_distributions()
423
424     # Make a copy of distribution we can happily trample on
425     changes["suite"] = copy.copy(changes["distribution"])
426
427     # Try to get an included dsc
428     dsc = None
429     (status, _) = upload.load_dsc()
430     if status:
431         dsc = upload.pkg.dsc
432
433     # The main NEW processing loop
434     done = 0
435     new = {}
436     while not done:
437         # Find out what's new
438         new, byhand = determine_new(upload.pkg.changes_file, changes, files, dsc=dsc, session=session, new=new)
439
440         if not new:
441             break
442
443         answer = "XXX"
444         if Options["No-Action"] or Options["Automatic"]:
445             answer = 'S'
446
447         (broken, note) = print_new(new, upload, indexed=0)
448         prompt = ""
449
450         if not broken and not note:
451             prompt = "Add overrides, "
452         if broken:
453             print "W: [!] marked entries must be fixed before package can be processed."
454         if note:
455             print "W: note must be removed before package can be processed."
456             prompt += "RemOve all notes, Remove note, "
457
458         prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
459
460         while prompt.find(answer) == -1:
461             answer = utils.our_raw_input(prompt)
462             m = re_default_answer.search(prompt)
463             if answer == "":
464                 answer = m.group(1)
465             answer = answer[:1].upper()
466
467         if answer in ( 'A', 'E', 'M', 'O', 'R' ) and Options["Trainee"]:
468             utils.warn("Trainees can't do that")
469             continue
470
471         if answer == 'A' and not Options["Trainee"]:
472             try:
473                 check_daily_lock()
474                 done = add_overrides (new, upload, session)
475                 new_accept(upload, Options["No-Action"], session)
476                 Logger.log(["NEW ACCEPT: %s" % (upload.pkg.changes_file)])
477             except CantGetLockError:
478                 print "Hello? Operator! Give me the number for 911!"
479                 print "Dinstall in the locked area, cant process packages, come back later"
480         elif answer == 'C':
481             check_pkg(upload)
482         elif answer == 'E' and not Options["Trainee"]:
483             new = edit_overrides (new, upload, session)
484         elif answer == 'M' and not Options["Trainee"]:
485             aborted = upload.do_reject(manual=1,
486                                        reject_message=Options["Manual-Reject"],
487                                        notes=get_new_comments(changes.get("source", ""), session=session))
488             if not aborted:
489                 upload.pkg.remove_known_changes(session=session)
490                 session.commit()
491                 Logger.log(["NEW REJECT: %s" % (upload.pkg.changes_file)])
492                 done = 1
493         elif answer == 'N':
494             edit_note(get_new_comments(changes.get("source", ""), session=session),
495                       upload, session, bool(Options["Trainee"]))
496         elif answer == 'P' and not Options["Trainee"]:
497             prod_maintainer(get_new_comments(changes.get("source", ""), session=session),
498                             upload)
499             Logger.log(["NEW PROD: %s" % (upload.pkg.changes_file)])
500         elif answer == 'R' and not Options["Trainee"]:
501             confirm = utils.our_raw_input("Really clear note (y/N)? ").lower()
502             if confirm == "y":
503                 for c in get_new_comments(changes.get("source", ""), changes.get("version", ""), session=session):
504                     session.delete(c)
505                 session.commit()
506         elif answer == 'O' and not Options["Trainee"]:
507             confirm = utils.our_raw_input("Really clear all notes (y/N)? ").lower()
508             if confirm == "y":
509                 for c in get_new_comments(changes.get("source", ""), session=session):
510                     session.delete(c)
511                 session.commit()
512
513         elif answer == 'S':
514             done = 1
515         elif answer == 'Q':
516             end()
517             sys.exit(0)
518
519 ################################################################################
520 ################################################################################
521 ################################################################################
522
523 def usage (exit_code=0):
524     print """Usage: dak process-new [OPTION]... [CHANGES]...
525   -a, --automatic           automatic run
526   -b, --no-binaries         do not sort binary-NEW packages first
527   -c, --comments            show NEW comments
528   -h, --help                show this help and exit.
529   -m, --manual-reject=MSG   manual reject with `msg'
530   -n, --no-action           don't do anything
531   -t, --trainee             FTP Trainee mode
532   -V, --version             display the version number and exit"""
533     sys.exit(exit_code)
534
535 ################################################################################
536
537 def do_byhand(upload, session):
538     done = 0
539     while not done:
540         files = upload.pkg.files
541         will_install = True
542         byhand = []
543
544         for f in files.keys():
545             if files[f]["section"] == "byhand":
546                 if os.path.exists(f):
547                     print "W: %s still present; please process byhand components and try again." % (f)
548                     will_install = False
549                 else:
550                     byhand.append(f)
551
552         answer = "XXXX"
553         if Options["No-Action"]:
554             answer = "S"
555         if will_install:
556             if Options["Automatic"] and not Options["No-Action"]:
557                 answer = 'A'
558             prompt = "[A]ccept, Manual reject, Skip, Quit ?"
559         else:
560             prompt = "Manual reject, [S]kip, Quit ?"
561
562         while prompt.find(answer) == -1:
563             answer = utils.our_raw_input(prompt)
564             m = re_default_answer.search(prompt)
565             if answer == "":
566                 answer = m.group(1)
567             answer = answer[:1].upper()
568
569         if answer == 'A':
570             dbchg = get_dbchange(upload.pkg.changes_file, session)
571             if dbchg is None:
572                 print "Warning: cannot find changes file in database; can't process BYHAND"
573             else:
574                 try:
575                     check_daily_lock()
576                     done = 1
577                     for b in byhand:
578                         # Find the file entry in the database
579                         found = False
580                         for f in dbchg.files:
581                             if f.filename == b:
582                                 found = True
583                                 f.processed = True
584                                 break
585
586                         if not found:
587                             print "Warning: Couldn't find BYHAND item %s in the database to mark it processed" % b
588
589                     session.commit()
590                     Logger.log(["BYHAND ACCEPT: %s" % (upload.pkg.changes_file)])
591                 except CantGetLockError:
592                     print "Hello? Operator! Give me the number for 911!"
593                     print "Dinstall in the locked area, cant process packages, come back later"
594         elif answer == 'M':
595             aborted = upload.do_reject(manual=1,
596                                        reject_message=Options["Manual-Reject"],
597                                        notes=get_new_comments(changes.get("source", ""), session=session))
598             if not aborted:
599                 upload.pkg.remove_known_changes(session=session)
600                 session.commit()
601                 Logger.log(["BYHAND REJECT: %s" % (upload.pkg.changes_file)])
602                 done = 1
603         elif answer == 'S':
604             done = 1
605         elif answer == 'Q':
606             end()
607             sys.exit(0)
608
609 ################################################################################
610
611 def check_daily_lock():
612     """
613     Raises CantGetLockError if the dinstall daily.lock exists.
614     """
615
616     cnf = Config()
617     try:
618         os.open(cnf["Process-New::DinstallLockFile"],
619                 os.O_RDONLY | os.O_CREAT | os.O_EXCL)
620     except OSError, e:
621         if e.errno == errno.EEXIST or e.errno == errno.EACCES:
622             raise CantGetLockError
623
624     os.unlink(cnf["Process-New::DinstallLockFile"])
625
626
627 @contextlib.contextmanager
628 def lock_package(package):
629     """
630     Lock C{package} so that noone else jumps in processing it.
631
632     @type package: string
633     @param package: source package name to lock
634     """
635
636     path = os.path.join(Config()["Process-New::LockDir"], package)
637     try:
638         fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDONLY)
639     except OSError, 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, 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(cnf, "process-new")
792         except CantOpenError, 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()