3 # Handles NEW and BYHAND packages
4 # Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org>
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.
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.
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
20 ################################################################################
22 # 23:12|<aj> I will not hush!
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!!!
37 ################################################################################
39 import copy, errno, os, readline, stat, sys, time
40 import apt_pkg, apt_inst
41 import dak.lib.database, examine_package, dak.lib.queue, dak.lib.logging, dak.lib.utils
55 ################################################################################
56 ################################################################################
57 ################################################################################
59 def reject (str, prefix="Rejected: "):
62 reject_message += prefix + str + "\n"
66 files = Upload.pkg.files
69 for file in files.keys():
70 # The .orig.tar.gz can disappear out from under us is it's a
71 # duplicate of one in the archive.
72 if not files.has_key(file):
74 # Check that the source still exists
75 if files[file]["type"] == "deb":
76 source_version = files[file]["source version"]
77 source_package = files[file]["source package"]
78 if not Upload.pkg.changes["architecture"].has_key("source") \
79 and not Upload.source_exists(source_package, source_version, Upload.pkg.changes["distribution"].keys()):
80 source_epochless_version = dak.lib.utils.re_no_epoch.sub('', source_version)
81 dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
82 if not os.path.exists(Cnf["Dir::Queue::Accepted"] + '/' + dsc_filename):
83 reject("no source found for %s %s (%s)." % (source_package, source_version, file))
85 # Version and file overwrite checks
86 if files[file]["type"] == "deb":
87 reject(Upload.check_binary_against_db(file))
88 elif files[file]["type"] == "dsc":
89 reject(Upload.check_source_against_db(file))
90 (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(file)
95 if Options["No-Action"] or Options["Automatic"]:
98 print "REJECT\n" + reject_message,
99 prompt = "[R]eject, Skip, Quit ?"
101 while prompt.find(answer) == -1:
102 answer = dak.lib.utils.our_raw_input(prompt)
103 m = dak.lib.queue.re_default_answer.match(prompt)
106 answer = answer[:1].upper()
109 Upload.do_reject(0, reject_message)
110 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
119 ################################################################################
121 def determine_new (changes, files):
124 # Build up a list of potentially new things
125 for file in files.keys():
127 # Skip byhand elements
128 if f["type"] == "byhand":
131 priority = f["priority"]
132 section = f["section"]
134 if section == "non-US/main":
137 component = f["component"]
141 if not new.has_key(pkg):
143 new[pkg]["priority"] = priority
144 new[pkg]["section"] = section
145 new[pkg]["type"] = type
146 new[pkg]["component"] = component
147 new[pkg]["files"] = []
149 old_type = new[pkg]["type"]
151 # source gets trumped by deb or udeb
152 if old_type == "dsc":
153 new[pkg]["priority"] = priority
154 new[pkg]["section"] = section
155 new[pkg]["type"] = type
156 new[pkg]["component"] = component
157 new[pkg]["files"].append(file)
158 if f.has_key("othercomponents"):
159 new[pkg]["othercomponents"] = f["othercomponents"]
161 for suite in changes["suite"].keys():
162 suite_id = dak.lib.database.get_suite_id(suite)
163 for pkg in new.keys():
164 component_id = dak.lib.database.get_component_id(new[pkg]["component"])
165 type_id = dak.lib.database.get_override_type_id(new[pkg]["type"])
166 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))
169 for file in new[pkg]["files"]:
170 if files[file].has_key("new"):
171 del files[file]["new"]
174 if changes["suite"].has_key("stable"):
175 print "WARNING: overrides will be added for stable!"
176 if changes["suite"].has_key("oldstable"):
177 print "WARNING: overrides will be added for OLDstable!"
178 for pkg in new.keys():
179 if new[pkg].has_key("othercomponents"):
180 print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"])
184 ################################################################################
186 def indiv_sg_compare (a, b):
187 """Sort by source name, source, version, 'have source', and
188 finally by filename."""
189 # Sort by source version
190 q = apt_pkg.VersionCompare(a["version"], b["version"])
194 # Sort by 'have source'
195 a_has_source = a["architecture"].get("source")
196 b_has_source = b["architecture"].get("source")
197 if a_has_source and not b_has_source:
199 elif b_has_source and not a_has_source:
202 return cmp(a["filename"], b["filename"])
204 ############################################################
206 def sg_compare (a, b):
209 """Sort by have note, time of oldest upload."""
211 a_note_state = a["note_state"]
212 b_note_state = b["note_state"]
213 if a_note_state < b_note_state:
215 elif a_note_state > b_note_state:
218 # Sort by time of oldest upload
219 return cmp(a["oldest"], b["oldest"])
221 def sort_changes(changes_files):
222 """Sort into source groups, then sort each source group by version,
223 have source, filename. Finally, sort the source groups by have
224 note, time of oldest upload of each source upload."""
225 if len(changes_files) == 1:
230 # Read in all the .changes files
231 for filename in changes_files:
233 Upload.pkg.changes_file = filename
236 cache[filename] = copy.copy(Upload.pkg.changes)
237 cache[filename]["filename"] = filename
239 sorted_list.append(filename)
241 # Divide the .changes into per-source groups
243 for filename in cache.keys():
244 source = cache[filename]["source"]
245 if not per_source.has_key(source):
246 per_source[source] = {}
247 per_source[source]["list"] = []
248 per_source[source]["list"].append(cache[filename])
249 # Determine oldest time and have note status for each source group
250 for source in per_source.keys():
251 source_list = per_source[source]["list"]
252 first = source_list[0]
253 oldest = os.stat(first["filename"])[stat.ST_MTIME]
255 for d in per_source[source]["list"]:
256 mtime = os.stat(d["filename"])[stat.ST_MTIME]
259 have_note += (d.has_key("process-new note"))
260 per_source[source]["oldest"] = oldest
262 per_source[source]["note_state"] = 0; # none
263 elif have_note < len(source_list):
264 per_source[source]["note_state"] = 1; # some
266 per_source[source]["note_state"] = 2; # all
267 per_source[source]["list"].sort(indiv_sg_compare)
268 per_source_items = per_source.items()
269 per_source_items.sort(sg_compare)
270 for i in per_source_items:
271 for j in i[1]["list"]:
272 sorted_list.append(j["filename"])
275 ################################################################################
277 class Section_Completer:
280 q = projectB.query("SELECT section FROM section")
281 for i in q.getresult():
282 self.sections.append(i[0])
284 def complete(self, text, state):
288 for word in self.sections:
290 self.matches.append(word)
292 return self.matches[state]
296 ############################################################
298 class Priority_Completer:
301 q = projectB.query("SELECT priority FROM priority")
302 for i in q.getresult():
303 self.priorities.append(i[0])
305 def complete(self, text, state):
309 for word in self.priorities:
311 self.matches.append(word)
313 return self.matches[state]
317 ################################################################################
319 def check_valid (new):
320 for pkg in new.keys():
321 section = new[pkg]["section"]
322 priority = new[pkg]["priority"]
323 type = new[pkg]["type"]
324 new[pkg]["section id"] = dak.lib.database.get_section_id(section)
325 new[pkg]["priority id"] = dak.lib.database.get_priority_id(new[pkg]["priority"])
327 if (section == "debian-installer" and type != "udeb") or \
328 (section != "debian-installer" and type == "udeb"):
329 new[pkg]["section id"] = -1
330 if (priority == "source" and type != "dsc") or \
331 (priority != "source" and type == "dsc"):
332 new[pkg]["priority id"] = -1
334 ################################################################################
336 def print_new (new, indexed, file=sys.stdout):
340 for pkg in new.keys():
342 section = new[pkg]["section"]
343 priority = new[pkg]["priority"]
344 if new[pkg]["section id"] == -1:
347 if new[pkg]["priority id"] == -1:
351 line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
353 line = "%-20s %-20s %-20s" % (pkg, priority, section)
354 line = line.strip()+'\n'
356 note = Upload.pkg.changes.get("process-new note")
363 ################################################################################
367 if f.has_key("dbtype"):
369 elif f["type"] == "orig.tar.gz" or f["type"] == "tar.gz" or f["type"] == "diff.gz" or f["type"] == "dsc":
372 dak.lib.utils.fubar("invalid type (%s) for new. Dazed, confused and sure as heck not continuing." % (type))
374 # Validate the override type
375 type_id = dak.lib.database.get_override_type_id(type)
377 dak.lib.utils.fubar("invalid type (%s) for new. Say wha?" % (type))
381 ################################################################################
383 def index_range (index):
387 return "1-%s" % (index)
389 ################################################################################
390 ################################################################################
393 # Write the current data to a temporary file
394 temp_filename = dak.lib.utils.temp_filename()
395 temp_file = dak.lib.utils.open_file(temp_filename, 'w')
396 print_new (new, 0, temp_file)
398 # Spawn an editor on that file
399 editor = os.environ.get("EDITOR","vi")
400 result = os.system("%s %s" % (editor, temp_filename))
402 dak.lib.utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
403 # Read the edited data back in
404 temp_file = dak.lib.utils.open_file(temp_filename)
405 lines = temp_file.readlines()
407 os.unlink(temp_filename)
414 # Pad the list if necessary
415 s[len(s):3] = [None] * (3-len(s))
416 (pkg, priority, section) = s[:3]
417 if not new.has_key(pkg):
418 dak.lib.utils.warn("Ignoring unknown package '%s'" % (pkg))
420 # Strip off any invalid markers, print_new will readd them.
421 if section.endswith("[!]"):
422 section = section[:-3]
423 if priority.endswith("[!]"):
424 priority = priority[:-3]
425 for file in new[pkg]["files"]:
426 Upload.pkg.files[file]["section"] = section
427 Upload.pkg.files[file]["priority"] = priority
428 new[pkg]["section"] = section
429 new[pkg]["priority"] = priority
431 ################################################################################
433 def edit_index (new, index):
434 priority = new[index]["priority"]
435 section = new[index]["section"]
436 type = new[index]["type"]
439 print "\t".join([index, priority, section])
443 prompt = "[B]oth, Priority, Section, Done ? "
445 prompt = "[S]ection, Done ? "
446 edit_priority = edit_section = 0
448 while prompt.find(answer) == -1:
449 answer = dak.lib.utils.our_raw_input(prompt)
450 m = dak.lib.queue.re_default_answer.match(prompt)
453 answer = answer[:1].upper()
460 edit_priority = edit_section = 1
466 readline.set_completer(Priorities.complete)
468 while not got_priority:
469 new_priority = dak.lib.utils.our_raw_input("New priority: ").strip()
470 if new_priority not in Priorities.priorities:
471 print "E: '%s' is not a valid priority, try again." % (new_priority)
474 priority = new_priority
478 readline.set_completer(Sections.complete)
480 while not got_section:
481 new_section = dak.lib.utils.our_raw_input("New section: ").strip()
482 if new_section not in Sections.sections:
483 print "E: '%s' is not a valid section, try again." % (new_section)
486 section = new_section
488 # Reset the readline completer
489 readline.set_completer(None)
491 for file in new[index]["files"]:
492 Upload.pkg.files[file]["section"] = section
493 Upload.pkg.files[file]["priority"] = priority
494 new[index]["priority"] = priority
495 new[index]["section"] = section
498 ################################################################################
500 def edit_overrides (new):
511 prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
514 while not got_answer:
515 answer = dak.lib.utils.our_raw_input(prompt)
516 if not dak.lib.utils.str_isnum(answer):
517 answer = answer[:1].upper()
518 if answer == "E" or answer == "D":
520 elif dak.lib.queue.re_isanum.match (answer):
522 if (answer < 1) or (answer > index):
523 print "%s is not a valid index (%s). Please retry." % (answer, index_range(index))
532 edit_index (new, new_index[answer])
536 ################################################################################
539 # Write the current data to a temporary file
540 temp_filename = dak.lib.utils.temp_filename()
541 temp_file = dak.lib.utils.open_file(temp_filename, 'w')
542 temp_file.write(note)
544 editor = os.environ.get("EDITOR","vi")
547 os.system("%s %s" % (editor, temp_filename))
548 temp_file = dak.lib.utils.open_file(temp_filename)
549 note = temp_file.read().rstrip()
552 print dak.lib.utils.prefix_multi_line_string(note," ")
553 prompt = "[D]one, Edit, Abandon, Quit ?"
555 while prompt.find(answer) == -1:
556 answer = dak.lib.utils.our_raw_input(prompt)
557 m = dak.lib.queue.re_default_answer.search(prompt)
560 answer = answer[:1].upper()
561 os.unlink(temp_filename)
566 Upload.pkg.changes["process-new note"] = note
567 Upload.dump_vars(Cnf["Dir::Queue::New"])
569 ################################################################################
573 less_fd = os.popen("less -R -", 'w', 0)
574 stdout_fd = sys.stdout
577 examine_package.display_changes(Upload.pkg.changes_file)
578 files = Upload.pkg.files
579 for file in files.keys():
580 if files[file].has_key("new"):
581 type = files[file]["type"]
583 examine_package.check_deb(file)
585 examine_package.check_dsc(file)
587 sys.stdout = stdout_fd
589 if errno.errorcode[e.errno] == 'EPIPE':
590 dak.lib.utils.warn("[examine_package] Caught EPIPE; skipping.")
594 except KeyboardInterrupt:
595 dak.lib.utils.warn("[examine_package] Caught C-c; skipping.")
598 ################################################################################
600 ## FIXME: horribly Debian specific
602 def do_bxa_notification():
603 files = Upload.pkg.files
605 for file in files.keys():
606 if files[file]["type"] == "deb":
607 control = apt_pkg.ParseSection(apt_inst.debExtractControl(dak.lib.utils.open_file(file)))
609 summary += "Package: %s\n" % (control.Find("Package"))
610 summary += "Description: %s\n" % (control.Find("Description"))
611 Upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
612 bxa_mail = dak.lib.utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-new.bxa_notification")
613 dak.lib.utils.send_mail(bxa_mail)
615 ################################################################################
617 def add_overrides (new):
618 changes = Upload.pkg.changes
619 files = Upload.pkg.files
621 projectB.query("BEGIN WORK")
622 for suite in changes["suite"].keys():
623 suite_id = dak.lib.database.get_suite_id(suite)
624 for pkg in new.keys():
625 component_id = dak.lib.database.get_component_id(new[pkg]["component"])
626 type_id = dak.lib.database.get_override_type_id(new[pkg]["type"])
627 priority_id = new[pkg]["priority id"]
628 section_id = new[pkg]["section id"]
629 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))
630 for file in new[pkg]["files"]:
631 if files[file].has_key("new"):
632 del files[file]["new"]
635 projectB.query("COMMIT WORK")
637 if Cnf.FindB("Dinstall::BXANotify"):
638 do_bxa_notification()
640 ################################################################################
642 def prod_maintainer ():
643 # Here we prepare an editor and get them ready to prod...
644 temp_filename = dak.lib.utils.temp_filename()
645 editor = os.environ.get("EDITOR","vi")
648 os.system("%s %s" % (editor, temp_filename))
649 file = dak.lib.utils.open_file(temp_filename)
650 prod_message = "".join(file.readlines())
652 print "Prod message:"
653 print dak.lib.utils.prefix_multi_line_string(prod_message," ",include_blank_lines=1)
654 prompt = "[P]rod, Edit, Abandon, Quit ?"
656 while prompt.find(answer) == -1:
657 answer = dak.lib.utils.our_raw_input(prompt)
658 m = dak.lib.queue.re_default_answer.search(prompt)
661 answer = answer[:1].upper()
662 os.unlink(temp_filename)
667 # Otherwise, do the proding...
668 user_email_address = dak.lib.utils.whoami() + " <%s>" % (
669 Cnf["Dinstall::MyAdminAddress"])
673 Subst["__FROM_ADDRESS__"] = user_email_address
674 Subst["__PROD_MESSAGE__"] = prod_message
675 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
677 prod_mail_message = dak.lib.utils.TemplateSubst(
678 Subst,Cnf["Dir::Templates"]+"/process-new.prod")
680 # Send the prod mail if appropriate
681 if not Cnf["Dinstall::Options::No-Mail"]:
682 dak.lib.utils.send_mail(prod_mail_message)
684 print "Sent proding message"
686 ################################################################################
690 files = Upload.pkg.files
691 changes = Upload.pkg.changes
693 # Make a copy of distribution we can happily trample on
694 changes["suite"] = copy.copy(changes["distribution"])
696 # Fix up the list of target suites
697 for suite in changes["suite"].keys():
698 override = Cnf.Find("Suite::%s::OverrideSuite" % (suite))
700 del changes["suite"][suite]
701 changes["suite"][override] = 1
703 for suite in changes["suite"].keys():
704 suite_id = dak.lib.database.get_suite_id(suite)
706 dak.lib.utils.fubar("%s has invalid suite '%s' (possibly overriden). say wha?" % (changes, suite))
708 # The main NEW processing loop
711 # Find out what's new
712 new = determine_new(changes, files)
718 if Options["No-Action"] or Options["Automatic"]:
721 (broken, note) = print_new(new, 0)
724 if not broken and not note:
725 prompt = "Add overrides, "
727 print "W: [!] marked entries must be fixed before package can be processed."
729 print "W: note must be removed before package can be processed."
730 prompt += "Remove note, "
732 prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
734 while prompt.find(answer) == -1:
735 answer = dak.lib.utils.our_raw_input(prompt)
736 m = dak.lib.queue.re_default_answer.search(prompt)
739 answer = answer[:1].upper()
742 done = add_overrides (new)
746 new = edit_overrides (new)
748 aborted = Upload.do_reject(1, Options["Manual-Reject"])
750 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
753 edit_note(changes.get("process-new note", ""))
757 confirm = dak.lib.utils.our_raw_input("Really clear note (y/N)? ").lower()
759 del changes["process-new note"]
765 ################################################################################
766 ################################################################################
767 ################################################################################
769 def usage (exit_code=0):
770 print """Usage: dak process-new [OPTION]... [CHANGES]...
771 -a, --automatic automatic run
772 -h, --help show this help and exit.
773 -m, --manual-reject=MSG manual reject with `msg'
774 -n, --no-action don't do anything
775 -V, --version display the version number and exit"""
778 ################################################################################
781 global Cnf, Options, Logger, Upload, projectB, Sections, Priorities
783 Cnf = dak.lib.utils.get_conf()
785 Arguments = [('a',"automatic","Process-New::Options::Automatic"),
786 ('h',"help","Process-New::Options::Help"),
787 ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
788 ('n',"no-action","Process-New::Options::No-Action")]
790 for i in ["automatic", "help", "manual-reject", "no-action", "version"]:
791 if not Cnf.has_key("Process-New::Options::%s" % (i)):
792 Cnf["Process-New::Options::%s" % (i)] = ""
794 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
795 Options = Cnf.SubTree("Process-New::Options")
800 Upload = dak.lib.queue.Upload(Cnf)
802 if not Options["No-Action"]:
803 Logger = Upload.Logger = dak.lib.logging.Logger(Cnf, "process-new")
805 projectB = Upload.projectB
807 Sections = Section_Completer()
808 Priorities = Priority_Completer()
809 readline.parse_and_bind("tab: complete")
813 ################################################################################
818 files = Upload.pkg.files
822 for file in files.keys():
823 if files[file]["type"] == "byhand":
824 if os.path.exists(file):
825 print "W: %s still present; please process byhand components and try again." % (file)
831 if Options["No-Action"]:
834 if Options["Automatic"] and not Options["No-Action"]:
836 prompt = "[A]ccept, Manual reject, Skip, Quit ?"
838 prompt = "Manual reject, [S]kip, Quit ?"
840 while prompt.find(answer) == -1:
841 answer = dak.lib.utils.our_raw_input(prompt)
842 m = dak.lib.queue.re_default_answer.search(prompt)
845 answer = answer[:1].upper()
852 Upload.do_reject(1, Options["Manual-Reject"])
853 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
860 ################################################################################
864 if not Options["No-Action"]:
868 lock_fd = os.open(Cnf["Process-New::AcceptedLockFile"], os.O_RDONLY | os.O_CREAT | os.O_EXCL)
871 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
874 dak.lib.utils.fubar("Couldn't obtain lock; assuming 'dak process-unchecked' is already running.")
876 print("Unable to get accepted lock (try %d of 10)" % retry)
880 (summary, short_summary) = Upload.build_summaries()
881 Upload.accept(summary, short_summary)
882 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
883 os.unlink(Cnf["Process-New::AcceptedLockFile"])
885 def check_status(files):
887 for file in files.keys():
888 if files[file]["type"] == "byhand":
890 elif files[file].has_key("new"):
894 def do_pkg(changes_file):
895 Upload.pkg.changes_file = changes_file
898 Upload.update_subst()
899 files = Upload.pkg.files
904 (new, byhand) = check_status(files)
910 (new, byhand) = check_status(files)
912 if not new and not byhand:
915 ################################################################################
918 accept_count = Upload.accept_count
919 accept_bytes = Upload.accept_bytes
925 sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, dak.lib.utils.size_type(int(accept_bytes))))
926 Logger.log(["total",accept_count,accept_bytes])
928 if not Options["No-Action"]:
931 ################################################################################
934 changes_files = init()
935 if len(changes_files) > 50:
936 sys.stderr.write("Sorting changes...\n")
937 changes_files = sort_changes(changes_files)
939 # Kill me now? **FIXME**
940 Cnf["Dinstall::Options::No-Mail"] = ""
941 bcc = "X-DAK: dak process-new\nX-Katie: this header is obsolete"
942 if Cnf.has_key("Dinstall::Bcc"):
943 Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
945 Upload.Subst["__BCC__"] = bcc
947 for changes_file in changes_files:
948 changes_file = dak.lib.utils.validate_changes_file_arg(changes_file, 0)
951 print "\n" + changes_file
952 do_pkg (changes_file)
956 ################################################################################
958 if __name__ == '__main__':