3 # Handles NEW and BYHAND packages
4 # Copyright (C) 2001 James Troup <james@nocrew.org>
5 # $Id: lisa,v 1.3 2002-02-22 01:03:07 troup Exp $
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.
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.
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
21 ################################################################################
23 # 23:12|<aj> I will not hush!
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!!!
38 ################################################################################
43 # We don't check error codes very thoroughly; the old 'trust jennifer'
44 # chess nut... db_access calls in particular
49 # Handle multiple different section/priorities (?)
50 # hardcoded debianness (debian-installer, source priority etc.) (?)
51 # Slang/ncurses interface (?)
52 # write changed sections/priority back to katie for later processing (?)
54 ################################################################################
56 import errno, os, readline, string, stat, sys, tempfile;
57 import apt_pkg, apt_inst;
58 import db_access, fernanda, katie, logging, utils;
61 lisa_version = "$Revision: 1.3 $";
72 ################################################################################
73 ################################################################################
74 ################################################################################
76 def determine_new (changes, files):
79 # Build up a list of potentially new things
80 for file in files.keys():
82 # Skip byhand elements
83 if f["type"] == "byhand":
86 priority = f["priority"];
87 section = f["section"];
89 component = f["component"];
93 if not new.has_key(pkg):
95 new[pkg]["priority"] = priority;
96 new[pkg]["section"] = section;
97 new[pkg]["type"] = type;
98 new[pkg]["component"] = component;
99 new[pkg]["files"] = [];
101 old_type = new[pkg]["type"];
103 # source gets trumped by deb or udeb
104 if old_type == "dsc":
105 new[pkg]["priority"] = priority;
106 new[pkg]["section"] = section;
107 new[pkg]["type"] = type;
108 new[pkg]["component"] = component;
109 new[pkg]["files"].append(file);
110 if f.has_key("othercomponents"):
111 new[pkg]["othercomponents"] = f["othercomponents"];
113 for suite in changes["distribution"].keys():
114 suite_id = db_access.get_suite_id(suite);
115 for pkg in new.keys():
116 component_id = db_access.get_component_id(new[pkg]["component"]);
117 type_id = db_access.get_override_type_id(new[pkg]["type"]);
118 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));
121 for file in new[pkg]["files"]:
122 if files[file].has_key("new"):
123 del files[file]["new"];
128 ################################################################################
130 # Sort by 'have source', by ctime, by source name, by source version number, by filename
132 def changes_compare_by_time (a, b):
134 a_changes = utils.parse_changes(a, 0)
139 b_changes = utils.parse_changes(b, 0)
143 utils.cc_fix_changes (a_changes);
144 utils.cc_fix_changes (b_changes);
146 # Sort by 'have source'
148 a_has_source = a_changes["architecture"].get("source")
149 b_has_source = b_changes["architecture"].get("source")
150 if a_has_source and not b_has_source:
152 elif b_has_source and not a_has_source:
156 a_ctime = os.stat(a)[stat.ST_CTIME];
157 b_ctime = os.stat(b)[stat.ST_CTIME];
158 q = cmp (a_ctime, b_ctime);
162 # Sort by source name
164 a_source = a_changes.get("source");
165 b_source = b_changes.get("source");
166 q = cmp (a_source, b_source);
170 # Sort by source version
172 a_version = a_changes.get("version");
173 b_version = b_changes.get("version");
174 q = apt_pkg.VersionCompare(a_version, b_version);
178 # Fall back to sort by filename
182 ################################################################################
184 class Section_Completer:
187 q = projectB.query("SELECT section FROM section");
188 for i in q.getresult():
189 self.sections.append(i[0]);
191 def complete(self, text, state):
195 for word in self.sections:
197 self.matches.append(word);
199 return self.matches[state]
203 ############################################################
205 class Priority_Completer:
207 self.priorities = [];
208 q = projectB.query("SELECT priority FROM priority");
209 for i in q.getresult():
210 self.priorities.append(i[0]);
212 def complete(self, text, state):
216 for word in self.priorities:
218 self.matches.append(word);
220 return self.matches[state]
224 ################################################################################
226 def check_valid (new):
227 for pkg in new.keys():
228 section = new[pkg]["section"];
229 priority = new[pkg]["priority"];
230 type = new[pkg]["type"];
231 new[pkg]["section id"] = db_access.get_section_id(section);
232 new[pkg]["priority id"] = db_access.get_priority_id(new[pkg]["priority"]);
234 if (section == "debian-installer" and type != "udeb") or \
235 (section != "debian-installer" and type == "udeb"):
236 new[pkg]["section id"] = -1;
237 if (priority == "source" and type != "dsc") or \
238 (priority != "source" and type == "dsc"):
239 new[pkg]["priority id"] = -1;
241 ################################################################################
243 def print_new (new, indexed, file=sys.stdout):
247 for pkg in new.keys():
249 section = new[pkg]["section"];
250 priority = new[pkg]["priority"];
251 if new[pkg]["section id"] == -1:
252 section = section + "[!]";
254 if new[pkg]["priority id"] == -1:
255 priority = priority + "[!]";
258 line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section);
260 line = "%-20s %-20s %-20s" % (pkg, priority, section);
261 line = string.strip(line)+'\n';
262 if new[pkg].has_key("othercomponents"):
263 line = line + "WARNING: Already present in %s distribution.\n" % (new[pkg]["othercomponents"]);
267 ################################################################################
271 if f.has_key("dbtype"):
273 elif f["type"] == "orig.tar.gz" or f["type"] == "tar.gz" or f["type"] == "diff.gz" or f["type"] == "dsc":
276 utils.fubar("invalid type (%s) for new. Dazed, confused and sure as heck not continuing." % (type));
278 # Validate the override type
279 type_id = db_access.get_override_type_id(type);
281 utils.fubar("invalid type (%s) for new. Say wha?" % (type));
285 ################################################################################
287 def index_range (index):
291 return "1-%s" % (index);
293 ################################################################################
294 ################################################################################
296 def spawn_editor (new):
297 # Write the current data to a temporary file
298 temp_filename = tempfile.mktemp();
299 fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
301 temp_file = utils.open_file(temp_filename, 'w');
302 print_new (new, 0, temp_file);
304 # Spawn an editor on that file
305 editor = os.environ.get("EDITOR","vi")
306 result = os.system("%s %s" % (editor, temp_filename))
308 utils.fubar ("vi invocation failed for `%s'!" % (temp_filename), result)
309 # Read the (edited) data back in
310 file = utils.open_file(temp_filename);
311 for line in file.readlines():
312 line = string.strip(line[:-1]);
315 s = string.split(line);
316 # Pad the list if necessary
317 s[len(s):3] = [None] * (3-len(s));
318 (pkg, priority, section) = s[:3];
319 if not new.has_key(pkg):
320 utils.warn("Ignoring unknown package '%s'" % (pkg));
322 # Strip off any invalid markers, print_new will readd them.
323 if section[-3:] == "[!]":
324 section = section[:-3];
325 if priority[-3:] == "[!]":
326 priority = priority[:-3];
327 for file in new[pkg]["files"]:
328 Katie.pkg.files[file]["section"] = section;
329 Katie.pkg.files[file]["priority"] = priority;
330 new[pkg]["section"] = section;
331 new[pkg]["priority"] = priority;
332 os.unlink(temp_filename);
334 ################################################################################
336 def edit_index (new, index):
337 priority = new[index]["priority"]
338 section = new[index]["section"]
339 type = new[index]["type"];
342 print string.join([index, priority, section], '\t');
346 prompt = "[B]oth, Priority, Section, Done ? ";
348 prompt = "[S]ection, Done ? ";
349 edit_priority = edit_section = 0;
351 while string.find(prompt, answer) == -1:
352 answer = raw_input(prompt);
353 m = katie.re_default_answer.match(prompt)
356 answer = string.upper(answer[:1])
363 edit_priority = edit_section = 1;
369 readline.set_completer(Priorities.complete);
371 while not got_priority:
372 new_priority = string.strip(raw_input("New priority: "));
373 if Priorities.priorities.count(new_priority) == 0:
374 print "E: '%s' is not a valid priority, try again." % (new_priority);
377 priority = new_priority;
381 readline.set_completer(Sections.complete);
383 while not got_section:
384 new_section = string.strip(raw_input("New section: "));
385 if Sections.sections.count(new_section) == 0:
386 print "E: '%s' is not a valid section, try again." % (new_section);
389 section = new_section;
391 # Reset the readline completer
392 readline.set_completer(None);
394 for file in new[index]["files"]:
395 Katie.pkg.files[file]["section"] = section;
396 Katie.pkg.files[file]["priority"] = priority;
397 new[index]["priority"] = priority;
398 new[index]["section"] = section;
401 ################################################################################
403 def edit_overrides (new):
412 new_index[index] = i;
414 prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index));
417 while not got_answer:
418 answer = raw_input(prompt)
419 answer = string.upper(answer[:1])
420 if answer == "E" or answer == "D":
422 elif katie.re_isanum.match (answer):
423 answer = int(answer);
424 if (answer < 1) or (answer > index):
425 print "%s is not a valid index (%s). Please retry." % (index_range(index), answer);
434 edit_index (new, new_index[answer]);
438 ################################################################################
442 less_fd = os.popen("less -", 'w', 0);
443 stdout_fd = sys.stdout;
445 sys.stdout = less_fd;
446 fernanda.display_changes(Katie.pkg.changes_file);
447 files = Katie.pkg.files;
448 for file in files.keys():
449 if files[file].has_key("new"):
450 type = files[file]["type"];
452 fernanda.check_deb(file);
454 fernanda.check_dsc(file);
456 sys.stdout = stdout_fd;
458 if errno.errorcode[e.errno] == 'EPIPE':
459 utils.warn("[fernanda] Caught EPIPE; skipping.");
463 except KeyboardInterrupt:
464 utils.warn("[fernanda] Caught C-c; skipping.");
467 ################################################################################
469 ## FIXME: horribly Debian specific
471 def do_bxa_notification():
472 files = Katie.pkg.files;
474 for file in files.keys():
475 if files[file]["type"] == "deb":
476 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)));
477 summary = summary + "\n";
478 summary = summary + "Package: %s\n" % (control.Find("Package"));
479 summary = summary + "Description: %s\n" % (control.Find("Description"));
480 Katie.Subst["__BINARY_DESCRIPTIONS__"] = summary;
481 bxa_mail = utils.TemplateSubst(Katie.Subst,open(Cnf["Dir::TemplatesDir"]+"/lisa.bxa_notification","r").read());
482 utils.send_mail(bxa_mail,"");
484 ################################################################################
486 def add_overrides (new):
487 changes = Katie.pkg.changes;
488 files = Katie.pkg.files;
490 projectB.query("BEGIN WORK");
491 for suite in changes["distribution"].keys():
492 suite_id = db_access.get_suite_id(suite);
493 for pkg in new.keys():
494 component_id = db_access.get_component_id(new[pkg]["component"]);
495 type_id = db_access.get_override_type_id(new[pkg]["type"]);
496 priority_id = new[pkg]["priority id"];
497 section_id = new[pkg]["section id"];
498 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));
499 for file in new[pkg]["files"]:
500 if files[file].has_key("new"):
501 del files[file]["new"];
504 projectB.query("COMMIT WORK");
506 if Cnf.FindI("Dinstall::BXANotify"):
507 do_bxa_notification();
509 ################################################################################
513 files = Katie.pkg.files;
514 changes = Katie.pkg.changes;
516 # Fix up the list of target suites
517 for suite in changes["distribution"].keys():
518 override = Cnf.Find("Suite::%s::OverrideSuite" % (suite));
520 del changes["distribution"][suite];
521 changes["distribution"][override] = 1;
523 for suite in changes["distribution"].keys():
524 suite_id = db_access.get_suite_id(suite);
526 utils.fubar("%s has invalid suite '%s' (possibly overriden). say wha?" % (changes, suite));
528 # The main NEW processing loop
531 # Find out what's new
532 new = determine_new(changes, files);
538 if Options["No-Action"] or Options["Automatic"]:
540 if Options["Automatic"]:
543 broken = print_new(new, 0);
546 prompt = "[A]dd overrides, ";
548 print "W: [!] marked entries must be fixed before package can be processed.";
551 prompt = prompt + "Edit overrides, Check, Manual reject, Skip, Quit ?";
553 while string.find(prompt, answer) == -1:
554 answer = raw_input(prompt)
555 m = katie.re_default_answer.match(prompt)
558 answer = string.upper(answer[:1])
561 done = add_overrides (new);
565 new = edit_overrides (new);
567 Katie.do_reject(1, Options["Manual-Reject"]);
568 os.unlink(Katie.pkg.changes_file[:-8]+".katie");
575 ################################################################################
576 ################################################################################
577 ################################################################################
579 def usage (exit_code=0):
580 print """Usage: lisa [OPTION]... [CHANGES]...
581 -a, --automatic automatic run
582 -h, --help show this help and exit.
583 -m, --manual-reject=MSG manual reject with `msg'
584 -n, --no-action don't do anything
585 -s, --sort=TYPE sort type ('time' or 'normal')
586 -V, --version display the version number and exit"""
589 ################################################################################
592 global Cnf, Options, Logger, Katie, projectB, Sections, Priorities;
594 Cnf = utils.get_conf();
596 Arguments = [('a',"automatic","Lisa::Options::Automatic"),
597 ('h',"help","Lisa::Options::Help"),
598 ('m',"manual-reject","Lisa::Options::Manual-Reject", "HasArg"),
599 ('n',"no-action","Lisa::Options::No-Action"),
600 ('s',"sort","Lisa::Options::Sort","HasArg"),
601 ('V',"version","Lisa::Options::Version")];
603 for i in ["automatic", "help", "manual-reject", "no-action", "version"]:
604 if not Cnf.has_key("Lisa::Options::%s" % (i)):
605 Cnf["Lisa::Options::%s" % (i)] = "";
606 if not Cnf.has_key("Lisa::Options::Sort"):
607 Cnf["Lisa::Options::Sort"] = "time";
609 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
610 Options = Cnf.SubTree("Lisa::Options")
615 if Options["Version"]:
616 print "lisa %s" % (lisa_version);
619 if Options["Sort"] != "time" and Options["Sort"] != "normal":
620 utils.fubar("Unrecognised sort type '%s'. (Recognised sort types are: time and normal)" % (Options["Sort"]));
622 Katie = katie.Katie(Cnf);
624 if not Options["No-Action"]:
625 Logger = Katie.Logger = logging.Logger(Cnf, "lisa");
627 projectB = Katie.projectB;
629 Sections = Section_Completer();
630 Priorities = Priority_Completer();
631 readline.parse_and_bind("tab: complete");
633 return changes_files;
635 ################################################################################
640 files = Katie.pkg.files;
644 for file in files.keys():
645 if files[file]["type"] == "byhand":
646 if os.path.exists(file):
647 print "W: %s still present; please process byhand components and try again." % (file);
653 if Options["No-Action"]:
656 if Options["Automatic"] and not Options["No-Action"]:
658 prompt = "[A]ccept, Manual reject, Skip, Quit ?";
660 prompt = "Manual reject, [S]kip, Quit ?";
662 while string.find(prompt, answer) == -1:
663 answer = raw_input(prompt)
664 m = katie.re_default_answer.match(prompt)
667 answer = string.upper(answer[:1])
674 Katie.do_reject(1, Options["Manual-Reject"]);
675 os.unlink(Katie.pkg.changes_file[:-8]+".katie");
682 ################################################################################
686 if not Options["No-Action"]:
687 (summary, short_summary) = Katie.build_summaries();
688 Katie.accept(summary, short_summary);
689 os.unlink(Katie.pkg.changes_file[:-8]+".katie");
691 def check_status(files):
693 for file in files.keys():
694 if files[file]["type"] == "byhand":
696 elif files[file].has_key("new"):
698 return (new, byhand);
700 def do_pkg(changes_file):
701 Katie.pkg.changes_file = changes_file;
704 Katie.update_subst();
705 files = Katie.pkg.files;
707 (new, byhand) = check_status(files);
713 (new, byhand) = check_status(files);
715 if not new and not byhand:
718 ################################################################################
721 accept_count = Katie.accept_count;
722 accept_bytes = Katie.accept_bytes;
728 sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))));
729 Logger.log(["total",accept_count,accept_bytes]);
731 if not Options["No-Action"]:
734 ################################################################################
737 changes_files = init();
739 # Sort the changes files
740 if Options["Sort"] == "time":
741 changes_files.sort(changes_compare_by_time);
743 changes_files.sort(utils.changes_compare);
745 # Kill me now? **FIXME**
746 Cnf["Dinstall::Options::No-Mail"] = "";
747 bcc = "X-Lisa: %s" % (lisa_version);
748 if Cnf.has_key("Dinstall::Bcc"):
749 Katie.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
751 Katie.Subst["__BCC__"] = bcc;
753 for changes_file in changes_files:
754 print "\n" + changes_file;
755 do_pkg (changes_file);
759 ################################################################################
761 if __name__ == '__main__':