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")
127 ################################################################################
129 def indiv_sg_compare (a, b):
130 """Sort by source name, source, version, 'have source', and
131 finally by filename."""
132 # Sort by source version
133 q = apt_pkg.VersionCompare(a["version"], b["version"])
137 # Sort by 'have source'
138 a_has_source = a["architecture"].get("source")
139 b_has_source = b["architecture"].get("source")
140 if a_has_source and not b_has_source:
142 elif b_has_source and not a_has_source:
145 return cmp(a["filename"], b["filename"])
147 ############################################################
149 def sg_compare (a, b):
152 """Sort by have note, time of oldest upload."""
154 a_note_state = a["note_state"]
155 b_note_state = b["note_state"]
156 if a_note_state < b_note_state:
158 elif a_note_state > b_note_state:
161 # Sort by time of oldest upload
162 return cmp(a["oldest"], b["oldest"])
164 def sort_changes(changes_files):
165 """Sort into source groups, then sort each source group by version,
166 have source, filename. Finally, sort the source groups by have
167 note, time of oldest upload of each source upload."""
168 if len(changes_files) == 1:
173 # Read in all the .changes files
174 for filename in changes_files:
176 Upload.pkg.changes_file = filename
179 cache[filename] = copy.copy(Upload.pkg.changes)
180 cache[filename]["filename"] = filename
182 sorted_list.append(filename)
184 # Divide the .changes into per-source groups
186 for filename in cache.keys():
187 source = cache[filename]["source"]
188 if not per_source.has_key(source):
189 per_source[source] = {}
190 per_source[source]["list"] = []
191 per_source[source]["list"].append(cache[filename])
192 # Determine oldest time and have note status for each source group
193 for source in per_source.keys():
194 source_list = per_source[source]["list"]
195 first = source_list[0]
196 oldest = os.stat(first["filename"])[stat.ST_MTIME]
198 for d in per_source[source]["list"]:
199 mtime = os.stat(d["filename"])[stat.ST_MTIME]
202 have_note += (d.has_key("process-new note"))
203 per_source[source]["oldest"] = oldest
205 per_source[source]["note_state"] = 0; # none
206 elif have_note < len(source_list):
207 per_source[source]["note_state"] = 1; # some
209 per_source[source]["note_state"] = 2; # all
210 per_source[source]["list"].sort(indiv_sg_compare)
211 per_source_items = per_source.items()
212 per_source_items.sort(sg_compare)
213 for i in per_source_items:
214 for j in i[1]["list"]:
215 sorted_list.append(j["filename"])
218 ################################################################################
220 class Section_Completer:
223 q = projectB.query("SELECT section FROM section")
224 for i in q.getresult():
225 self.sections.append(i[0])
227 def complete(self, text, state):
231 for word in self.sections:
233 self.matches.append(word)
235 return self.matches[state]
239 ############################################################
241 class Priority_Completer:
244 q = projectB.query("SELECT priority FROM priority")
245 for i in q.getresult():
246 self.priorities.append(i[0])
248 def complete(self, text, state):
252 for word in self.priorities:
254 self.matches.append(word)
256 return self.matches[state]
260 ################################################################################
262 def print_new (new, indexed, file=sys.stdout):
263 daklib.queue.check_valid(new)
266 for pkg in new.keys():
268 section = new[pkg]["section"]
269 priority = new[pkg]["priority"]
270 if new[pkg]["section id"] == -1:
273 if new[pkg]["priority id"] == -1:
277 line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
279 line = "%-20s %-20s %-20s" % (pkg, priority, section)
280 line = line.strip()+'\n'
282 note = Upload.pkg.changes.get("process-new note")
289 ################################################################################
291 def index_range (index):
295 return "1-%s" % (index)
297 ################################################################################
298 ################################################################################
301 # Write the current data to a temporary file
302 temp_filename = daklib.utils.temp_filename()
303 temp_file = daklib.utils.open_file(temp_filename, 'w')
304 print_new (new, 0, temp_file)
306 # Spawn an editor on that file
307 editor = os.environ.get("EDITOR","vi")
308 result = os.system("%s %s" % (editor, temp_filename))
310 daklib.utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
311 # Read the edited data back in
312 temp_file = daklib.utils.open_file(temp_filename)
313 lines = temp_file.readlines()
315 os.unlink(temp_filename)
322 # Pad the list if necessary
323 s[len(s):3] = [None] * (3-len(s))
324 (pkg, priority, section) = s[:3]
325 if not new.has_key(pkg):
326 daklib.utils.warn("Ignoring unknown package '%s'" % (pkg))
328 # Strip off any invalid markers, print_new will readd them.
329 if section.endswith("[!]"):
330 section = section[:-3]
331 if priority.endswith("[!]"):
332 priority = priority[:-3]
333 for file in new[pkg]["files"]:
334 Upload.pkg.files[file]["section"] = section
335 Upload.pkg.files[file]["priority"] = priority
336 new[pkg]["section"] = section
337 new[pkg]["priority"] = priority
339 ################################################################################
341 def edit_index (new, index):
342 priority = new[index]["priority"]
343 section = new[index]["section"]
344 type = new[index]["type"]
347 print "\t".join([index, priority, section])
351 prompt = "[B]oth, Priority, Section, Done ? "
353 prompt = "[S]ection, Done ? "
354 edit_priority = edit_section = 0
356 while prompt.find(answer) == -1:
357 answer = daklib.utils.our_raw_input(prompt)
358 m = daklib.queue.re_default_answer.match(prompt)
361 answer = answer[:1].upper()
368 edit_priority = edit_section = 1
374 readline.set_completer(Priorities.complete)
376 while not got_priority:
377 new_priority = daklib.utils.our_raw_input("New priority: ").strip()
378 if new_priority not in Priorities.priorities:
379 print "E: '%s' is not a valid priority, try again." % (new_priority)
382 priority = new_priority
386 readline.set_completer(Sections.complete)
388 while not got_section:
389 new_section = daklib.utils.our_raw_input("New section: ").strip()
390 if new_section not in Sections.sections:
391 print "E: '%s' is not a valid section, try again." % (new_section)
394 section = new_section
396 # Reset the readline completer
397 readline.set_completer(None)
399 for file in new[index]["files"]:
400 Upload.pkg.files[file]["section"] = section
401 Upload.pkg.files[file]["priority"] = priority
402 new[index]["priority"] = priority
403 new[index]["section"] = section
406 ################################################################################
408 def edit_overrides (new):
419 prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
422 while not got_answer:
423 answer = daklib.utils.our_raw_input(prompt)
424 if not answer.isdigit():
425 answer = answer[:1].upper()
426 if answer == "E" or answer == "D":
428 elif daklib.queue.re_isanum.match (answer):
430 if (answer < 1) or (answer > index):
431 print "%s is not a valid index (%s). Please retry." % (answer, index_range(index))
440 edit_index (new, new_index[answer])
444 ################################################################################
447 # Write the current data to a temporary file
448 temp_filename = daklib.utils.temp_filename()
449 temp_file = daklib.utils.open_file(temp_filename, 'w')
450 temp_file.write(note)
452 editor = os.environ.get("EDITOR","vi")
455 os.system("%s %s" % (editor, temp_filename))
456 temp_file = daklib.utils.open_file(temp_filename)
457 note = temp_file.read().rstrip()
460 print daklib.utils.prefix_multi_line_string(note," ")
461 prompt = "[D]one, Edit, Abandon, Quit ?"
463 while prompt.find(answer) == -1:
464 answer = daklib.utils.our_raw_input(prompt)
465 m = daklib.queue.re_default_answer.search(prompt)
468 answer = answer[:1].upper()
469 os.unlink(temp_filename)
474 Upload.pkg.changes["process-new note"] = note
475 Upload.dump_vars(Cnf["Dir::Queue::New"])
477 ################################################################################
481 less_fd = os.popen("less -R -", 'w', 0)
482 stdout_fd = sys.stdout
485 examine_package.display_changes(Upload.pkg.changes_file)
486 files = Upload.pkg.files
487 for file in files.keys():
488 if files[file].has_key("new"):
489 type = files[file]["type"]
491 examine_package.check_deb(file)
493 examine_package.check_dsc(file)
495 sys.stdout = stdout_fd
497 if errno.errorcode[e.errno] == 'EPIPE':
498 daklib.utils.warn("[examine_package] Caught EPIPE; skipping.")
502 except KeyboardInterrupt:
503 daklib.utils.warn("[examine_package] Caught C-c; skipping.")
506 ################################################################################
508 ## FIXME: horribly Debian specific
510 def do_bxa_notification():
511 files = Upload.pkg.files
513 for file in files.keys():
514 if files[file]["type"] == "deb":
515 control = apt_pkg.ParseSection(apt_inst.debExtractControl(daklib.utils.open_file(file)))
517 summary += "Package: %s\n" % (control.Find("Package"))
518 summary += "Description: %s\n" % (control.Find("Description"))
519 Upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
520 bxa_mail = daklib.utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-new.bxa_notification")
521 daklib.utils.send_mail(bxa_mail)
523 ################################################################################
525 def add_overrides (new):
526 changes = Upload.pkg.changes
527 files = Upload.pkg.files
529 projectB.query("BEGIN WORK")
530 for suite in changes["suite"].keys():
531 suite_id = daklib.database.get_suite_id(suite)
532 for pkg in new.keys():
533 component_id = daklib.database.get_component_id(new[pkg]["component"])
534 type_id = daklib.database.get_override_type_id(new[pkg]["type"])
535 priority_id = new[pkg]["priority id"]
536 section_id = new[pkg]["section id"]
537 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))
538 for file in new[pkg]["files"]:
539 if files[file].has_key("new"):
540 del files[file]["new"]
543 projectB.query("COMMIT WORK")
545 if Cnf.FindB("Dinstall::BXANotify"):
546 do_bxa_notification()
548 ################################################################################
550 def prod_maintainer ():
551 # Here we prepare an editor and get them ready to prod...
552 temp_filename = daklib.utils.temp_filename()
553 editor = os.environ.get("EDITOR","vi")
556 os.system("%s %s" % (editor, temp_filename))
557 file = daklib.utils.open_file(temp_filename)
558 prod_message = "".join(file.readlines())
560 print "Prod message:"
561 print daklib.utils.prefix_multi_line_string(prod_message," ",include_blank_lines=1)
562 prompt = "[P]rod, Edit, Abandon, Quit ?"
564 while prompt.find(answer) == -1:
565 answer = daklib.utils.our_raw_input(prompt)
566 m = daklib.queue.re_default_answer.search(prompt)
569 answer = answer[:1].upper()
570 os.unlink(temp_filename)
575 # Otherwise, do the proding...
576 user_email_address = daklib.utils.whoami() + " <%s>" % (
577 Cnf["Dinstall::MyAdminAddress"])
581 Subst["__FROM_ADDRESS__"] = user_email_address
582 Subst["__PROD_MESSAGE__"] = prod_message
583 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
585 prod_mail_message = daklib.utils.TemplateSubst(
586 Subst,Cnf["Dir::Templates"]+"/process-new.prod")
588 # Send the prod mail if appropriate
589 if not Cnf["Dinstall::Options::No-Mail"]:
590 daklib.utils.send_mail(prod_mail_message)
592 print "Sent proding message"
594 ################################################################################
598 files = Upload.pkg.files
599 changes = Upload.pkg.changes
601 # Make a copy of distribution we can happily trample on
602 changes["suite"] = copy.copy(changes["distribution"])
604 # Fix up the list of target suites
605 for suite in changes["suite"].keys():
606 override = Cnf.Find("Suite::%s::OverrideSuite" % (suite))
608 (olderr, newerr) = (daklib.database.get_suite_id(suite) == -1,
609 daklib.database.get_suite_id(override) == -1)
611 (oinv, newinv) = ("", "")
612 if olderr: oinv = "invalid "
613 if newerr: ninv = "invalid "
614 print "warning: overriding %ssuite %s to %ssuite %s" % (
615 oinv, suite, ninv, override)
616 del changes["suite"][suite]
617 changes["suite"][override] = 1
619 for suite in changes["suite"].keys():
620 suite_id = daklib.database.get_suite_id(suite)
622 daklib.utils.fubar("%s has invalid suite '%s' (possibly overriden). say wha?" % (changes, suite))
624 # The main NEW processing loop
627 # Find out what's new
628 new = daklib.queue.determine_new(changes, files, projectB)
634 if Options["No-Action"] or Options["Automatic"]:
637 (broken, note) = print_new(new, 0)
640 if not broken and not note:
641 prompt = "Add overrides, "
643 print "W: [!] marked entries must be fixed before package can be processed."
645 print "W: note must be removed before package can be processed."
646 prompt += "Remove note, "
648 prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
650 while prompt.find(answer) == -1:
651 answer = daklib.utils.our_raw_input(prompt)
652 m = daklib.queue.re_default_answer.search(prompt)
655 answer = answer[:1].upper()
658 done = add_overrides (new)
662 new = edit_overrides (new)
664 aborted = Upload.do_reject(1, Options["Manual-Reject"])
666 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
669 edit_note(changes.get("process-new note", ""))
673 confirm = daklib.utils.our_raw_input("Really clear note (y/N)? ").lower()
675 del changes["process-new note"]
681 ################################################################################
682 ################################################################################
683 ################################################################################
685 def usage (exit_code=0):
686 print """Usage: dak process-new [OPTION]... [CHANGES]...
687 -a, --automatic automatic run
688 -h, --help show this help and exit.
689 -m, --manual-reject=MSG manual reject with `msg'
690 -n, --no-action don't do anything
691 -V, --version display the version number and exit"""
694 ################################################################################
697 global Cnf, Options, Logger, Upload, projectB, Sections, Priorities
699 Cnf = daklib.utils.get_conf()
701 Arguments = [('a',"automatic","Process-New::Options::Automatic"),
702 ('h',"help","Process-New::Options::Help"),
703 ('C',"comments-dir","Process-New::Options::Comments-Dir", "HasArg"),
704 ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
705 ('n',"no-action","Process-New::Options::No-Action")]
707 for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir"]:
708 if not Cnf.has_key("Process-New::Options::%s" % (i)):
709 Cnf["Process-New::Options::%s" % (i)] = ""
711 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
712 Options = Cnf.SubTree("Process-New::Options")
717 Upload = daklib.queue.Upload(Cnf)
719 if not Options["No-Action"]:
720 Logger = Upload.Logger = daklib.logging.Logger(Cnf, "process-new")
722 projectB = Upload.projectB
724 Sections = Section_Completer()
725 Priorities = Priority_Completer()
726 readline.parse_and_bind("tab: complete")
730 ################################################################################
735 files = Upload.pkg.files
739 for file in files.keys():
740 if files[file]["type"] == "byhand":
741 if os.path.exists(file):
742 print "W: %s still present; please process byhand components and try again." % (file)
748 if Options["No-Action"]:
751 if Options["Automatic"] and not Options["No-Action"]:
753 prompt = "[A]ccept, Manual reject, Skip, Quit ?"
755 prompt = "Manual reject, [S]kip, Quit ?"
757 while prompt.find(answer) == -1:
758 answer = daklib.utils.our_raw_input(prompt)
759 m = daklib.queue.re_default_answer.search(prompt)
762 answer = answer[:1].upper()
769 Upload.do_reject(1, Options["Manual-Reject"])
770 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
777 ################################################################################
779 def get_accept_lock():
783 lock_fd = os.open(Cnf["Process-New::AcceptedLockFile"], os.O_RDONLY | os.O_CREAT | os.O_EXCL)
786 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
789 daklib.utils.fubar("Couldn't obtain lock; assuming 'dak process-unchecked' is already running.")
791 print("Unable to get accepted lock (try %d of 10)" % retry)
796 def move_to_dir (dest, perms=0660, changesperms=0664):
797 daklib.utils.move (Upload.pkg.changes_file, dest, perms=changesperms)
798 file_keys = Upload.pkg.files.keys()
799 for file in file_keys:
800 daklib.utils.move (file, dest, perms=perms)
804 if not Options["No-Action"]:
806 (summary, short_summary) = Upload.build_summaries()
807 if Cnf.FindB("Dinstall::SecurityQueueHandling"):
808 Upload.dump_vars(Cnf["Dir::Queue::Embargoed"])
809 move_to_dir(Cnf["Dir::Queue::Embargoed"])
810 Upload.queue_build("embargoed", Cnf["Dir::Queue::Embargoed"])
811 # Check for override disparities
812 Upload.Subst["__SUMMARY__"] = summary
814 Upload.accept(summary, short_summary)
815 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
816 os.unlink(Cnf["Process-New::AcceptedLockFile"])
818 def check_status(files):
820 for file in files.keys():
821 if files[file]["type"] == "byhand":
823 elif files[file].has_key("new"):
827 def do_pkg(changes_file):
828 Upload.pkg.changes_file = changes_file
831 Upload.update_subst()
832 files = Upload.pkg.files
837 (new, byhand) = check_status(files)
843 (new, byhand) = check_status(files)
845 if not new and not byhand:
848 ################################################################################
851 accept_count = Upload.accept_count
852 accept_bytes = Upload.accept_bytes
858 sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, daklib.utils.size_type(int(accept_bytes))))
859 Logger.log(["total",accept_count,accept_bytes])
861 if not Options["No-Action"]:
864 ################################################################################
866 def do_comments(dir, opref, npref, line, fn):
867 for comm in [ x for x in os.listdir(dir) if x.startswith(opref) ]:
868 lines = open("%s/%s" % (dir, comm)).readlines()
869 if len(lines) == 0 or lines[0] != line + "\n": continue
870 changes_files = [ x for x in os.listdir(".") if x.startswith(comm[7:]+"_")
871 and x.endswith(".changes") ]
872 changes_files = sort_changes(changes_files)
873 for f in changes_files:
874 f = daklib.utils.validate_changes_file_arg(f, 0)
877 fn(f, "".join(lines[1:]))
879 if opref != npref and not Options["No-Action"]:
880 newcomm = npref + comm[len(opref):]
881 os.rename("%s/%s" % (dir, comm), "%s/%s" % (dir, newcomm))
883 ################################################################################
885 def comment_accept(changes_file, comments):
886 Upload.pkg.changes_file = changes_file
889 Upload.update_subst()
890 files = Upload.pkg.files
893 return # dak wants to REJECT, crap
895 (new, byhand) = check_status(files)
896 if not new and not byhand:
899 ################################################################################
901 def comment_reject(changes_file, comments):
902 Upload.pkg.changes_file = changes_file
905 Upload.update_subst()
906 files = Upload.pkg.files
909 pass # dak has its own reasons to reject as well, which is fine
912 print "REJECT\n" + reject_message,
913 if not Options["No-Action"]:
914 Upload.do_reject(0, reject_message)
915 os.unlink(Upload.pkg.changes_file[:-8]+".dak")
917 ################################################################################
920 changes_files = init()
921 if len(changes_files) > 50:
922 sys.stderr.write("Sorting changes...\n")
923 changes_files = sort_changes(changes_files)
925 # Kill me now? **FIXME**
926 Cnf["Dinstall::Options::No-Mail"] = ""
927 bcc = "X-DAK: dak process-new\nX-Katie: lisa $Revision: 1.31 $"
928 if Cnf.has_key("Dinstall::Bcc"):
929 Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
931 Upload.Subst["__BCC__"] = bcc
933 commentsdir = Cnf.get("Process-New::Options::Comments-Dir","")
935 if changes_files != []:
936 sys.stderr.write("Can't specify any changes files if working with comments-dir")
938 do_comments(commentsdir, "ACCEPT.", "ACCEPTED.", "OK", comment_accept)
939 do_comments(commentsdir, "REJECT.", "REJECTED.", "NOTOK", comment_reject)
941 for changes_file in changes_files:
942 changes_file = daklib.utils.validate_changes_file_arg(changes_file, 0)
945 print "\n" + changes_file
946 do_pkg (changes_file)
950 ################################################################################
952 if __name__ == '__main__':