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