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