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 examine_package
42 import daklib.database
59 ################################################################################
60 ################################################################################
61 ################################################################################
63 def reject (str, prefix="Rejected: "):
66 reject_message += prefix + str + "\n"
70 files = Upload.pkg.files
73 for f 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(f):
78 # Check that the source still exists
79 if files[f]["type"] == "deb":
80 source_version = files[f]["source version"]
81 source_package = files[f]["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)
87 for q in ["Accepted", "Embargoed", "Unembargoed"]:
88 if Cnf.has_key("Dir::Queue::%s" % (q)):
89 if os.path.exists(Cnf["Dir::Queue::%s" % (q)] + '/' + dsc_filename):
92 reject("no source found for %s %s (%s)." % (source_package, source_version, f))
94 # Version and file overwrite checks
95 if files[f]["type"] == "deb":
96 reject(Upload.check_binary_against_db(f))
97 elif files[f]["type"] == "dsc":
98 reject(Upload.check_source_against_db(f))
99 (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(f)
100 reject(reject_msg, "")
102 if reject_message.find("Rejected") != -1:
104 if Options["No-Action"] or Options["Automatic"]:
107 print "REJECT\n" + reject_message,
108 prompt = "[R]eject, Skip, Quit ?"
110 while prompt.find(answer) == -1:
111 answer = daklib.utils.our_raw_input(prompt)
112 m = daklib.queue.re_default_answer.match(prompt)
115 answer = answer[:1].upper()
118 Upload.do_reject(0, reject_message)
119 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
129 ################################################################################
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"])
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:
144 elif b_has_source and not a_has_source:
147 return cmp(a["filename"], b["filename"])
149 ############################################################
151 def sg_compare (a, b):
154 """Sort by have note, time of oldest upload."""
156 a_note_state = a["note_state"]
157 b_note_state = b["note_state"]
158 if a_note_state < b_note_state:
160 elif a_note_state > b_note_state:
163 # Sort by time of oldest upload
164 return cmp(a["oldest"], b["oldest"])
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:
175 # Read in all the .changes files
176 for filename in changes_files:
178 Upload.pkg.changes_file = filename
181 cache[filename] = copy.copy(Upload.pkg.changes)
182 cache[filename]["filename"] = filename
184 sorted_list.append(filename)
186 # Divide the .changes into per-source groups
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]
200 for d in per_source[source]["list"]:
201 mtime = os.stat(d["filename"])[stat.ST_MTIME]
204 have_note += (d.has_key("process-new note"))
205 per_source[source]["oldest"] = oldest
207 per_source[source]["note_state"] = 0; # none
208 elif have_note < len(source_list):
209 per_source[source]["note_state"] = 1; # some
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"])
220 ################################################################################
222 class Section_Completer:
225 q = projectB.query("SELECT section FROM section")
226 for i in q.getresult():
227 self.sections.append(i[0])
229 def complete(self, text, state):
233 for word in self.sections:
235 self.matches.append(word)
237 return self.matches[state]
241 ############################################################
243 class Priority_Completer:
246 q = projectB.query("SELECT priority FROM priority")
247 for i in q.getresult():
248 self.priorities.append(i[0])
250 def complete(self, text, state):
254 for word in self.priorities:
256 self.matches.append(word)
258 return self.matches[state]
262 ################################################################################
264 def print_new (new, indexed, file=sys.stdout):
265 daklib.queue.check_valid(new)
268 for pkg in new.keys():
270 section = new[pkg]["section"]
271 priority = new[pkg]["priority"]
272 if new[pkg]["section id"] == -1:
275 if new[pkg]["priority id"] == -1:
279 line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
281 line = "%-20s %-20s %-20s" % (pkg, priority, section)
282 line = line.strip()+'\n'
284 note = Upload.pkg.changes.get("process-new note")
291 ################################################################################
293 def index_range (index):
297 return "1-%s" % (index)
299 ################################################################################
300 ################################################################################
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)
308 # Spawn an editor on that file
309 editor = os.environ.get("EDITOR","vi")
310 result = os.system("%s %s" % (editor, temp_filename))
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()
317 os.unlink(temp_filename)
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))
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 f in new[pkg]["files"]:
336 Upload.pkg.files[f]["section"] = section
337 Upload.pkg.files[f]["priority"] = priority
338 new[pkg]["section"] = section
339 new[pkg]["priority"] = priority
341 ################################################################################
343 def edit_index (new, index):
344 priority = new[index]["priority"]
345 section = new[index]["section"]
346 ftype = new[index]["type"]
349 print "\t".join([index, priority, section])
353 prompt = "[B]oth, Priority, Section, Done ? "
355 prompt = "[S]ection, Done ? "
356 edit_priority = edit_section = 0
358 while prompt.find(answer) == -1:
359 answer = daklib.utils.our_raw_input(prompt)
360 m = daklib.queue.re_default_answer.match(prompt)
363 answer = answer[:1].upper()
370 edit_priority = edit_section = 1
376 readline.set_completer(Priorities.complete)
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)
384 priority = new_priority
388 readline.set_completer(Sections.complete)
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)
396 section = new_section
398 # Reset the readline completer
399 readline.set_completer(None)
401 for f in new[index]["files"]:
402 Upload.pkg.files[f]["section"] = section
403 Upload.pkg.files[f]["priority"] = priority
404 new[index]["priority"] = priority
405 new[index]["section"] = section
408 ################################################################################
410 def edit_overrides (new):
421 prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
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":
430 elif daklib.queue.re_isanum.match (answer):
432 if (answer < 1) or (answer > index):
433 print "%s is not a valid index (%s). Please retry." % (answer, index_range(index))
442 edit_index (new, new_index[answer])
446 ################################################################################
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)
454 editor = os.environ.get("EDITOR","vi")
457 os.system("%s %s" % (editor, temp_filename))
458 temp_file = daklib.utils.open_file(temp_filename)
459 note = temp_file.read().rstrip()
462 print daklib.utils.prefix_multi_line_string(note," ")
463 prompt = "[D]one, Edit, Abandon, Quit ?"
465 while prompt.find(answer) == -1:
466 answer = daklib.utils.our_raw_input(prompt)
467 m = daklib.queue.re_default_answer.search(prompt)
470 answer = answer[:1].upper()
471 os.unlink(temp_filename)
477 Upload.pkg.changes["process-new note"] = note
478 Upload.dump_vars(Cnf["Dir::Queue::New"])
480 ################################################################################
484 less_fd = os.popen("less -R -", 'w', 0)
485 stdout_fd = sys.stdout
488 examine_package.display_changes(Upload.pkg.changes_file)
489 files = Upload.pkg.files
490 for f in files.keys():
491 if files[f].has_key("new"):
492 ftype = files[f]["type"]
494 examine_package.check_deb(f)
496 examine_package.check_dsc(f)
498 sys.stdout = stdout_fd
500 if e.errno == errno.EPIPE:
501 daklib.utils.warn("[examine_package] Caught EPIPE; skipping.")
505 except KeyboardInterrupt:
506 daklib.utils.warn("[examine_package] Caught C-c; skipping.")
509 ################################################################################
511 ## FIXME: horribly Debian specific
513 def do_bxa_notification():
514 files = Upload.pkg.files
516 for f in files.keys():
517 if files[f]["type"] == "deb":
518 control = apt_pkg.ParseSection(apt_inst.debExtractControl(daklib.utils.open_file(f)))
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)
526 ################################################################################
528 def add_overrides (new):
529 changes = Upload.pkg.changes
530 files = Upload.pkg.files
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 f in new[pkg]["files"]:
542 if files[f].has_key("new"):
546 projectB.query("COMMIT WORK")
548 if Cnf.FindB("Dinstall::BXANotify"):
549 do_bxa_notification()
551 ################################################################################
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")
559 os.system("%s %s" % (editor, temp_filename))
560 f = daklib.utils.open_file(temp_filename)
561 prod_message = "".join(f.readlines())
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 ?"
567 while prompt.find(answer) == -1:
568 answer = daklib.utils.our_raw_input(prompt)
569 m = daklib.queue.re_default_answer.search(prompt)
572 answer = answer[:1].upper()
573 os.unlink(temp_filename)
579 # Otherwise, do the proding...
580 user_email_address = daklib.utils.whoami() + " <%s>" % (
581 Cnf["Dinstall::MyAdminAddress"])
585 Subst["__FROM_ADDRESS__"] = user_email_address
586 Subst["__PROD_MESSAGE__"] = prod_message
587 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
589 prod_mail_message = daklib.utils.TemplateSubst(
590 Subst,Cnf["Dir::Templates"]+"/process-new.prod")
592 # Send the prod mail if appropriate
593 if not Cnf["Dinstall::Options::No-Mail"]:
594 daklib.utils.send_mail(prod_mail_message)
596 print "Sent proding message"
598 ################################################################################
602 files = Upload.pkg.files
603 changes = Upload.pkg.changes
605 # Make a copy of distribution we can happily trample on
606 changes["suite"] = copy.copy(changes["distribution"])
608 # Fix up the list of target suites
609 for suite in changes["suite"].keys():
610 override = Cnf.Find("Suite::%s::OverrideSuite" % (suite))
612 (olderr, newerr) = (daklib.database.get_suite_id(suite) == -1,
613 daklib.database.get_suite_id(override) == -1)
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
623 for suite in changes["suite"].keys():
624 suite_id = daklib.database.get_suite_id(suite)
626 daklib.utils.fubar("%s has invalid suite '%s' (possibly overriden). say wha?" % (changes, suite))
628 # The main NEW processing loop
631 # Find out what's new
632 new = daklib.queue.determine_new(changes, files, projectB)
638 if Options["No-Action"] or Options["Automatic"]:
641 (broken, note) = print_new(new, 0)
644 if not broken and not note:
645 prompt = "Add overrides, "
647 print "W: [!] marked entries must be fixed before package can be processed."
649 print "W: note must be removed before package can be processed."
650 prompt += "Remove note, "
652 prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
654 while prompt.find(answer) == -1:
655 answer = daklib.utils.our_raw_input(prompt)
656 m = daklib.queue.re_default_answer.search(prompt)
659 answer = answer[:1].upper()
662 done = add_overrides (new)
666 new = edit_overrides (new)
668 aborted = Upload.do_reject(1, Options["Manual-Reject"])
670 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
673 edit_note(changes.get("process-new note", ""))
677 confirm = daklib.utils.our_raw_input("Really clear note (y/N)? ").lower()
679 del changes["process-new note"]
686 ################################################################################
687 ################################################################################
688 ################################################################################
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 -C, --comments-dir=DIR use DIR as comments-dir, for [o-]p-u-new
695 -m, --manual-reject=MSG manual reject with `msg'
696 -n, --no-action don't do anything
697 -V, --version display the version number and exit"""
700 ################################################################################
703 global Cnf, Options, Logger, Upload, projectB, Sections, Priorities
705 Cnf = daklib.utils.get_conf()
707 Arguments = [('a',"automatic","Process-New::Options::Automatic"),
708 ('h',"help","Process-New::Options::Help"),
709 ('C',"comments-dir","Process-New::Options::Comments-Dir", "HasArg"),
710 ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
711 ('n',"no-action","Process-New::Options::No-Action")]
713 for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir"]:
714 if not Cnf.has_key("Process-New::Options::%s" % (i)):
715 Cnf["Process-New::Options::%s" % (i)] = ""
717 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
718 Options = Cnf.SubTree("Process-New::Options")
723 Upload = daklib.queue.Upload(Cnf)
725 if not Options["No-Action"]:
726 Logger = Upload.Logger = daklib.logging.Logger(Cnf, "process-new")
728 projectB = Upload.projectB
730 Sections = Section_Completer()
731 Priorities = Priority_Completer()
732 readline.parse_and_bind("tab: complete")
736 ################################################################################
741 files = Upload.pkg.files
745 for f in files.keys():
746 if files[f]["type"] == "byhand":
747 if os.path.exists(f):
748 print "W: %s still present; please process byhand components and try again." % (f)
754 if Options["No-Action"]:
757 if Options["Automatic"] and not Options["No-Action"]:
759 prompt = "[A]ccept, Manual reject, Skip, Quit ?"
761 prompt = "Manual reject, [S]kip, Quit ?"
763 while prompt.find(answer) == -1:
764 answer = daklib.utils.our_raw_input(prompt)
765 m = daklib.queue.re_default_answer.search(prompt)
768 answer = answer[:1].upper()
775 Upload.do_reject(1, Options["Manual-Reject"])
776 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
784 ################################################################################
786 def get_accept_lock():
790 os.open(Cnf["Process-New::AcceptedLockFile"], os.O_RDONLY | os.O_CREAT | os.O_EXCL)
793 if e.errno == errno.EACCES or e.errno == errno.EEXIST:
796 daklib.utils.fubar("Couldn't obtain lock; assuming 'dak process-unchecked' is already running.")
798 print("Unable to get accepted lock (try %d of 10)" % retry)
803 def move_to_dir (dest, perms=0660, changesperms=0664):
804 daklib.utils.move (Upload.pkg.changes_file, dest, perms=changesperms)
805 file_keys = Upload.pkg.files.keys()
807 daklib.utils.move (f, dest, perms=perms)
811 if not Options["No-Action"]:
813 (summary, short_summary) = Upload.build_summaries()
814 if Cnf.FindB("Dinstall::SecurityQueueHandling"):
815 Upload.dump_vars(Cnf["Dir::Queue::Embargoed"])
816 move_to_dir(Cnf["Dir::Queue::Embargoed"])
817 Upload.queue_build("embargoed", Cnf["Dir::Queue::Embargoed"])
818 # Check for override disparities
819 Upload.Subst["__SUMMARY__"] = summary
821 Upload.accept(summary, short_summary)
822 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
823 os.unlink(Cnf["Process-New::AcceptedLockFile"])
825 def check_status(files):
827 for f in files.keys():
828 if files[f]["type"] == "byhand":
830 elif files[f].has_key("new"):
834 def do_pkg(changes_file):
835 Upload.pkg.changes_file = changes_file
838 Upload.update_subst()
839 files = Upload.pkg.files
844 (new, byhand) = check_status(files)
850 (new, byhand) = check_status(files)
852 if not new and not byhand:
855 ################################################################################
858 accept_count = Upload.accept_count
859 accept_bytes = Upload.accept_bytes
865 sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, daklib.utils.size_type(int(accept_bytes))))
866 Logger.log(["total",accept_count,accept_bytes])
868 if not Options["No-Action"]:
871 ################################################################################
873 def do_comments(dir, opref, npref, line, fn):
874 for comm in [ x for x in os.listdir(dir) if x.startswith(opref) ]:
875 lines = open("%s/%s" % (dir, comm)).readlines()
876 if len(lines) == 0 or lines[0] != line + "\n": continue
877 changes_files = [ x for x in os.listdir(".") if x.startswith(comm[7:]+"_")
878 and x.endswith(".changes") ]
879 changes_files = sort_changes(changes_files)
880 for f in changes_files:
881 f = daklib.utils.validate_changes_file_arg(f, 0)
884 fn(f, "".join(lines[1:]))
886 if opref != npref and not Options["No-Action"]:
887 newcomm = npref + comm[len(opref):]
888 os.rename("%s/%s" % (dir, comm), "%s/%s" % (dir, newcomm))
890 ################################################################################
892 def comment_accept(changes_file, comments):
893 Upload.pkg.changes_file = changes_file
896 Upload.update_subst()
897 files = Upload.pkg.files
900 return # dak wants to REJECT, crap
902 (new, byhand) = check_status(files)
903 if not new and not byhand:
906 ################################################################################
908 def comment_reject(changes_file, comments):
909 Upload.pkg.changes_file = changes_file
912 Upload.update_subst()
915 pass # dak has its own reasons to reject as well, which is fine
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")
923 ################################################################################
926 changes_files = init()
927 if len(changes_files) > 50:
928 sys.stderr.write("Sorting changes...\n")
929 changes_files = sort_changes(changes_files)
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"])
937 Upload.Subst["__BCC__"] = bcc
939 commentsdir = Cnf.get("Process-New::Options::Comments-Dir","")
941 if changes_files != []:
942 sys.stderr.write("Can't specify any changes files if working with comments-dir")
944 do_comments(commentsdir, "ACCEPT.", "ACCEPTED.", "OK", comment_accept)
945 do_comments(commentsdir, "REJECT.", "REJECTED.", "NOTOK", comment_reject)
947 for changes_file in changes_files:
948 changes_file = daklib.utils.validate_changes_file_arg(changes_file, 0)
951 print "\n" + changes_file
952 do_pkg (changes_file)
956 ################################################################################
958 if __name__ == '__main__':