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 file in files.keys():
74 # The .orig.tar.gz can disappear out from under us is it's a
75 # duplicate of one in the archive.
76 if not files.has_key(file):
78 # Check that the source still exists
79 if files[file]["type"] == "deb":
80 source_version = files[file]["source version"]
81 source_package = files[file]["source package"]
82 if not Upload.pkg.changes["architecture"].has_key("source") \
83 and not Upload.source_exists(source_package, source_version, Upload.pkg.changes["distribution"].keys()):
84 source_epochless_version = daklib.utils.re_no_epoch.sub('', source_version)
85 dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
87 for q in ["Accepted", "Embargoed", "Unembargoed"]:
88 if os.path.exists(Cnf["Dir::Queue::%s" % (q)] + '/' + dsc_filename):
91 reject("no source found for %s %s (%s)." % (source_package, source_version, file))
93 # Version and file overwrite checks
94 if files[file]["type"] == "deb":
95 reject(Upload.check_binary_against_db(file))
96 elif files[file]["type"] == "dsc":
97 reject(Upload.check_source_against_db(file))
98 (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(file)
99 reject(reject_msg, "")
101 if reject_message.find("Rejected") != -1:
103 if Options["No-Action"] or Options["Automatic"]:
106 print "REJECT\n" + reject_message,
107 prompt = "[R]eject, Skip, Quit ?"
109 while prompt.find(answer) == -1:
110 answer = daklib.utils.our_raw_input(prompt)
111 m = daklib.queue.re_default_answer.match(prompt)
114 answer = answer[:1].upper()
117 Upload.do_reject(0, reject_message)
118 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
128 ################################################################################
130 def indiv_sg_compare (a, b):
131 """Sort by source name, source, version, 'have source', and
132 finally by filename."""
133 # Sort by source version
134 q = apt_pkg.VersionCompare(a["version"], b["version"])
138 # Sort by 'have source'
139 a_has_source = a["architecture"].get("source")
140 b_has_source = b["architecture"].get("source")
141 if a_has_source and not b_has_source:
143 elif b_has_source and not a_has_source:
146 return cmp(a["filename"], b["filename"])
148 ############################################################
150 def sg_compare (a, b):
153 """Sort by have note, time of oldest upload."""
155 a_note_state = a["note_state"]
156 b_note_state = b["note_state"]
157 if a_note_state < b_note_state:
159 elif a_note_state > b_note_state:
162 # Sort by time of oldest upload
163 return cmp(a["oldest"], b["oldest"])
165 def sort_changes(changes_files):
166 """Sort into source groups, then sort each source group by version,
167 have source, filename. Finally, sort the source groups by have
168 note, time of oldest upload of each source upload."""
169 if len(changes_files) == 1:
174 # Read in all the .changes files
175 for filename in changes_files:
177 Upload.pkg.changes_file = filename
180 cache[filename] = copy.copy(Upload.pkg.changes)
181 cache[filename]["filename"] = filename
183 sorted_list.append(filename)
185 # Divide the .changes into per-source groups
187 for filename in cache.keys():
188 source = cache[filename]["source"]
189 if not per_source.has_key(source):
190 per_source[source] = {}
191 per_source[source]["list"] = []
192 per_source[source]["list"].append(cache[filename])
193 # Determine oldest time and have note status for each source group
194 for source in per_source.keys():
195 source_list = per_source[source]["list"]
196 first = source_list[0]
197 oldest = os.stat(first["filename"])[stat.ST_MTIME]
199 for d in per_source[source]["list"]:
200 mtime = os.stat(d["filename"])[stat.ST_MTIME]
203 have_note += (d.has_key("process-new note"))
204 per_source[source]["oldest"] = oldest
206 per_source[source]["note_state"] = 0; # none
207 elif have_note < len(source_list):
208 per_source[source]["note_state"] = 1; # some
210 per_source[source]["note_state"] = 2; # all
211 per_source[source]["list"].sort(indiv_sg_compare)
212 per_source_items = per_source.items()
213 per_source_items.sort(sg_compare)
214 for i in per_source_items:
215 for j in i[1]["list"]:
216 sorted_list.append(j["filename"])
219 ################################################################################
221 class Section_Completer:
224 q = projectB.query("SELECT section FROM section")
225 for i in q.getresult():
226 self.sections.append(i[0])
228 def complete(self, text, state):
232 for word in self.sections:
234 self.matches.append(word)
236 return self.matches[state]
240 ############################################################
242 class Priority_Completer:
245 q = projectB.query("SELECT priority FROM priority")
246 for i in q.getresult():
247 self.priorities.append(i[0])
249 def complete(self, text, state):
253 for word in self.priorities:
255 self.matches.append(word)
257 return self.matches[state]
261 ################################################################################
263 def print_new (new, indexed, file=sys.stdout):
264 daklib.queue.check_valid(new)
267 for pkg in new.keys():
269 section = new[pkg]["section"]
270 priority = new[pkg]["priority"]
271 if new[pkg]["section id"] == -1:
274 if new[pkg]["priority id"] == -1:
278 line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
280 line = "%-20s %-20s %-20s" % (pkg, priority, section)
281 line = line.strip()+'\n'
283 note = Upload.pkg.changes.get("process-new note")
290 ################################################################################
292 def index_range (index):
296 return "1-%s" % (index)
298 ################################################################################
299 ################################################################################
302 # Write the current data to a temporary file
303 temp_filename = daklib.utils.temp_filename()
304 temp_file = daklib.utils.open_file(temp_filename, 'w')
305 print_new (new, 0, temp_file)
307 # Spawn an editor on that file
308 editor = os.environ.get("EDITOR","vi")
309 result = os.system("%s %s" % (editor, temp_filename))
311 daklib.utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
312 # Read the edited data back in
313 temp_file = daklib.utils.open_file(temp_filename)
314 lines = temp_file.readlines()
316 os.unlink(temp_filename)
323 # Pad the list if necessary
324 s[len(s):3] = [None] * (3-len(s))
325 (pkg, priority, section) = s[:3]
326 if not new.has_key(pkg):
327 daklib.utils.warn("Ignoring unknown package '%s'" % (pkg))
329 # Strip off any invalid markers, print_new will readd them.
330 if section.endswith("[!]"):
331 section = section[:-3]
332 if priority.endswith("[!]"):
333 priority = priority[:-3]
334 for file in new[pkg]["files"]:
335 Upload.pkg.files[file]["section"] = section
336 Upload.pkg.files[file]["priority"] = priority
337 new[pkg]["section"] = section
338 new[pkg]["priority"] = priority
340 ################################################################################
342 def edit_index (new, index):
343 priority = new[index]["priority"]
344 section = new[index]["section"]
345 type = new[index]["type"]
348 print "\t".join([index, priority, section])
352 prompt = "[B]oth, Priority, Section, Done ? "
354 prompt = "[S]ection, Done ? "
355 edit_priority = edit_section = 0
357 while prompt.find(answer) == -1:
358 answer = daklib.utils.our_raw_input(prompt)
359 m = daklib.queue.re_default_answer.match(prompt)
362 answer = answer[:1].upper()
369 edit_priority = edit_section = 1
375 readline.set_completer(Priorities.complete)
377 while not got_priority:
378 new_priority = daklib.utils.our_raw_input("New priority: ").strip()
379 if new_priority not in Priorities.priorities:
380 print "E: '%s' is not a valid priority, try again." % (new_priority)
383 priority = new_priority
387 readline.set_completer(Sections.complete)
389 while not got_section:
390 new_section = daklib.utils.our_raw_input("New section: ").strip()
391 if new_section not in Sections.sections:
392 print "E: '%s' is not a valid section, try again." % (new_section)
395 section = new_section
397 # Reset the readline completer
398 readline.set_completer(None)
400 for file in new[index]["files"]:
401 Upload.pkg.files[file]["section"] = section
402 Upload.pkg.files[file]["priority"] = priority
403 new[index]["priority"] = priority
404 new[index]["section"] = section
407 ################################################################################
409 def edit_overrides (new):
420 prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
423 while not got_answer:
424 answer = daklib.utils.our_raw_input(prompt)
425 if not answer.isdigit():
426 answer = answer[:1].upper()
427 if answer == "E" or answer == "D":
429 elif daklib.queue.re_isanum.match (answer):
431 if (answer < 1) or (answer > index):
432 print "%s is not a valid index (%s). Please retry." % (answer, index_range(index))
441 edit_index (new, new_index[answer])
445 ################################################################################
448 # Write the current data to a temporary file
449 temp_filename = daklib.utils.temp_filename()
450 temp_file = daklib.utils.open_file(temp_filename, 'w')
451 temp_file.write(note)
453 editor = os.environ.get("EDITOR","vi")
456 os.system("%s %s" % (editor, temp_filename))
457 temp_file = daklib.utils.open_file(temp_filename)
458 note = temp_file.read().rstrip()
461 print daklib.utils.prefix_multi_line_string(note," ")
462 prompt = "[D]one, Edit, Abandon, Quit ?"
464 while prompt.find(answer) == -1:
465 answer = daklib.utils.our_raw_input(prompt)
466 m = daklib.queue.re_default_answer.search(prompt)
469 answer = answer[:1].upper()
470 os.unlink(temp_filename)
476 Upload.pkg.changes["process-new note"] = note
477 Upload.dump_vars(Cnf["Dir::Queue::New"])
479 ################################################################################
483 less_fd = os.popen("less -R -", 'w', 0)
484 stdout_fd = sys.stdout
487 examine_package.display_changes(Upload.pkg.changes_file)
488 files = Upload.pkg.files
489 for file in files.keys():
490 if files[file].has_key("new"):
491 type = files[file]["type"]
493 examine_package.check_deb(file)
495 examine_package.check_dsc(file)
497 sys.stdout = stdout_fd
499 if errno.errorcode[e.errno] == 'EPIPE':
500 daklib.utils.warn("[examine_package] Caught EPIPE; skipping.")
504 except KeyboardInterrupt:
505 daklib.utils.warn("[examine_package] Caught C-c; skipping.")
508 ################################################################################
510 ## FIXME: horribly Debian specific
512 def do_bxa_notification():
513 files = Upload.pkg.files
515 for file in files.keys():
516 if files[file]["type"] == "deb":
517 control = apt_pkg.ParseSection(apt_inst.debExtractControl(daklib.utils.open_file(file)))
519 summary += "Package: %s\n" % (control.Find("Package"))
520 summary += "Description: %s\n" % (control.Find("Description"))
521 Upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
522 bxa_mail = daklib.utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-new.bxa_notification")
523 daklib.utils.send_mail(bxa_mail)
525 ################################################################################
527 def add_overrides (new):
528 changes = Upload.pkg.changes
529 files = Upload.pkg.files
531 projectB.query("BEGIN WORK")
532 for suite in changes["suite"].keys():
533 suite_id = daklib.database.get_suite_id(suite)
534 for pkg in new.keys():
535 component_id = daklib.database.get_component_id(new[pkg]["component"])
536 type_id = daklib.database.get_override_type_id(new[pkg]["type"])
537 priority_id = new[pkg]["priority id"]
538 section_id = new[pkg]["section id"]
539 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))
540 for file in new[pkg]["files"]:
541 if files[file].has_key("new"):
542 del files[file]["new"]
545 projectB.query("COMMIT WORK")
547 if Cnf.FindB("Dinstall::BXANotify"):
548 do_bxa_notification()
550 ################################################################################
552 def prod_maintainer ():
553 # Here we prepare an editor and get them ready to prod...
554 temp_filename = daklib.utils.temp_filename()
555 editor = os.environ.get("EDITOR","vi")
558 os.system("%s %s" % (editor, temp_filename))
559 file = daklib.utils.open_file(temp_filename)
560 prod_message = "".join(file.readlines())
562 print "Prod message:"
563 print daklib.utils.prefix_multi_line_string(prod_message," ",include_blank_lines=1)
564 prompt = "[P]rod, Edit, Abandon, Quit ?"
566 while prompt.find(answer) == -1:
567 answer = daklib.utils.our_raw_input(prompt)
568 m = daklib.queue.re_default_answer.search(prompt)
571 answer = answer[:1].upper()
572 os.unlink(temp_filename)
578 # Otherwise, do the proding...
579 user_email_address = daklib.utils.whoami() + " <%s>" % (
580 Cnf["Dinstall::MyAdminAddress"])
584 Subst["__FROM_ADDRESS__"] = user_email_address
585 Subst["__PROD_MESSAGE__"] = prod_message
586 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
588 prod_mail_message = daklib.utils.TemplateSubst(
589 Subst,Cnf["Dir::Templates"]+"/process-new.prod")
591 # Send the prod mail if appropriate
592 if not Cnf["Dinstall::Options::No-Mail"]:
593 daklib.utils.send_mail(prod_mail_message)
595 print "Sent proding message"
597 ################################################################################
601 files = Upload.pkg.files
602 changes = Upload.pkg.changes
604 # Make a copy of distribution we can happily trample on
605 changes["suite"] = copy.copy(changes["distribution"])
607 # Fix up the list of target suites
608 for suite in changes["suite"].keys():
609 override = Cnf.Find("Suite::%s::OverrideSuite" % (suite))
611 (olderr, newerr) = (daklib.database.get_suite_id(suite) == -1,
612 daklib.database.get_suite_id(override) == -1)
614 (oinv, newinv) = ("", "")
615 if olderr: oinv = "invalid "
616 if newerr: ninv = "invalid "
617 print "warning: overriding %ssuite %s to %ssuite %s" % (
618 oinv, suite, ninv, override)
619 del changes["suite"][suite]
620 changes["suite"][override] = 1
622 for suite in changes["suite"].keys():
623 suite_id = daklib.database.get_suite_id(suite)
625 daklib.utils.fubar("%s has invalid suite '%s' (possibly overriden). say wha?" % (changes, suite))
627 # The main NEW processing loop
630 # Find out what's new
631 new = daklib.queue.determine_new(changes, files, projectB)
637 if Options["No-Action"] or Options["Automatic"]:
640 (broken, note) = print_new(new, 0)
643 if not broken and not note:
644 prompt = "Add overrides, "
646 print "W: [!] marked entries must be fixed before package can be processed."
648 print "W: note must be removed before package can be processed."
649 prompt += "Remove note, "
651 prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
653 while prompt.find(answer) == -1:
654 answer = daklib.utils.our_raw_input(prompt)
655 m = daklib.queue.re_default_answer.search(prompt)
658 answer = answer[:1].upper()
661 done = add_overrides (new)
665 new = edit_overrides (new)
667 aborted = Upload.do_reject(1, Options["Manual-Reject"])
669 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
672 edit_note(changes.get("process-new note", ""))
676 confirm = daklib.utils.our_raw_input("Really clear note (y/N)? ").lower()
678 del changes["process-new note"]
685 ################################################################################
686 ################################################################################
687 ################################################################################
689 def usage (exit_code=0):
690 print """Usage: dak process-new [OPTION]... [CHANGES]...
691 -a, --automatic automatic run
692 -h, --help show this help and exit.
693 -m, --manual-reject=MSG manual reject with `msg'
694 -n, --no-action don't do anything
695 -V, --version display the version number and exit"""
698 ################################################################################
701 global Cnf, Options, Logger, Upload, projectB, Sections, Priorities
703 Cnf = daklib.utils.get_conf()
705 Arguments = [('a',"automatic","Process-New::Options::Automatic"),
706 ('h',"help","Process-New::Options::Help"),
707 ('C',"comments-dir","Process-New::Options::Comments-Dir", "HasArg"),
708 ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
709 ('n',"no-action","Process-New::Options::No-Action")]
711 for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir"]:
712 if not Cnf.has_key("Process-New::Options::%s" % (i)):
713 Cnf["Process-New::Options::%s" % (i)] = ""
715 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
716 Options = Cnf.SubTree("Process-New::Options")
721 Upload = daklib.queue.Upload(Cnf)
723 if not Options["No-Action"]:
724 Logger = Upload.Logger = daklib.logging.Logger(Cnf, "process-new")
726 projectB = Upload.projectB
728 Sections = Section_Completer()
729 Priorities = Priority_Completer()
730 readline.parse_and_bind("tab: complete")
734 ################################################################################
739 files = Upload.pkg.files
743 for file in files.keys():
744 if files[file]["type"] == "byhand":
745 if os.path.exists(file):
746 print "W: %s still present; please process byhand components and try again." % (file)
752 if Options["No-Action"]:
755 if Options["Automatic"] and not Options["No-Action"]:
757 prompt = "[A]ccept, Manual reject, Skip, Quit ?"
759 prompt = "Manual reject, [S]kip, Quit ?"
761 while prompt.find(answer) == -1:
762 answer = daklib.utils.our_raw_input(prompt)
763 m = daklib.queue.re_default_answer.search(prompt)
766 answer = answer[:1].upper()
773 Upload.do_reject(1, Options["Manual-Reject"])
774 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
782 ################################################################################
784 def get_accept_lock():
788 lock_fd = os.open(Cnf["Process-New::AcceptedLockFile"], os.O_RDONLY | os.O_CREAT | os.O_EXCL)
791 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
794 daklib.utils.fubar("Couldn't obtain lock; assuming 'dak process-unchecked' is already running.")
796 print("Unable to get accepted lock (try %d of 10)" % retry)
801 def move_to_dir (dest, perms=0660, changesperms=0664):
802 daklib.utils.move (Upload.pkg.changes_file, dest, perms=changesperms)
803 file_keys = Upload.pkg.files.keys()
804 for file in file_keys:
805 daklib.utils.move (file, dest, perms=perms)
809 if not Options["No-Action"]:
811 (summary, short_summary) = Upload.build_summaries()
812 if Cnf.FindB("Dinstall::SecurityQueueHandling"):
813 Upload.dump_vars(Cnf["Dir::Queue::Embargoed"])
814 move_to_dir(Cnf["Dir::Queue::Embargoed"])
815 Upload.queue_build("embargoed", Cnf["Dir::Queue::Embargoed"])
816 # Check for override disparities
817 Upload.Subst["__SUMMARY__"] = summary
819 Upload.accept(summary, short_summary)
820 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
821 os.unlink(Cnf["Process-New::AcceptedLockFile"])
823 def check_status(files):
825 for file in files.keys():
826 if files[file]["type"] == "byhand":
828 elif files[file].has_key("new"):
832 def do_pkg(changes_file):
833 Upload.pkg.changes_file = changes_file
836 Upload.update_subst()
837 files = Upload.pkg.files
842 (new, byhand) = check_status(files)
848 (new, byhand) = check_status(files)
850 if not new and not byhand:
853 ################################################################################
856 accept_count = Upload.accept_count
857 accept_bytes = Upload.accept_bytes
863 sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, daklib.utils.size_type(int(accept_bytes))))
864 Logger.log(["total",accept_count,accept_bytes])
866 if not Options["No-Action"]:
869 ################################################################################
871 def do_comments(dir, opref, npref, line, fn):
872 for comm in [ x for x in os.listdir(dir) if x.startswith(opref) ]:
873 lines = open("%s/%s" % (dir, comm)).readlines()
874 if len(lines) == 0 or lines[0] != line + "\n": continue
875 changes_files = [ x for x in os.listdir(".") if x.startswith(comm[7:]+"_")
876 and x.endswith(".changes") ]
877 changes_files = sort_changes(changes_files)
878 for f in changes_files:
879 f = daklib.utils.validate_changes_file_arg(f, 0)
882 fn(f, "".join(lines[1:]))
884 if opref != npref and not Options["No-Action"]:
885 newcomm = npref + comm[len(opref):]
886 os.rename("%s/%s" % (dir, comm), "%s/%s" % (dir, newcomm))
888 ################################################################################
890 def comment_accept(changes_file, comments):
891 Upload.pkg.changes_file = changes_file
894 Upload.update_subst()
895 files = Upload.pkg.files
898 return # dak wants to REJECT, crap
900 (new, byhand) = check_status(files)
901 if not new and not byhand:
904 ################################################################################
906 def comment_reject(changes_file, comments):
907 Upload.pkg.changes_file = changes_file
910 Upload.update_subst()
911 files = Upload.pkg.files
914 pass # dak has its own reasons to reject as well, which is fine
917 print "REJECT\n" + reject_message,
918 if not Options["No-Action"]:
919 Upload.do_reject(0, reject_message)
920 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
922 ################################################################################
925 changes_files = init()
926 if len(changes_files) > 50:
927 sys.stderr.write("Sorting changes...\n")
928 changes_files = sort_changes(changes_files)
930 # Kill me now? **FIXME**
931 Cnf["Dinstall::Options::No-Mail"] = ""
932 bcc = "X-DAK: dak process-new\nX-Katie: lisa $Revision: 1.31 $"
933 if Cnf.has_key("Dinstall::Bcc"):
934 Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
936 Upload.Subst["__BCC__"] = bcc
938 commentsdir = Cnf.get("Process-New::Options::Comments-Dir","")
940 if changes_files != []:
941 sys.stderr.write("Can't specify any changes files if working with comments-dir")
943 do_comments(commentsdir, "ACCEPT.", "ACCEPTED.", "OK", comment_accept)
944 do_comments(commentsdir, "REJECT.", "REJECTED.", "NOTOK", comment_reject)
946 for changes_file in changes_files:
947 changes_file = daklib.utils.validate_changes_file_arg(changes_file, 0)
950 print "\n" + changes_file
951 do_pkg (changes_file)
955 ################################################################################
957 if __name__ == '__main__':