]> git.decadent.org.uk Git - dak.git/blob - dak/process_new.py
[???] various (aj?) changes
[dak.git] / dak / process_new.py
1 #!/usr/bin/env python
2
3 # Handles NEW and BYHAND packages
4 # Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 ################################################################################
21
22 # 23:12|<aj> I will not hush!
23 # 23:12|<elmo> :>
24 # 23:12|<aj> Where there is injustice in the world, I shall be there!
25 # 23:13|<aj> I shall not be silenced!
26 # 23:13|<aj> The world shall know!
27 # 23:13|<aj> The world *must* know!
28 # 23:13|<elmo> oh dear, he's gone back to powerpuff girls... ;-)
29 # 23:13|<aj> yay powerpuff girls!!
30 # 23:13|<aj> buttercup's my favourite, who's yours?
31 # 23:14|<aj> you're backing away from the keyboard right now aren't you?
32 # 23:14|<aj> *AREN'T YOU*?!
33 # 23:15|<aj> I will not be treated like this.
34 # 23:15|<aj> I shall have my revenge.
35 # 23:15|<aj> I SHALL!!!
36
37 ################################################################################
38
39 import copy, errno, os, readline, stat, sys, time
40 import apt_pkg, apt_inst
41 import examine_package
42 import daklib.database
43 import daklib.logging
44 import daklib.queue 
45 import daklib.utils
46
47 # Globals
48 Cnf = None
49 Options = None
50 Upload = None
51 projectB = None
52 Logger = None
53
54 Priorities = None
55 Sections = None
56
57 reject_message = ""
58
59 ################################################################################
60 ################################################################################
61 ################################################################################
62
63 def reject (str, prefix="Rejected: "):
64     global reject_message
65     if str:
66         reject_message += prefix + str + "\n"
67
68 def recheck():
69     global reject_message
70     files = Upload.pkg.files
71     reject_message = ""
72
73     for file in files.keys():
74         # The .orig.tar.gz can disappear out from under us is it's a
75         # duplicate of one in the archive.
76         if not files.has_key(file):
77             continue
78         # Check that the source still exists
79         if files[file]["type"] == "deb":
80             source_version = files[file]["source version"]
81             source_package = files[file]["source package"]
82             if not Upload.pkg.changes["architecture"].has_key("source") \
83                and not Upload.source_exists(source_package, source_version, Upload.pkg.changes["distribution"].keys()):
84                 source_epochless_version = daklib.utils.re_no_epoch.sub('', source_version)
85                 dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
86                 found = 0
87                 for q in ["Accepted", "Embargoed", "Unembargoed"]:
88                     if os.path.exists(Cnf["Dir::Queue::%s" % (q)] + '/' + dsc_filename):
89                         found = 1
90                 if not found:
91                     reject("no source found for %s %s (%s)." % (source_package, source_version, file))
92
93         # Version and file overwrite checks
94         if files[file]["type"] == "deb":
95             reject(Upload.check_binary_against_db(file))
96         elif files[file]["type"] == "dsc":
97             reject(Upload.check_source_against_db(file))
98             (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(file)
99             reject(reject_msg, "")
100
101     if reject_message.find("Rejected") != -1:
102         answer = "XXX"
103         if Options["No-Action"] or Options["Automatic"]:
104             answer = 'S'
105
106         print "REJECT\n" + reject_message,
107         prompt = "[R]eject, Skip, Quit ?"
108
109         while prompt.find(answer) == -1:
110             answer = daklib.utils.our_raw_input(prompt)
111             m = daklib.queue.re_default_answer.match(prompt)
112             if answer == "":
113                 answer = m.group(1)
114             answer = answer[:1].upper()
115
116         if answer == 'R':
117             Upload.do_reject(0, reject_message)
118             os.unlink(Upload.pkg.changes_file[:-8]+".dak")
119             return 0
120         elif answer == 'S':
121             return 0
122         elif answer == 'Q':
123             sys.exit(0)
124
125     return 1
126
127 ################################################################################
128
129 def indiv_sg_compare (a, b):
130     """Sort by source name, source, version, 'have source', and
131        finally by filename."""
132     # Sort by source version
133     q = apt_pkg.VersionCompare(a["version"], b["version"])
134     if q:
135         return -q
136
137     # Sort by 'have source'
138     a_has_source = a["architecture"].get("source")
139     b_has_source = b["architecture"].get("source")
140     if a_has_source and not b_has_source:
141         return -1
142     elif b_has_source and not a_has_source:
143         return 1
144
145     return cmp(a["filename"], b["filename"])
146
147 ############################################################
148
149 def sg_compare (a, b):
150     a = a[1]
151     b = b[1]
152     """Sort by have note, time of oldest upload."""
153     # Sort by have note
154     a_note_state = a["note_state"]
155     b_note_state = b["note_state"]
156     if a_note_state < b_note_state:
157         return -1
158     elif a_note_state > b_note_state:
159         return 1
160
161     # Sort by time of oldest upload
162     return cmp(a["oldest"], b["oldest"])
163
164 def sort_changes(changes_files):
165     """Sort into source groups, then sort each source group by version,
166     have source, filename.  Finally, sort the source groups by have
167     note, time of oldest upload of each source upload."""
168     if len(changes_files) == 1:
169         return changes_files
170
171     sorted_list = []
172     cache = {}
173     # Read in all the .changes files
174     for filename in changes_files:
175         try:
176             Upload.pkg.changes_file = filename
177             Upload.init_vars()
178             Upload.update_vars()
179             cache[filename] = copy.copy(Upload.pkg.changes)
180             cache[filename]["filename"] = filename
181         except:
182             sorted_list.append(filename)
183             break
184     # Divide the .changes into per-source groups
185     per_source = {}
186     for filename in cache.keys():
187         source = cache[filename]["source"]
188         if not per_source.has_key(source):
189             per_source[source] = {}
190             per_source[source]["list"] = []
191         per_source[source]["list"].append(cache[filename])
192     # Determine oldest time and have note status for each source group
193     for source in per_source.keys():
194         source_list = per_source[source]["list"]
195         first = source_list[0]
196         oldest = os.stat(first["filename"])[stat.ST_MTIME]
197         have_note = 0
198         for d in per_source[source]["list"]:
199             mtime = os.stat(d["filename"])[stat.ST_MTIME]
200             if mtime < oldest:
201                 oldest = mtime
202             have_note += (d.has_key("process-new note"))
203         per_source[source]["oldest"] = oldest
204         if not have_note:
205             per_source[source]["note_state"] = 0; # none
206         elif have_note < len(source_list):
207             per_source[source]["note_state"] = 1; # some
208         else:
209             per_source[source]["note_state"] = 2; # all
210         per_source[source]["list"].sort(indiv_sg_compare)
211     per_source_items = per_source.items()
212     per_source_items.sort(sg_compare)
213     for i in per_source_items:
214         for j in i[1]["list"]:
215             sorted_list.append(j["filename"])
216     return sorted_list
217
218 ################################################################################
219
220 class Section_Completer:
221     def __init__ (self):
222         self.sections = []
223         q = projectB.query("SELECT section FROM section")
224         for i in q.getresult():
225             self.sections.append(i[0])
226
227     def complete(self, text, state):
228         if state == 0:
229             self.matches = []
230             n = len(text)
231             for word in self.sections:
232                 if word[:n] == text:
233                     self.matches.append(word)
234         try:
235             return self.matches[state]
236         except IndexError:
237             return None
238
239 ############################################################
240
241 class Priority_Completer:
242     def __init__ (self):
243         self.priorities = []
244         q = projectB.query("SELECT priority FROM priority")
245         for i in q.getresult():
246             self.priorities.append(i[0])
247
248     def complete(self, text, state):
249         if state == 0:
250             self.matches = []
251             n = len(text)
252             for word in self.priorities:
253                 if word[:n] == text:
254                     self.matches.append(word)
255         try:
256             return self.matches[state]
257         except IndexError:
258             return None
259
260 ################################################################################
261
262 def print_new (new, indexed, file=sys.stdout):
263     daklib.queue.check_valid(new)
264     broken = 0
265     index = 0
266     for pkg in new.keys():
267         index += 1
268         section = new[pkg]["section"]
269         priority = new[pkg]["priority"]
270         if new[pkg]["section id"] == -1:
271             section += "[!]"
272             broken = 1
273         if new[pkg]["priority id"] == -1:
274             priority += "[!]"
275             broken = 1
276         if indexed:
277             line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
278         else:
279             line = "%-20s %-20s %-20s" % (pkg, priority, section)
280         line = line.strip()+'\n'
281         file.write(line)
282     note = Upload.pkg.changes.get("process-new note")
283     if note:
284         print "*"*75
285         print note
286         print "*"*75
287     return broken, note
288
289 ################################################################################
290
291 def index_range (index):
292     if index == 1:
293         return "1"
294     else:
295         return "1-%s" % (index)
296
297 ################################################################################
298 ################################################################################
299
300 def edit_new (new):
301     # Write the current data to a temporary file
302     temp_filename = daklib.utils.temp_filename()
303     temp_file = daklib.utils.open_file(temp_filename, 'w')
304     print_new (new, 0, temp_file)
305     temp_file.close()
306     # Spawn an editor on that file
307     editor = os.environ.get("EDITOR","vi")
308     result = os.system("%s %s" % (editor, temp_filename))
309     if result != 0:
310         daklib.utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
311     # Read the edited data back in
312     temp_file = daklib.utils.open_file(temp_filename)
313     lines = temp_file.readlines()
314     temp_file.close()
315     os.unlink(temp_filename)
316     # Parse the new data
317     for line in lines:
318         line = line.strip()
319         if line == "":
320             continue
321         s = line.split()
322         # Pad the list if necessary
323         s[len(s):3] = [None] * (3-len(s))
324         (pkg, priority, section) = s[:3]
325         if not new.has_key(pkg):
326             daklib.utils.warn("Ignoring unknown package '%s'" % (pkg))
327         else:
328             # Strip off any invalid markers, print_new will readd them.
329             if section.endswith("[!]"):
330                 section = section[:-3]
331             if priority.endswith("[!]"):
332                 priority = priority[:-3]
333             for file in new[pkg]["files"]:
334                 Upload.pkg.files[file]["section"] = section
335                 Upload.pkg.files[file]["priority"] = priority
336             new[pkg]["section"] = section
337             new[pkg]["priority"] = priority
338
339 ################################################################################
340
341 def edit_index (new, index):
342     priority = new[index]["priority"]
343     section = new[index]["section"]
344     type = new[index]["type"]
345     done = 0
346     while not done:
347         print "\t".join([index, priority, section])
348
349         answer = "XXX"
350         if type != "dsc":
351             prompt = "[B]oth, Priority, Section, Done ? "
352         else:
353             prompt = "[S]ection, Done ? "
354         edit_priority = edit_section = 0
355
356         while prompt.find(answer) == -1:
357             answer = daklib.utils.our_raw_input(prompt)
358             m = daklib.queue.re_default_answer.match(prompt)
359             if answer == "":
360                 answer = m.group(1)
361             answer = answer[:1].upper()
362
363         if answer == 'P':
364             edit_priority = 1
365         elif answer == 'S':
366             edit_section = 1
367         elif answer == 'B':
368             edit_priority = edit_section = 1
369         elif answer == 'D':
370             done = 1
371
372         # Edit the priority
373         if edit_priority:
374             readline.set_completer(Priorities.complete)
375             got_priority = 0
376             while not got_priority:
377                 new_priority = daklib.utils.our_raw_input("New priority: ").strip()
378                 if new_priority not in Priorities.priorities:
379                     print "E: '%s' is not a valid priority, try again." % (new_priority)
380                 else:
381                     got_priority = 1
382                     priority = new_priority
383
384         # Edit the section
385         if edit_section:
386             readline.set_completer(Sections.complete)
387             got_section = 0
388             while not got_section:
389                 new_section = daklib.utils.our_raw_input("New section: ").strip()
390                 if new_section not in Sections.sections:
391                     print "E: '%s' is not a valid section, try again." % (new_section)
392                 else:
393                     got_section = 1
394                     section = new_section
395
396         # Reset the readline completer
397         readline.set_completer(None)
398
399     for file in new[index]["files"]:
400         Upload.pkg.files[file]["section"] = section
401         Upload.pkg.files[file]["priority"] = priority
402     new[index]["priority"] = priority
403     new[index]["section"] = section
404     return new
405
406 ################################################################################
407
408 def edit_overrides (new):
409     print
410     done = 0
411     while not done:
412         print_new (new, 1)
413         new_index = {}
414         index = 0
415         for i in new.keys():
416             index += 1
417             new_index[index] = i
418
419         prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
420
421         got_answer = 0
422         while not got_answer:
423             answer = daklib.utils.our_raw_input(prompt)
424             if not answer.isdigit():
425                 answer = answer[:1].upper()
426             if answer == "E" or answer == "D":
427                 got_answer = 1
428             elif daklib.queue.re_isanum.match (answer):
429                 answer = int(answer)
430                 if (answer < 1) or (answer > index):
431                     print "%s is not a valid index (%s).  Please retry." % (answer, index_range(index))
432                 else:
433                     got_answer = 1
434
435         if answer == 'E':
436             edit_new(new)
437         elif answer == 'D':
438             done = 1
439         else:
440             edit_index (new, new_index[answer])
441
442     return new
443
444 ################################################################################
445
446 def edit_note(note):
447     # Write the current data to a temporary file
448     temp_filename = daklib.utils.temp_filename()
449     temp_file = daklib.utils.open_file(temp_filename, 'w')
450     temp_file.write(note)
451     temp_file.close()
452     editor = os.environ.get("EDITOR","vi")
453     answer = 'E'
454     while answer == 'E':
455         os.system("%s %s" % (editor, temp_filename))
456         temp_file = daklib.utils.open_file(temp_filename)
457         note = temp_file.read().rstrip()
458         temp_file.close()
459         print "Note:"
460         print daklib.utils.prefix_multi_line_string(note,"  ")
461         prompt = "[D]one, Edit, Abandon, Quit ?"
462         answer = "XXX"
463         while prompt.find(answer) == -1:
464             answer = daklib.utils.our_raw_input(prompt)
465             m = daklib.queue.re_default_answer.search(prompt)
466             if answer == "":
467                 answer = m.group(1)
468             answer = answer[:1].upper()
469     os.unlink(temp_filename)
470     if answer == 'A':
471         return
472     elif answer == 'Q':
473         sys.exit(0)
474     Upload.pkg.changes["process-new note"] = note
475     Upload.dump_vars(Cnf["Dir::Queue::New"])
476
477 ################################################################################
478
479 def check_pkg ():
480     try:
481         less_fd = os.popen("less -R -", 'w', 0)
482         stdout_fd = sys.stdout
483         try:
484             sys.stdout = less_fd
485             examine_package.display_changes(Upload.pkg.changes_file)
486             files = Upload.pkg.files
487             for file in files.keys():
488                 if files[file].has_key("new"):
489                     type = files[file]["type"]
490                     if type == "deb":
491                         examine_package.check_deb(file)
492                     elif type == "dsc":
493                         examine_package.check_dsc(file)
494         finally:
495             sys.stdout = stdout_fd
496     except IOError, e:
497         if errno.errorcode[e.errno] == 'EPIPE':
498             daklib.utils.warn("[examine_package] Caught EPIPE; skipping.")
499             pass
500         else:
501             raise
502     except KeyboardInterrupt:
503         daklib.utils.warn("[examine_package] Caught C-c; skipping.")
504         pass
505
506 ################################################################################
507
508 ## FIXME: horribly Debian specific
509
510 def do_bxa_notification():
511     files = Upload.pkg.files
512     summary = ""
513     for file in files.keys():
514         if files[file]["type"] == "deb":
515             control = apt_pkg.ParseSection(apt_inst.debExtractControl(daklib.utils.open_file(file)))
516             summary += "\n"
517             summary += "Package: %s\n" % (control.Find("Package"))
518             summary += "Description: %s\n" % (control.Find("Description"))
519     Upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
520     bxa_mail = daklib.utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-new.bxa_notification")
521     daklib.utils.send_mail(bxa_mail)
522
523 ################################################################################
524
525 def add_overrides (new):
526     changes = Upload.pkg.changes
527     files = Upload.pkg.files
528
529     projectB.query("BEGIN WORK")
530     for suite in changes["suite"].keys():
531         suite_id = daklib.database.get_suite_id(suite)
532         for pkg in new.keys():
533             component_id = daklib.database.get_component_id(new[pkg]["component"])
534             type_id = daklib.database.get_override_type_id(new[pkg]["type"])
535             priority_id = new[pkg]["priority id"]
536             section_id = new[pkg]["section id"]
537             projectB.query("INSERT INTO override (suite, component, type, package, priority, section, maintainer) VALUES (%s, %s, %s, '%s', %s, %s, '')" % (suite_id, component_id, type_id, pkg, priority_id, section_id))
538             for file in new[pkg]["files"]:
539                 if files[file].has_key("new"):
540                     del files[file]["new"]
541             del new[pkg]
542
543     projectB.query("COMMIT WORK")
544
545     if Cnf.FindB("Dinstall::BXANotify"):
546         do_bxa_notification()
547
548 ################################################################################
549
550 def prod_maintainer ():
551     # Here we prepare an editor and get them ready to prod...
552     temp_filename = daklib.utils.temp_filename()
553     editor = os.environ.get("EDITOR","vi")
554     answer = 'E'
555     while answer == 'E':
556         os.system("%s %s" % (editor, temp_filename))
557         file = daklib.utils.open_file(temp_filename)
558         prod_message = "".join(file.readlines())
559         file.close()
560         print "Prod message:"
561         print daklib.utils.prefix_multi_line_string(prod_message,"  ",include_blank_lines=1)
562         prompt = "[P]rod, Edit, Abandon, Quit ?"
563         answer = "XXX"
564         while prompt.find(answer) == -1:
565             answer = daklib.utils.our_raw_input(prompt)
566             m = daklib.queue.re_default_answer.search(prompt)
567             if answer == "":
568                 answer = m.group(1)
569             answer = answer[:1].upper()
570         os.unlink(temp_filename)
571         if answer == 'A':
572             return
573         elif answer == 'Q':
574             sys.exit(0)
575     # Otherwise, do the proding...
576     user_email_address = daklib.utils.whoami() + " <%s>" % (
577         Cnf["Dinstall::MyAdminAddress"])
578
579     Subst = Upload.Subst
580
581     Subst["__FROM_ADDRESS__"] = user_email_address
582     Subst["__PROD_MESSAGE__"] = prod_message
583     Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
584
585     prod_mail_message = daklib.utils.TemplateSubst(
586         Subst,Cnf["Dir::Templates"]+"/process-new.prod")
587
588     # Send the prod mail if appropriate
589     if not Cnf["Dinstall::Options::No-Mail"]:
590         daklib.utils.send_mail(prod_mail_message)
591
592     print "Sent proding message"
593
594 ################################################################################
595
596 def do_new():
597     print "NEW\n"
598     files = Upload.pkg.files
599     changes = Upload.pkg.changes
600
601     # Make a copy of distribution we can happily trample on
602     changes["suite"] = copy.copy(changes["distribution"])
603
604     # Fix up the list of target suites
605     for suite in changes["suite"].keys():
606         override = Cnf.Find("Suite::%s::OverrideSuite" % (suite))
607         if override:
608             (olderr, newerr) = (daklib.database.get_suite_id(suite) == -1,
609               daklib.database.get_suite_id(override) == -1)
610             if olderr or newerr:
611                 (oinv, newinv) = ("", "")
612                 if olderr: oinv = "invalid "
613                 if newerr: ninv = "invalid "
614                 print "warning: overriding %ssuite %s to %ssuite %s" % (
615                         oinv, suite, ninv, override)
616             del changes["suite"][suite]
617             changes["suite"][override] = 1
618     # Validate suites
619     for suite in changes["suite"].keys():
620         suite_id = daklib.database.get_suite_id(suite)
621         if suite_id == -1:
622             daklib.utils.fubar("%s has invalid suite '%s' (possibly overriden).  say wha?" % (changes, suite))
623
624     # The main NEW processing loop
625     done = 0
626     while not done:
627         # Find out what's new
628         new = daklib.queue.determine_new(changes, files, projectB)
629
630         if not new:
631             break
632
633         answer = "XXX"
634         if Options["No-Action"] or Options["Automatic"]:
635             answer = 'S'
636
637         (broken, note) = print_new(new, 0)
638         prompt = ""
639
640         if not broken and not note:
641             prompt = "Add overrides, "
642         if broken:
643             print "W: [!] marked entries must be fixed before package can be processed."
644         if note:
645             print "W: note must be removed before package can be processed."
646             prompt += "Remove note, "
647
648         prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
649
650         while prompt.find(answer) == -1:
651             answer = daklib.utils.our_raw_input(prompt)
652             m = daklib.queue.re_default_answer.search(prompt)
653             if answer == "":
654                 answer = m.group(1)
655             answer = answer[:1].upper()
656
657         if answer == 'A':
658             done = add_overrides (new)
659         elif answer == 'C':
660             check_pkg()
661         elif answer == 'E':
662             new = edit_overrides (new)
663         elif answer == 'M':
664             aborted = Upload.do_reject(1, Options["Manual-Reject"])
665             if not aborted:
666                 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
667                 done = 1
668         elif answer == 'N':
669             edit_note(changes.get("process-new note", ""))
670         elif answer == 'P':
671             prod_maintainer()
672         elif answer == 'R':
673             confirm = daklib.utils.our_raw_input("Really clear note (y/N)? ").lower()
674             if confirm == "y":
675                 del changes["process-new note"]
676         elif answer == 'S':
677             done = 1
678         elif answer == 'Q':
679             sys.exit(0)
680
681 ################################################################################
682 ################################################################################
683 ################################################################################
684
685 def usage (exit_code=0):
686     print """Usage: dak process-new [OPTION]... [CHANGES]...
687   -a, --automatic           automatic run
688   -h, --help                show this help and exit.
689   -m, --manual-reject=MSG   manual reject with `msg'
690   -n, --no-action           don't do anything
691   -V, --version             display the version number and exit"""
692     sys.exit(exit_code)
693
694 ################################################################################
695
696 def init():
697     global Cnf, Options, Logger, Upload, projectB, Sections, Priorities
698
699     Cnf = daklib.utils.get_conf()
700
701     Arguments = [('a',"automatic","Process-New::Options::Automatic"),
702                  ('h',"help","Process-New::Options::Help"),
703                  ('C',"comments-dir","Process-New::Options::Comments-Dir", "HasArg"),
704                  ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
705                  ('n',"no-action","Process-New::Options::No-Action")]
706
707     for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir"]:
708         if not Cnf.has_key("Process-New::Options::%s" % (i)):
709             Cnf["Process-New::Options::%s" % (i)] = ""
710
711     changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
712     Options = Cnf.SubTree("Process-New::Options")
713
714     if Options["Help"]:
715         usage()
716
717     Upload = daklib.queue.Upload(Cnf)
718
719     if not Options["No-Action"]:
720         Logger = Upload.Logger = daklib.logging.Logger(Cnf, "process-new")
721
722     projectB = Upload.projectB
723
724     Sections = Section_Completer()
725     Priorities = Priority_Completer()
726     readline.parse_and_bind("tab: complete")
727
728     return changes_files
729
730 ################################################################################
731
732 def do_byhand():
733     done = 0
734     while not done:
735         files = Upload.pkg.files
736         will_install = 1
737         byhand = []
738
739         for file in files.keys():
740             if files[file]["type"] == "byhand":
741                 if os.path.exists(file):
742                     print "W: %s still present; please process byhand components and try again." % (file)
743                     will_install = 0
744                 else:
745                     byhand.append(file)
746
747         answer = "XXXX"
748         if Options["No-Action"]:
749             answer = "S"
750         if will_install:
751             if Options["Automatic"] and not Options["No-Action"]:
752                 answer = 'A'
753             prompt = "[A]ccept, Manual reject, Skip, Quit ?"
754         else:
755             prompt = "Manual reject, [S]kip, Quit ?"
756
757         while prompt.find(answer) == -1:
758             answer = daklib.utils.our_raw_input(prompt)
759             m = daklib.queue.re_default_answer.search(prompt)
760             if answer == "":
761                 answer = m.group(1)
762             answer = answer[:1].upper()
763
764         if answer == 'A':
765             done = 1
766             for file in byhand:
767                 del files[file]
768         elif answer == 'M':
769             Upload.do_reject(1, Options["Manual-Reject"])
770             os.unlink(Upload.pkg.changes_file[:-8]+".dak")
771             done = 1
772         elif answer == 'S':
773             done = 1
774         elif answer == 'Q':
775             sys.exit(0)
776
777 ################################################################################
778
779 def get_accept_lock():
780     retry = 0
781     while retry < 10:
782         try:
783             lock_fd = os.open(Cnf["Process-New::AcceptedLockFile"], os.O_RDONLY | os.O_CREAT | os.O_EXCL)
784             retry = 10
785         except OSError, e:
786             if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
787                 retry += 1
788                 if (retry >= 10):
789                     daklib.utils.fubar("Couldn't obtain lock; assuming 'dak process-unchecked' is already running.")
790                 else:
791                     print("Unable to get accepted lock (try %d of 10)" % retry)
792                 time.sleep(60)
793             else:
794                 raise
795
796 def move_to_dir (dest, perms=0660, changesperms=0664):
797     daklib.utils.move (Upload.pkg.changes_file, dest, perms=changesperms)
798     file_keys = Upload.pkg.files.keys()
799     for file in file_keys:
800         daklib.utils.move (file, dest, perms=perms)
801
802 def do_accept():
803     print "ACCEPT"
804     if not Options["No-Action"]:
805         get_accept_lock()
806         (summary, short_summary) = Upload.build_summaries()
807         if Cnf.FindB("Dinstall::SecurityQueueHandling"):
808             Upload.dump_vars(Cnf["Dir::Queue::Embargoed"])
809             move_to_dir(Cnf["Dir::Queue::Embargoed"])
810             Upload.queue_build("embargoed", Cnf["Dir::Queue::Embargoed"])
811             # Check for override disparities
812             Upload.Subst["__SUMMARY__"] = summary
813         else:
814             Upload.accept(summary, short_summary)
815         os.unlink(Upload.pkg.changes_file[:-8]+".dak")
816         os.unlink(Cnf["Process-New::AcceptedLockFile"])
817
818 def check_status(files):
819     new = byhand = 0
820     for file in files.keys():
821         if files[file]["type"] == "byhand":
822             byhand = 1
823         elif files[file].has_key("new"):
824             new = 1
825     return (new, byhand)
826
827 def do_pkg(changes_file):
828     Upload.pkg.changes_file = changes_file
829     Upload.init_vars()
830     Upload.update_vars()
831     Upload.update_subst()
832     files = Upload.pkg.files
833
834     if not recheck():
835         return
836
837     (new, byhand) = check_status(files)
838     if new or byhand:
839         if new:
840             do_new()
841         if byhand:
842             do_byhand()
843         (new, byhand) = check_status(files)
844
845     if not new and not byhand:
846         do_accept()
847
848 ################################################################################
849
850 def end():
851     accept_count = Upload.accept_count
852     accept_bytes = Upload.accept_bytes
853
854     if accept_count:
855         sets = "set"
856         if accept_count > 1:
857             sets = "sets"
858         sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, daklib.utils.size_type(int(accept_bytes))))
859         Logger.log(["total",accept_count,accept_bytes])
860
861     if not Options["No-Action"]:
862         Logger.close()
863
864 ################################################################################
865
866 def do_comments(dir, opref, npref, line, fn):
867     for comm in [ x for x in os.listdir(dir) if x.startswith(opref) ]:
868         lines = open("%s/%s" % (dir, comm)).readlines()
869         if len(lines) == 0 or lines[0] != line + "\n": continue
870         changes_files = [ x for x in os.listdir(".") if x.startswith(comm[7:]+"_")
871                                 and x.endswith(".changes") ]
872         changes_files = sort_changes(changes_files)
873         for f in changes_files:
874                 f = daklib.utils.validate_changes_file_arg(f, 0)
875                 if not f: continue
876                 print "\n" + f
877                 fn(f, "".join(lines[1:]))
878
879         if opref != npref and not Options["No-Action"]:
880                 newcomm = npref + comm[len(opref):]
881                 os.rename("%s/%s" % (dir, comm), "%s/%s" % (dir, newcomm))
882
883 ################################################################################
884
885 def comment_accept(changes_file, comments):
886     Upload.pkg.changes_file = changes_file
887     Upload.init_vars()
888     Upload.update_vars()
889     Upload.update_subst()
890     files = Upload.pkg.files
891
892     if not recheck():
893         return # dak wants to REJECT, crap
894
895     (new, byhand) = check_status(files)
896     if not new and not byhand:
897         do_accept()
898
899 ################################################################################
900
901 def comment_reject(changes_file, comments):
902     Upload.pkg.changes_file = changes_file
903     Upload.init_vars()
904     Upload.update_vars()
905     Upload.update_subst()
906     files = Upload.pkg.files
907
908     if not recheck():
909         pass # dak has its own reasons to reject as well, which is fine
910
911     reject(comments)
912     print "REJECT\n" + reject_message,
913     if not Options["No-Action"]:
914         Upload.do_reject(0, reject_message)
915         os.unlink(Upload.pkg.changes_file[:-8]+".dak")
916
917 ################################################################################
918
919 def main():
920     changes_files = init()
921     if len(changes_files) > 50:
922         sys.stderr.write("Sorting changes...\n")
923     changes_files = sort_changes(changes_files)
924
925     # Kill me now? **FIXME**
926     Cnf["Dinstall::Options::No-Mail"] = ""
927     bcc = "X-DAK: dak process-new\nX-Katie: lisa $Revision: 1.31 $"
928     if Cnf.has_key("Dinstall::Bcc"):
929         Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
930     else:
931         Upload.Subst["__BCC__"] = bcc
932
933     commentsdir = Cnf.get("Process-New::Options::Comments-Dir","")
934     if commentsdir:
935         if changes_files != []:
936                 sys.stderr.write("Can't specify any changes files if working with comments-dir")
937                 sys.exit(1)
938         do_comments(commentsdir, "ACCEPT.", "ACCEPTED.", "OK", comment_accept)
939         do_comments(commentsdir, "REJECT.", "REJECTED.", "NOTOK", comment_reject)
940     else:
941         for changes_file in changes_files:
942             changes_file = daklib.utils.validate_changes_file_arg(changes_file, 0)
943             if not changes_file:
944                 continue
945             print "\n" + changes_file
946             do_pkg (changes_file)
947
948     end()
949
950 ################################################################################
951
952 if __name__ == '__main__':
953     main()