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