1#! /usr/bin/env python3
2# vim:set et ts=4 sw=4:
4"""Handles NEW and BYHAND packages
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org>
8@copyright: 2009 Joerg Jaspert <joerg@debian.org>
9@copyright: 2009 Frank Lichtenheld <djpig@debian.org>
10@license: GNU General Public License version 2 or later
11"""
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
22# You should have received a copy of the GNU General Public License
23# along with this program; if not, write to the Free Software
24# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26################################################################################
28# 23:12|<aj> I will not hush!
29# 23:12|<elmo> :>
30# 23:12|<aj> Where there is injustice in the world, I shall be there!
31# 23:13|<aj> I shall not be silenced!
32# 23:13|<aj> The world shall know!
33# 23:13|<aj> The world *must* know!
34# 23:13|<elmo> oh dear, he's gone back to powerpuff girls... ;-)
35# 23:13|<aj> yay powerpuff girls!!
36# 23:13|<aj> buttercup's my favourite, who's yours?
37# 23:14|<aj> you're backing away from the keyboard right now aren't you?
38# 23:14|<aj> *AREN'T YOU*?!
39# 23:15|<aj> I will not be treated like this.
40# 23:15|<aj> I shall have my revenge.
41# 23:15|<aj> I SHALL!!!
43################################################################################
45import contextlib
46import errno
47import os
48import pwd
49import readline
50import stat
51import subprocess
52import sys
53import tempfile
54from collections import defaultdict
55from collections.abc import Iterable
56from typing import NoReturn, Optional
58import apt_pkg
59from sqlalchemy import or_
61import dak.examine_package
62import daklib.dbconn
63from daklib import daklog, utils
64from daklib.config import Config
65from daklib.dak_exceptions import AlreadyLockedError
66from daklib.dbconn import (
67 BinaryMetadata,
68 DBBinary,
69 DBChange,
70 DBConn,
71 DBSource,
72 MetadataKey,
73 NewComment,
74 PolicyQueue,
75 PolicyQueueUpload,
76 Priority,
77 Section,
78 Suite,
79 get_new_comments,
80)
81from daklib.policy import PolicyQueueUploadHandler, UploadCopy
82from daklib.queue import check_valid, edit_note, prod_maintainer
83from daklib.regexes import re_default_answer, re_isanum
84from daklib.summarystats import SummaryStats
85from daklib.termcolor import colorize as Color
87# Globals
88Options = None
89Logger = None
91Priorities = None
92Sections = None
94################################################################################
95################################################################################
96################################################################################
99class Section_Completer:
100 def __init__(self, session):
101 self.sections = []
102 self.matches = []
103 for (s,) in session.query(Section.section):
104 self.sections.append(s)
106 def complete(self, text, state):
107 if state == 0:
108 self.matches = []
109 n = len(text)
110 for word in self.sections:
111 if word[:n] == text:
112 self.matches.append(word)
113 try:
114 return self.matches[state]
115 except IndexError:
116 return None
119############################################################
122class Priority_Completer:
123 def __init__(self, session):
124 self.priorities = []
125 self.matches = []
126 for (p,) in session.query(Priority.priority):
127 self.priorities.append(p)
129 def complete(self, text, state):
130 if state == 0:
131 self.matches = []
132 n = len(text)
133 for word in self.priorities:
134 if word[:n] == text:
135 self.matches.append(word)
136 try:
137 return self.matches[state]
138 except IndexError:
139 return None
142################################################################################
145def takenover_binaries(upload, missing, session):
146 rows = []
147 binaries = set([x.package for x in upload.binaries])
148 for m in missing:
149 if m["type"] != "dsc":
150 binaries.discard(m["package"])
151 if binaries:
152 source = upload.binaries[0].source.source
153 suite = upload.target_suite.overridesuite or upload.target_suite.suite_name
154 suites = [
155 s[0]
156 for s in session.query(Suite.suite_name)
157 .filter(or_(Suite.suite_name == suite, Suite.overridesuite == suite))
158 .all()
159 ]
160 rows = (
161 session.query(DBSource.source, DBBinary.package)
162 .distinct()
163 .filter(DBBinary.package.in_(binaries))
164 .join(DBBinary.source)
165 .filter(DBSource.source != source)
166 .join(DBBinary.suites)
167 .filter(Suite.suite_name.in_(suites))
168 .order_by(DBSource.source, DBBinary.package)
169 .all()
170 )
171 return rows
174################################################################################
177def print_new(
178 upload: daklib.dbconn.PolicyQueueUpload,
179 missing,
180 indexed: bool,
181 session,
182 file=sys.stdout,
183):
184 check_valid(missing, session)
185 for index, m in enumerate(missing, 1):
186 if m["type"] != "deb":
187 package = "{0}:{1}".format(m["type"], m["package"])
188 else:
189 package = m["package"]
190 section = m["section"]
191 priority = m["priority"]
192 if m["type"] == "deb" and priority != "optional": 192 ↛ 193line 192 didn't jump to line 193, because the condition on line 192 was never true
193 priority = Color(priority, "red")
194 included = "" if m["included"] else "NOT UPLOADED"
195 if indexed: 195 ↛ 196line 195 didn't jump to line 196, because the condition on line 195 was never true
196 line = "(%s): %-20s %-20s %-20s %s" % (
197 index,
198 package,
199 priority,
200 section,
201 included,
202 )
203 else:
204 line = "%-20s %-20s %-20s %s" % (package, priority, section, included)
205 line = line.strip()
206 if not m["valid"]: 206 ↛ 207line 206 didn't jump to line 207, because the condition on line 206 was never true
207 line = line + " [!]"
208 print(line, file=file)
209 takenover = takenover_binaries(upload, missing, session)
210 if takenover: 210 ↛ 211line 210 didn't jump to line 211, because the condition on line 210 was never true
211 print("\n\nBINARIES TAKEN OVER\n")
212 for t in takenover:
213 print("%s: %s" % (t[0], t[1]))
214 notes = get_new_comments(upload.policy_queue, upload.changes.source)
215 for note in notes: 215 ↛ 216line 215 didn't jump to line 216, because the loop on line 215 never started
216 print("\n")
217 print(Color("Author:", "yellow"), "%s" % note.author)
218 print(Color("Version:", "yellow"), "%s" % note.version)
219 print(Color("Timestamp:", "yellow"), "%s" % note.notedate)
220 print("\n\n")
221 print(note.comment)
222 print("-" * 72)
223 return len(notes) > 0
226################################################################################
229def index_range(index: int) -> str:
230 if index == 1:
231 return "1"
232 else:
233 return "1-%s" % (index)
236################################################################################
237################################################################################
240def edit_new(overrides, upload: daklib.dbconn.PolicyQueueUpload, session):
241 with tempfile.NamedTemporaryFile(mode="w+t") as fh:
242 # Write the current data to a temporary file
243 print_new(upload, overrides, indexed=False, session=session, file=fh)
244 fh.flush()
245 utils.call_editor_for_file(fh.name)
246 # Read the edited data back in
247 fh.seek(0)
248 lines = fh.readlines()
250 overrides_map = dict([((o["type"], o["package"]), o) for o in overrides])
251 new_overrides = []
252 # Parse the new data
253 for line in lines:
254 line = line.strip()
255 if line == "" or line[0] == "#":
256 continue
257 s = line.split()
258 # Pad the list if necessary
259 s[len(s) : 3] = [None] * (3 - len(s))
260 (pkg, priority, section) = s[:3]
261 if pkg.find(":") != -1:
262 type, pkg = pkg.split(":", 1)
263 else:
264 type = "deb"
265 o = overrides_map.get((type, pkg), None)
266 if o is None:
267 utils.warn("Ignoring unknown package '%s'" % (pkg))
268 else:
269 if section.find("/") != -1:
270 component = section.split("/", 1)[0]
271 else:
272 component = "main"
273 new_overrides.append(
274 dict(
275 package=pkg,
276 type=type,
277 section=section,
278 component=component,
279 priority=priority,
280 included=o["included"],
281 )
282 )
283 return new_overrides
286################################################################################
289def edit_index(new, upload: daklib.dbconn.PolicyQueueUpload, index):
290 package = new[index]["package"]
291 priority = new[index]["priority"]
292 section = new[index]["section"]
293 ftype = new[index]["type"]
294 done = False
295 while not done:
296 print("\t".join([package, priority, section]))
298 answer = "XXX"
299 if ftype != "dsc":
300 prompt = "[B]oth, Priority, Section, Done ? "
301 else:
302 prompt = "[S]ection, Done ? "
303 edit_priority = edit_section = 0
305 while prompt.find(answer) == -1:
306 answer = utils.input_or_exit(prompt)
307 m = re_default_answer.match(prompt)
308 if answer == "":
309 answer = m.group(1)
310 answer = answer[:1].upper()
312 if answer == "P":
313 edit_priority = 1
314 elif answer == "S":
315 edit_section = 1
316 elif answer == "B":
317 edit_priority = edit_section = 1
318 elif answer == "D":
319 done = True
321 # Edit the priority
322 if edit_priority:
323 readline.set_completer(Priorities.complete)
324 got_priority = 0
325 while not got_priority:
326 new_priority = utils.input_or_exit("New priority: ").strip()
327 if new_priority not in Priorities.priorities:
328 print(
329 "E: '%s' is not a valid priority, try again." % (new_priority)
330 )
331 else:
332 got_priority = 1
333 priority = new_priority
335 # Edit the section
336 if edit_section:
337 readline.set_completer(Sections.complete)
338 got_section = 0
339 while not got_section:
340 new_section = utils.input_or_exit("New section: ").strip()
341 if new_section not in Sections.sections:
342 print("E: '%s' is not a valid section, try again." % (new_section))
343 else:
344 got_section = 1
345 section = new_section
347 # Reset the readline completer
348 readline.set_completer(None)
350 new[index]["priority"] = priority
351 new[index]["section"] = section
352 if section.find("/") != -1:
353 component = section.split("/", 1)[0]
354 else:
355 component = "main"
356 new[index]["component"] = component
358 return new
361################################################################################
364def edit_overrides(new, upload: daklib.dbconn.PolicyQueueUpload, session):
365 print()
366 done = False
367 while not done:
368 print_new(upload, new, indexed=True, session=session)
369 prompt = "edit override <n>, Editor, Done ? "
371 got_answer = 0
372 while not got_answer:
373 answer = utils.input_or_exit(prompt)
374 if not answer.isdigit():
375 answer = answer[:1].upper()
376 if answer == "E" or answer == "D":
377 got_answer = 1
378 elif re_isanum.match(answer):
379 answer = int(answer)
380 if answer < 1 or answer > len(new):
381 print("{0} is not a valid index. Please retry.".format(answer))
382 else:
383 got_answer = 1
385 if answer == "E":
386 new = edit_new(new, upload, session)
387 elif answer == "D":
388 done = True
389 else:
390 edit_index(new, upload, answer - 1)
392 return new
395################################################################################
398def check_pkg(upload, upload_copy: UploadCopy, session):
399 missing = []
400 changes = os.path.join(upload_copy.directory, upload.changes.changesname)
401 suite_name = upload.target_suite.suite_name
402 handler = PolicyQueueUploadHandler(upload, session)
403 missing = [
404 (m["type"], m["package"]) for m in handler.missing_overrides(hints=missing)
405 ]
407 less_cmd = ("less", "-r", "-")
408 less_process = subprocess.Popen(
409 less_cmd, bufsize=0, stdin=subprocess.PIPE, text=True
410 )
411 try:
412 less_fd = less_process.stdin
413 less_fd.write(dak.examine_package.display_changes(suite_name, changes))
415 source = upload.source
416 if source is not None: 416 ↛ 422line 416 didn't jump to line 422, because the condition on line 416 was never false
417 source_file = os.path.join(
418 upload_copy.directory, os.path.basename(source.poolfile.filename)
419 )
420 less_fd.write(dak.examine_package.check_dsc(suite_name, source_file))
422 for binary in upload.binaries:
423 binary_file = os.path.join(
424 upload_copy.directory, os.path.basename(binary.poolfile.filename)
425 )
426 examined = dak.examine_package.check_deb(suite_name, binary_file)
427 # We always need to call check_deb to display package relations for every binary,
428 # but we print its output only if new overrides are being added.
429 if ("deb", binary.package) in missing: 429 ↛ 422line 429 didn't jump to line 422, because the condition on line 429 was never false
430 less_fd.write(examined)
432 less_fd.write(dak.examine_package.output_package_relations())
433 less_process.stdin.close()
434 except OSError as e:
435 if e.errno == errno.EPIPE:
436 utils.warn("[examine_package] Caught EPIPE; skipping.")
437 else:
438 raise
439 except KeyboardInterrupt:
440 utils.warn("[examine_package] Caught C-c; skipping.")
441 finally:
442 less_process.communicate()
445################################################################################
447## FIXME: horribly Debian specific
450def do_bxa_notification(new, upload: daklib.dbconn.PolicyQueueUpload, session):
451 cnf = Config()
453 new = set([o["package"] for o in new if o["type"] == "deb"])
454 if len(new) == 0:
455 return
457 key = session.query(MetadataKey).filter_by(key="Description").one()
458 summary = ""
459 for binary in upload.binaries:
460 if binary.package not in new:
461 continue
462 description = (
463 session.query(BinaryMetadata).filter_by(binary=binary, key=key).one().value
464 )
465 summary += "\n"
466 summary += "Package: {0}\n".format(binary.package)
467 summary += "Description: {0}\n".format(description)
469 subst = {
470 "__DISTRO__": cnf["Dinstall::MyDistribution"],
471 "__BCC__": "X-DAK: dak process-new",
472 "__BINARY_DESCRIPTIONS__": summary,
473 "__CHANGES_FILENAME__": upload.changes.changesname,
474 "__SOURCE__": upload.changes.source,
475 "__VERSION__": upload.changes.version,
476 "__ARCHITECTURE__": upload.changes.architecture,
477 }
479 bxa_mail = utils.TemplateSubst(
480 subst, os.path.join(cnf["Dir::Templates"], "process-new.bxa_notification")
481 )
482 utils.send_mail(bxa_mail)
485################################################################################
488def run_user_inspect_command(
489 upload: daklib.dbconn.PolicyQueueUpload, upload_copy: UploadCopy
490):
491 command = os.environ.get("DAK_INSPECT_UPLOAD")
492 if command is None: 492 ↛ 495line 492 didn't jump to line 495, because the condition on line 492 was never false
493 return
495 directory = upload_copy.directory
496 if upload.source:
497 dsc = os.path.basename(upload.source.poolfile.filename)
498 else:
499 dsc = ""
500 changes = upload.changes.changesname
502 shell_command = command.format(
503 directory=directory,
504 dsc=dsc,
505 changes=changes,
506 )
508 subprocess.check_call(shell_command, shell=True)
511################################################################################
514def get_reject_reason(reason: str = "") -> Optional[str]:
515 """get reason for rejection
517 :return: string giving the reason for the rejection or :const:`None` if the
518 rejection should be cancelled
519 """
520 answer = "E"
521 if Options["Automatic"]: 521 ↛ 522line 521 didn't jump to line 522, because the condition on line 521 was never true
522 answer = "R"
524 while answer == "E":
525 reason = utils.call_editor(reason)
526 print("Reject message:")
527 print(utils.prefix_multi_line_string(reason, " ", include_blank_lines=True))
528 prompt = "[R]eject, Edit, Abandon, Quit ?"
529 answer = "XXX"
530 while prompt.find(answer) == -1:
531 answer = utils.input_or_exit(prompt)
532 m = re_default_answer.search(prompt)
533 if answer == "": 533 ↛ 534line 533 didn't jump to line 534, because the condition on line 533 was never true
534 answer = m.group(1)
535 answer = answer[:1].upper()
537 if answer == "Q": 537 ↛ 538line 537 didn't jump to line 538, because the condition on line 537 was never true
538 sys.exit(0)
540 if answer == "R": 540 ↛ 542line 540 didn't jump to line 542, because the condition on line 540 was never false
541 return reason
542 return None
545################################################################################
548def do_new(
549 upload: daklib.dbconn.PolicyQueueUpload, upload_copy: UploadCopy, handler, session
550):
551 run_user_inspect_command(upload, upload_copy)
553 # The main NEW processing loop
554 done = False
555 missing = []
556 while not done:
557 queuedir = upload.policy_queue.path
558 byhand = upload.byhand
560 missing = handler.missing_overrides(hints=missing)
561 broken = not check_valid(missing, session)
563 changesname = os.path.basename(upload.changes.changesname)
565 print()
566 print(changesname)
567 print("-" * len(changesname))
568 print()
569 print(" Target: {0}".format(upload.target_suite.suite_name))
570 print(" Changed-By: {0}".format(upload.changes.changedby))
571 print(" Date: {0}".format(upload.changes.date))
572 print()
574 if missing:
575 print("NEW\n")
577 for package in missing:
578 if package["type"] == "deb" and package["priority"] == "extra": 578 ↛ 579line 578 didn't jump to line 579, because the condition on line 578 was never true
579 package["priority"] = "optional"
581 answer = "XXX"
582 if Options["No-Action"] or Options["Automatic"]:
583 answer = "S"
585 note = print_new(upload, missing, indexed=False, session=session)
586 prompt = ""
588 has_unprocessed_byhand = False
589 for f in byhand: 589 ↛ 590line 589 didn't jump to line 590, because the loop on line 589 never started
590 path = os.path.join(queuedir, f.filename)
591 if not f.processed and os.path.exists(path):
592 print(
593 "W: {0} still present; please process byhand components and try again".format(
594 f.filename
595 )
596 )
597 has_unprocessed_byhand = True
599 if not has_unprocessed_byhand and not broken and not note: 599 ↛ 605line 599 didn't jump to line 605, because the condition on line 599 was never false
600 if len(missing) == 0:
601 prompt = "Accept, "
602 answer = "A"
603 else:
604 prompt = "Add overrides, "
605 if broken: 605 ↛ 606line 605 didn't jump to line 606, because the condition on line 605 was never true
606 print(
607 "W: [!] marked entries must be fixed before package can be processed."
608 )
609 if note: 609 ↛ 610line 609 didn't jump to line 610, because the condition on line 609 was never true
610 print("W: note must be removed before package can be processed.")
611 prompt += "RemOve all notes, Remove note, "
613 prompt += (
614 "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
615 )
617 while prompt.find(answer) == -1:
618 answer = utils.input_or_exit(prompt)
619 m = re_default_answer.search(prompt)
620 if answer == "": 620 ↛ 621line 620 didn't jump to line 621, because the condition on line 620 was never true
621 answer = m.group(1)
622 answer = answer[:1].upper()
624 if answer in ("A", "E", "M", "O", "R") and Options["Trainee"]: 624 ↛ 625line 624 didn't jump to line 625, because the condition on line 624 was never true
625 utils.warn("Trainees can't do that")
626 continue
628 if answer == "A" and not Options["Trainee"]:
629 handler.add_overrides(missing, upload.target_suite)
630 if Config().find_b("Dinstall::BXANotify"): 630 ↛ 632line 630 didn't jump to line 632, because the condition on line 630 was never false
631 do_bxa_notification(missing, upload, session)
632 handler.accept()
633 done = True
634 Logger.log(["NEW ACCEPT", upload.changes.changesname])
635 elif answer == "C":
636 check_pkg(upload, upload_copy, session)
637 elif answer == "E" and not Options["Trainee"]: 637 ↛ 638line 637 didn't jump to line 638, because the condition on line 637 was never true
638 missing = edit_overrides(missing, upload, session)
639 elif answer == "M" and not Options["Trainee"]:
640 reason = Options.get("Manual-Reject", "") + "\n"
641 reason = reason + "\n\n=====\n\n".join(
642 [
643 n.comment
644 for n in get_new_comments(
645 upload.policy_queue, upload.changes.source, session=session
646 )
647 ]
648 )
649 reason = get_reject_reason(reason)
650 if reason is not None: 650 ↛ 709line 650 didn't jump to line 709, because the condition on line 650 was never false
651 Logger.log(["NEW REJECT", upload.changes.changesname])
652 handler.reject(reason)
653 done = True
654 elif answer == "N": 654 ↛ 655line 654 didn't jump to line 655, because the condition on line 654 was never true
655 if (
656 edit_note(
657 get_new_comments(
658 upload.policy_queue, upload.changes.source, session=session
659 ),
660 upload,
661 session,
662 bool(Options["Trainee"]),
663 )
664 == 0
665 ):
666 end()
667 sys.exit(0)
668 elif answer == "P" and not Options["Trainee"]: 668 ↛ 669line 668 didn't jump to line 669, because the condition on line 668 was never true
669 if (
670 prod_maintainer(
671 get_new_comments(
672 upload.policy_queue, upload.changes.source, session=session
673 ),
674 upload,
675 session,
676 bool(Options["Trainee"]),
677 )
678 == 0
679 ):
680 end()
681 sys.exit(0)
682 Logger.log(["NEW PROD", upload.changes.changesname])
683 elif answer == "R" and not Options["Trainee"]: 683 ↛ 684line 683 didn't jump to line 684, because the condition on line 683 was never true
684 confirm = utils.input_or_exit("Really clear note (y/N)? ").lower()
685 if confirm == "y":
686 for c in get_new_comments(
687 upload.policy_queue,
688 upload.changes.source,
689 upload.changes.version,
690 session=session,
691 ):
692 session.delete(c)
693 session.commit()
694 elif answer == "O" and not Options["Trainee"]: 694 ↛ 695line 694 didn't jump to line 695, because the condition on line 694 was never true
695 confirm = utils.input_or_exit("Really clear all notes (y/N)? ").lower()
696 if confirm == "y":
697 for c in get_new_comments(
698 upload.policy_queue, upload.changes.source, session=session
699 ):
700 session.delete(c)
701 session.commit()
703 elif answer == "S": 703 ↛ 705line 703 didn't jump to line 705, because the condition on line 703 was never false
704 done = True
705 elif answer == "Q":
706 end()
707 sys.exit(0)
709 if handler.get_action():
710 print("PENDING %s\n" % handler.get_action())
713################################################################################
714################################################################################
715################################################################################
718def usage(exit_code: int = 0) -> NoReturn:
719 print(
720 """Usage: dak process-new [OPTION]... [CHANGES]...
721 -a, --automatic automatic run
722 -b, --no-binaries do not sort binary-NEW packages first
723 -c, --comments show NEW comments
724 -h, --help show this help and exit.
725 -m, --manual-reject=MSG manual reject with `msg'
726 -n, --no-action don't do anything
727 -q, --queue=QUEUE operate on a different queue
728 -t, --trainee FTP Trainee mode
729 -V, --version display the version number and exit
731ENVIRONMENT VARIABLES
733 DAK_INSPECT_UPLOAD: shell command to run to inspect a package
734 The command is automatically run in a shell when an upload
735 is checked. The following substitutions are available:
737 {directory}: directory the upload is contained in
738 {dsc}: name of the included dsc or the empty string
739 {changes}: name of the changes file
741 Note that Python's 'format' method is used to format the command.
743 Example: run mc in a tmux session to inspect the upload
745 export DAK_INSPECT_UPLOAD='tmux new-session -d -s process-new 2>/dev/null; tmux new-window -n "{changes}" -t process-new:0 -k "cd {directory}; mc"'
747 and run
749 tmux attach -t process-new
751 in a separate terminal session.
752"""
753 )
754 sys.exit(exit_code)
757################################################################################
760@contextlib.contextmanager
761def lock_package(package: str):
762 """
763 Lock `package` so that noone else jumps in processing it.
765 :param package: source package name to lock
766 """
768 cnf = Config()
770 path = os.path.join(cnf.get("Process-New::LockDir", cnf["Dir::Lock"]), package)
772 try:
773 fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDONLY, 0o644)
774 except OSError as e:
775 if e.errno == errno.EEXIST or e.errno == errno.EACCES:
776 try:
777 user = (
778 pwd.getpwuid(os.stat(path)[stat.ST_UID])[4]
779 .split(",")[0]
780 .replace(".", "")
781 )
782 except KeyError:
783 user = "TotallyUnknown"
784 raise AlreadyLockedError(user)
785 raise
787 try:
788 yield fd
789 finally:
790 os.unlink(path)
793def do_pkg(upload: daklib.dbconn.PolicyQueueUpload, session):
794 cnf = Config()
795 group = cnf.get("Dinstall::UnprivGroup") or None
797 try:
798 with lock_package(upload.changes.source), UploadCopy(
799 upload, group=group
800 ) as upload_copy:
801 handler = PolicyQueueUploadHandler(upload, session)
802 if handler.get_action() is not None:
803 print("PENDING %s\n" % handler.get_action())
804 return
806 do_new(upload, upload_copy, handler, session)
807 except AlreadyLockedError as e:
808 print("Seems to be locked by %s already, skipping..." % (e))
811def show_new_comments(uploads: Iterable[daklib.dbconn.PolicyQueueUpload], session):
812 sources = [upload.changes.source for upload in uploads]
813 if len(sources) == 0:
814 return
816 query = """SELECT package, version, comment, author
817 FROM new_comments
818 WHERE package IN :sources
819 ORDER BY package, version"""
821 r = session.execute(query, params=dict(sources=tuple(sources)))
823 for i in r:
824 print("%s_%s\n%s\n(%s)\n\n\n" % (i[0], i[1], i[2], i[3]))
826 session.rollback()
829################################################################################
832def sort_uploads(
833 new_queue: PolicyQueue,
834 uploads: Iterable[daklib.dbconn.PolicyQueueUpload],
835 session,
836 nobinaries: bool = False,
837) -> list[daklib.dbconn.PolicyQueueUpload]:
838 sources = defaultdict(list)
839 sorteduploads = []
840 suitesrc = [
841 s.source
842 for s in session.query(DBSource.source).filter(
843 DBSource.suites.any(Suite.suite_name.in_(["unstable", "experimental"]))
844 )
845 ]
846 comments = [
847 p.package
848 for p in session.query(NewComment.package)
849 .filter_by(trainee=False, policy_queue=new_queue)
850 .distinct()
851 ]
852 for upload in uploads:
853 source = upload.changes.source
854 sources[source].append(
855 {
856 "upload": upload,
857 "date": upload.changes.created,
858 "stack": 1,
859 "binary": True if source in suitesrc else False,
860 "comments": True if source in comments else False,
861 }
862 )
863 for src in sources:
864 if len(sources[src]) > 1:
865 changes = sources[src]
866 firstseen = sorted(changes, key=lambda k: (k["date"]))[0]["date"]
867 changes.sort(key=lambda item: item["date"])
868 for i in range(0, len(changes)):
869 changes[i]["date"] = firstseen
870 changes[i]["stack"] = i + 1
871 sorteduploads += sources[src]
872 if nobinaries: 872 ↛ 873line 872 didn't jump to line 873, because the condition on line 872 was never true
873 sorteduploads = [
874 u["upload"]
875 for u in sorted(
876 sorteduploads,
877 key=lambda k: (k["comments"], k["binary"], k["date"], -k["stack"]),
878 )
879 ]
880 else:
881 sorteduploads = [
882 u["upload"]
883 for u in sorted(
884 sorteduploads,
885 key=lambda k: (k["comments"], -k["binary"], k["date"], -k["stack"]),
886 )
887 ]
888 return sorteduploads
891################################################################################
894def end():
895 accept_count = SummaryStats().accept_count
896 accept_bytes = SummaryStats().accept_bytes
898 if accept_count: 898 ↛ 899line 898 didn't jump to line 899, because the condition on line 898 was never true
899 sets = "set"
900 if accept_count > 1:
901 sets = "sets"
902 print(
903 "Accepted %d package %s, %s."
904 % (accept_count, sets, utils.size_type(int(accept_bytes))),
905 file=sys.stderr,
906 )
907 Logger.log(["total", accept_count, accept_bytes])
909 if not Options["No-Action"] and not Options["Trainee"]: 909 ↛ exitline 909 didn't return from function 'end', because the condition on line 909 was never false
910 Logger.close()
913################################################################################
916def main():
917 global Options, Logger, Sections, Priorities
919 cnf = Config()
920 session = DBConn().session()
922 Arguments = [
923 ("a", "automatic", "Process-New::Options::Automatic"),
924 ("b", "no-binaries", "Process-New::Options::No-Binaries"),
925 ("c", "comments", "Process-New::Options::Comments"),
926 ("h", "help", "Process-New::Options::Help"),
927 ("m", "manual-reject", "Process-New::Options::Manual-Reject", "HasArg"),
928 ("t", "trainee", "Process-New::Options::Trainee"),
929 ("q", "queue", "Process-New::Options::Queue", "HasArg"),
930 ("n", "no-action", "Process-New::Options::No-Action"),
931 ]
933 changes_files = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
935 for i in [
936 "automatic",
937 "no-binaries",
938 "comments",
939 "help",
940 "manual-reject",
941 "no-action",
942 "version",
943 "trainee",
944 ]:
945 key = "Process-New::Options::%s" % i
946 if key not in cnf:
947 cnf[key] = ""
949 queue_name = cnf.get("Process-New::Options::Queue", "new")
950 new_queue = session.query(PolicyQueue).filter_by(queue_name=queue_name).one()
951 if len(changes_files) == 0:
952 uploads = new_queue.uploads
953 else:
954 uploads = (
955 session.query(PolicyQueueUpload)
956 .filter_by(policy_queue=new_queue)
957 .join(DBChange)
958 .filter(DBChange.changesname.in_(changes_files))
959 .all()
960 )
962 Options = cnf.subtree("Process-New::Options")
964 if Options["Help"]:
965 usage()
967 if not Options["No-Action"]: 967 ↛ 973line 967 didn't jump to line 973, because the condition on line 967 was never false
968 try:
969 Logger = daklog.Logger("process-new")
970 except OSError:
971 Options["Trainee"] = "True"
973 Sections = Section_Completer(session)
974 Priorities = Priority_Completer(session)
975 readline.parse_and_bind("tab: complete")
977 if len(uploads) > 1:
978 print("Sorting changes...", file=sys.stderr)
979 uploads = sort_uploads(new_queue, uploads, session, Options["No-Binaries"])
981 if Options["Comments"]: 981 ↛ 982line 981 didn't jump to line 982, because the condition on line 981 was never true
982 show_new_comments(uploads, session)
983 else:
984 for upload in uploads:
985 do_pkg(upload, session)
987 end()
990################################################################################
993if __name__ == "__main__":
994 main()