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 errno
46import os
47import readline
48import stat
49import subprocess
50import sys
51import tempfile
52import contextlib
53import pwd
54import apt_pkg
55import dak.examine_package
56from collections import defaultdict
57from collections.abc import Iterable
58from sqlalchemy import or_
59from typing import NoReturn, Optional
61import daklib.dbconn
62from daklib.queue import *
63from daklib import daklog
64from daklib import utils
65from daklib.regexes import re_default_answer, re_isanum
66from daklib.dak_exceptions import AlreadyLockedError
67from daklib.summarystats import SummaryStats
68from daklib.config import Config
69from daklib.policy import UploadCopy, PolicyQueueUploadHandler
70from daklib.termcolor import colorize as Color
72# Globals
73Options = None
74Logger = None
76Priorities = None
77Sections = None
79################################################################################
80################################################################################
81################################################################################
84class Section_Completer:
85 def __init__(self, session):
86 self.sections = []
87 self.matches = []
88 for s, in session.query(Section.section):
89 self.sections.append(s)
91 def complete(self, text, state):
92 if state == 0:
93 self.matches = []
94 n = len(text)
95 for word in self.sections:
96 if word[:n] == text:
97 self.matches.append(word)
98 try:
99 return self.matches[state]
100 except IndexError:
101 return None
103############################################################
106class Priority_Completer:
107 def __init__(self, session):
108 self.priorities = []
109 self.matches = []
110 for p, in session.query(Priority.priority):
111 self.priorities.append(p)
113 def complete(self, text, state):
114 if state == 0:
115 self.matches = []
116 n = len(text)
117 for word in self.priorities:
118 if word[:n] == text:
119 self.matches.append(word)
120 try:
121 return self.matches[state]
122 except IndexError:
123 return None
125################################################################################
128def takenover_binaries(upload, missing, session):
129 rows = []
130 binaries = set([x.package for x in upload.binaries])
131 for m in missing:
132 if m['type'] != 'dsc':
133 binaries.discard(m['package'])
134 if binaries:
135 source = upload.binaries[0].source.source
136 suite = upload.target_suite.overridesuite or \
137 upload.target_suite.suite_name
138 suites = [s[0] for s in session.query(Suite.suite_name).filter(or_(Suite.suite_name == suite,
139 Suite.overridesuite == suite)).all()]
140 rows = session.query(DBSource.source, DBBinary.package).distinct(). \
141 filter(DBBinary.package.in_(binaries)). \
142 join(DBBinary.source). \
143 filter(DBSource.source != source). \
144 join(DBBinary.suites). \
145 filter(Suite.suite_name.in_(suites)). \
146 order_by(DBSource.source, DBBinary.package).all()
147 return rows
149################################################################################
152def print_new(upload: daklib.dbconn.PolicyQueueUpload, missing, indexed: bool, session, file=sys.stdout):
153 check_valid(missing, session)
154 for index, m in enumerate(missing, 1):
155 if m['type'] != 'deb':
156 package = '{0}:{1}'.format(m['type'], m['package'])
157 else:
158 package = m['package']
159 section = m['section']
160 priority = m['priority']
161 if m["type"] == 'deb' and priority != 'optional': 161 ↛ 162line 161 didn't jump to line 162, because the condition on line 161 was never true
162 priority = Color(priority, "red")
163 included = "" if m['included'] else "NOT UPLOADED"
164 if indexed: 164 ↛ 165line 164 didn't jump to line 165, because the condition on line 164 was never true
165 line = "(%s): %-20s %-20s %-20s %s" % (index, package, priority, section, included)
166 else:
167 line = "%-20s %-20s %-20s %s" % (package, priority, section, included)
168 line = line.strip()
169 if not m['valid']: 169 ↛ 170line 169 didn't jump to line 170, because the condition on line 169 was never true
170 line = line + ' [!]'
171 print(line, file=file)
172 takenover = takenover_binaries(upload, missing, session)
173 if takenover: 173 ↛ 174line 173 didn't jump to line 174, because the condition on line 173 was never true
174 print('\n\nBINARIES TAKEN OVER\n')
175 for t in takenover:
176 print('%s: %s' % (t[0], t[1]))
177 notes = get_new_comments(upload.policy_queue, upload.changes.source)
178 for note in notes: 178 ↛ 179line 178 didn't jump to line 179, because the loop on line 178 never started
179 print("\n")
180 print(Color("Author:", "yellow"), "%s" % note.author)
181 print(Color("Version:", "yellow"), "%s" % note.version)
182 print(Color("Timestamp:", "yellow"), "%s" % note.notedate)
183 print("\n\n")
184 print(note.comment)
185 print("-" * 72)
186 return len(notes) > 0
188################################################################################
191def index_range(index: int) -> str:
192 if index == 1:
193 return "1"
194 else:
195 return "1-%s" % (index)
197################################################################################
198################################################################################
201def edit_new(overrides, upload: daklib.dbconn.PolicyQueueUpload, session):
202 with tempfile.NamedTemporaryFile(mode="w+t") as fh:
203 # Write the current data to a temporary file
204 print_new(upload, overrides, indexed=False, session=session, file=fh)
205 fh.flush()
206 utils.call_editor_for_file(fh.name)
207 # Read the edited data back in
208 fh.seek(0)
209 lines = fh.readlines()
211 overrides_map = dict([((o['type'], o['package']), o) for o in overrides])
212 new_overrides = []
213 # Parse the new data
214 for line in lines:
215 line = line.strip()
216 if line == "" or line[0] == '#':
217 continue
218 s = line.split()
219 # Pad the list if necessary
220 s[len(s):3] = [None] * (3 - len(s))
221 (pkg, priority, section) = s[:3]
222 if pkg.find(':') != -1:
223 type, pkg = pkg.split(':', 1)
224 else:
225 type = 'deb'
226 o = overrides_map.get((type, pkg), None)
227 if o is None:
228 utils.warn("Ignoring unknown package '%s'" % (pkg))
229 else:
230 if section.find('/') != -1:
231 component = section.split('/', 1)[0]
232 else:
233 component = 'main'
234 new_overrides.append(dict(
235 package=pkg,
236 type=type,
237 section=section,
238 component=component,
239 priority=priority,
240 included=o['included'],
241 ))
242 return new_overrides
244################################################################################
247def edit_index(new, upload: daklib.dbconn.PolicyQueueUpload, index):
248 package = new[index]['package']
249 priority = new[index]["priority"]
250 section = new[index]["section"]
251 ftype = new[index]["type"]
252 done = False
253 while not done:
254 print("\t".join([package, priority, section]))
256 answer = "XXX"
257 if ftype != "dsc":
258 prompt = "[B]oth, Priority, Section, Done ? "
259 else:
260 prompt = "[S]ection, Done ? "
261 edit_priority = edit_section = 0
263 while prompt.find(answer) == -1:
264 answer = utils.input_or_exit(prompt)
265 m = re_default_answer.match(prompt)
266 if answer == "":
267 answer = m.group(1)
268 answer = answer[:1].upper()
270 if answer == 'P':
271 edit_priority = 1
272 elif answer == 'S':
273 edit_section = 1
274 elif answer == 'B':
275 edit_priority = edit_section = 1
276 elif answer == 'D':
277 done = True
279 # Edit the priority
280 if edit_priority:
281 readline.set_completer(Priorities.complete)
282 got_priority = 0
283 while not got_priority:
284 new_priority = utils.input_or_exit("New priority: ").strip()
285 if new_priority not in Priorities.priorities:
286 print("E: '%s' is not a valid priority, try again." % (new_priority))
287 else:
288 got_priority = 1
289 priority = new_priority
291 # Edit the section
292 if edit_section:
293 readline.set_completer(Sections.complete)
294 got_section = 0
295 while not got_section:
296 new_section = utils.input_or_exit("New section: ").strip()
297 if new_section not in Sections.sections:
298 print("E: '%s' is not a valid section, try again." % (new_section))
299 else:
300 got_section = 1
301 section = new_section
303 # Reset the readline completer
304 readline.set_completer(None)
306 new[index]["priority"] = priority
307 new[index]["section"] = section
308 if section.find('/') != -1:
309 component = section.split('/', 1)[0]
310 else:
311 component = 'main'
312 new[index]['component'] = component
314 return new
316################################################################################
319def edit_overrides(new, upload: daklib.dbconn.PolicyQueueUpload, session):
320 print()
321 done = False
322 while not done:
323 print_new(upload, new, indexed=True, session=session)
324 prompt = "edit override <n>, Editor, Done ? "
326 got_answer = 0
327 while not got_answer:
328 answer = utils.input_or_exit(prompt)
329 if not answer.isdigit():
330 answer = answer[:1].upper()
331 if answer == "E" or answer == "D":
332 got_answer = 1
333 elif re_isanum.match(answer):
334 answer = int(answer)
335 if answer < 1 or answer > len(new):
336 print("{0} is not a valid index. Please retry.".format(answer))
337 else:
338 got_answer = 1
340 if answer == 'E':
341 new = edit_new(new, upload, session)
342 elif answer == 'D':
343 done = True
344 else:
345 edit_index(new, upload, answer - 1)
347 return new
350################################################################################
352def check_pkg(upload, upload_copy: UploadCopy, session):
353 missing = []
354 changes = os.path.join(upload_copy.directory, upload.changes.changesname)
355 suite_name = upload.target_suite.suite_name
356 handler = PolicyQueueUploadHandler(upload, session)
357 missing = [(m['type'], m["package"]) for m in handler.missing_overrides(hints=missing)]
359 less_cmd = ("less", "-r", "-")
360 less_process = subprocess.Popen(less_cmd, bufsize=0, stdin=subprocess.PIPE, text=True)
361 try:
362 less_fd = less_process.stdin
363 less_fd.write(dak.examine_package.display_changes(suite_name, changes))
365 source = upload.source
366 if source is not None: 366 ↛ 370line 366 didn't jump to line 370, because the condition on line 366 was never false
367 source_file = os.path.join(upload_copy.directory, os.path.basename(source.poolfile.filename))
368 less_fd.write(dak.examine_package.check_dsc(suite_name, source_file))
370 for binary in upload.binaries:
371 binary_file = os.path.join(upload_copy.directory, os.path.basename(binary.poolfile.filename))
372 examined = dak.examine_package.check_deb(suite_name, binary_file)
373 # We always need to call check_deb to display package relations for every binary,
374 # but we print its output only if new overrides are being added.
375 if ("deb", binary.package) in missing: 375 ↛ 370line 375 didn't jump to line 370, because the condition on line 375 was never false
376 less_fd.write(examined)
378 less_fd.write(dak.examine_package.output_package_relations())
379 less_process.stdin.close()
380 except OSError as e:
381 if e.errno == errno.EPIPE:
382 utils.warn("[examine_package] Caught EPIPE; skipping.")
383 else:
384 raise
385 except KeyboardInterrupt:
386 utils.warn("[examine_package] Caught C-c; skipping.")
387 finally:
388 less_process.communicate()
390################################################################################
392## FIXME: horribly Debian specific
395def do_bxa_notification(new, upload: daklib.dbconn.PolicyQueueUpload, session):
396 cnf = Config()
398 new = set([o['package'] for o in new if o['type'] == 'deb'])
399 if len(new) == 0:
400 return
402 key = session.query(MetadataKey).filter_by(key='Description').one()
403 summary = ""
404 for binary in upload.binaries:
405 if binary.package not in new:
406 continue
407 description = session.query(BinaryMetadata).filter_by(binary=binary, key=key).one().value
408 summary += "\n"
409 summary += "Package: {0}\n".format(binary.package)
410 summary += "Description: {0}\n".format(description)
412 subst = {
413 '__DISTRO__': cnf['Dinstall::MyDistribution'],
414 '__BCC__': 'X-DAK: dak process-new',
415 '__BINARY_DESCRIPTIONS__': summary,
416 '__CHANGES_FILENAME__': upload.changes.changesname,
417 '__SOURCE__': upload.changes.source,
418 '__VERSION__': upload.changes.version,
419 '__ARCHITECTURE__': upload.changes.architecture,
420 }
422 bxa_mail = utils.TemplateSubst(subst, os.path.join(cnf["Dir::Templates"], "process-new.bxa_notification"))
423 utils.send_mail(bxa_mail)
425################################################################################
428def run_user_inspect_command(upload: daklib.dbconn.PolicyQueueUpload, upload_copy: UploadCopy):
429 command = os.environ.get('DAK_INSPECT_UPLOAD')
430 if command is None: 430 ↛ 433line 430 didn't jump to line 433, because the condition on line 430 was never false
431 return
433 directory = upload_copy.directory
434 if upload.source:
435 dsc = os.path.basename(upload.source.poolfile.filename)
436 else:
437 dsc = ''
438 changes = upload.changes.changesname
440 shell_command = command.format(
441 directory=directory,
442 dsc=dsc,
443 changes=changes,
444 )
446 subprocess.check_call(shell_command, shell=True)
448################################################################################
451def get_reject_reason(reason: str = '') -> Optional[str]:
452 """get reason for rejection
454 :return: string giving the reason for the rejection or :const:`None` if the
455 rejection should be cancelled
456 """
457 answer = 'E'
458 if Options['Automatic']: 458 ↛ 459line 458 didn't jump to line 459, because the condition on line 458 was never true
459 answer = 'R'
461 while answer == 'E':
462 reason = utils.call_editor(reason)
463 print("Reject message:")
464 print(utils.prefix_multi_line_string(reason, " ", include_blank_lines=True))
465 prompt = "[R]eject, Edit, Abandon, Quit ?"
466 answer = "XXX"
467 while prompt.find(answer) == -1:
468 answer = utils.input_or_exit(prompt)
469 m = re_default_answer.search(prompt)
470 if answer == "": 470 ↛ 471line 470 didn't jump to line 471, because the condition on line 470 was never true
471 answer = m.group(1)
472 answer = answer[:1].upper()
474 if answer == 'Q': 474 ↛ 475line 474 didn't jump to line 475, because the condition on line 474 was never true
475 sys.exit(0)
477 if answer == 'R': 477 ↛ 479line 477 didn't jump to line 479, because the condition on line 477 was never false
478 return reason
479 return None
481################################################################################
484def do_new(upload: daklib.dbconn.PolicyQueueUpload, upload_copy: UploadCopy, handler, session):
485 run_user_inspect_command(upload, upload_copy)
487 # The main NEW processing loop
488 done = False
489 missing = []
490 while not done:
491 queuedir = upload.policy_queue.path
492 byhand = upload.byhand
494 missing = handler.missing_overrides(hints=missing)
495 broken = not check_valid(missing, session)
497 changesname = os.path.basename(upload.changes.changesname)
499 print()
500 print(changesname)
501 print("-" * len(changesname))
502 print()
503 print(" Target: {0}".format(upload.target_suite.suite_name))
504 print(" Changed-By: {0}".format(upload.changes.changedby))
505 print(" Date: {0}".format(upload.changes.date))
506 print()
508 if missing:
509 print("NEW\n")
511 for package in missing:
512 if package["type"] == "deb" and package["priority"] == "extra": 512 ↛ 513line 512 didn't jump to line 513, because the condition on line 512 was never true
513 package["priority"] = "optional"
515 answer = "XXX"
516 if Options["No-Action"] or Options["Automatic"]:
517 answer = 'S'
519 note = print_new(upload, missing, indexed=False, session=session)
520 prompt = ""
522 has_unprocessed_byhand = False
523 for f in byhand: 523 ↛ 524line 523 didn't jump to line 524, because the loop on line 523 never started
524 path = os.path.join(queuedir, f.filename)
525 if not f.processed and os.path.exists(path):
526 print("W: {0} still present; please process byhand components and try again".format(f.filename))
527 has_unprocessed_byhand = True
529 if not has_unprocessed_byhand and not broken and not note: 529 ↛ 535line 529 didn't jump to line 535, because the condition on line 529 was never false
530 if len(missing) == 0:
531 prompt = "Accept, "
532 answer = 'A'
533 else:
534 prompt = "Add overrides, "
535 if broken: 535 ↛ 536line 535 didn't jump to line 536, because the condition on line 535 was never true
536 print("W: [!] marked entries must be fixed before package can be processed.")
537 if note: 537 ↛ 538line 537 didn't jump to line 538, because the condition on line 537 was never true
538 print("W: note must be removed before package can be processed.")
539 prompt += "RemOve all notes, Remove note, "
541 prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
543 while prompt.find(answer) == -1:
544 answer = utils.input_or_exit(prompt)
545 m = re_default_answer.search(prompt)
546 if answer == "": 546 ↛ 547line 546 didn't jump to line 547, because the condition on line 546 was never true
547 answer = m.group(1)
548 answer = answer[:1].upper()
550 if answer in ('A', 'E', 'M', 'O', 'R') and Options["Trainee"]: 550 ↛ 551line 550 didn't jump to line 551, because the condition on line 550 was never true
551 utils.warn("Trainees can't do that")
552 continue
554 if answer == 'A' and not Options["Trainee"]:
555 handler.add_overrides(missing, upload.target_suite)
556 if Config().find_b("Dinstall::BXANotify"): 556 ↛ 558line 556 didn't jump to line 558, because the condition on line 556 was never false
557 do_bxa_notification(missing, upload, session)
558 handler.accept()
559 done = True
560 Logger.log(["NEW ACCEPT", upload.changes.changesname])
561 elif answer == 'C':
562 check_pkg(upload, upload_copy, session)
563 elif answer == 'E' and not Options["Trainee"]: 563 ↛ 564line 563 didn't jump to line 564, because the condition on line 563 was never true
564 missing = edit_overrides(missing, upload, session)
565 elif answer == 'M' and not Options["Trainee"]:
566 reason = Options.get('Manual-Reject', '') + "\n"
567 reason = reason + "\n\n=====\n\n".join([n.comment for n in get_new_comments(upload.policy_queue, upload.changes.source, session=session)])
568 reason = get_reject_reason(reason)
569 if reason is not None: 569 ↛ 603line 569 didn't jump to line 603, because the condition on line 569 was never false
570 Logger.log(["NEW REJECT", upload.changes.changesname])
571 handler.reject(reason)
572 done = True
573 elif answer == 'N': 573 ↛ 574line 573 didn't jump to line 574, because the condition on line 573 was never true
574 if edit_note(get_new_comments(upload.policy_queue, upload.changes.source, session=session),
575 upload, session, bool(Options["Trainee"])) == 0:
576 end()
577 sys.exit(0)
578 elif answer == 'P' and not Options["Trainee"]: 578 ↛ 579line 578 didn't jump to line 579, because the condition on line 578 was never true
579 if prod_maintainer(get_new_comments(upload.policy_queue, upload.changes.source, session=session),
580 upload, session, bool(Options["Trainee"])) == 0:
581 end()
582 sys.exit(0)
583 Logger.log(["NEW PROD", upload.changes.changesname])
584 elif answer == 'R' and not Options["Trainee"]: 584 ↛ 585line 584 didn't jump to line 585, because the condition on line 584 was never true
585 confirm = utils.input_or_exit("Really clear note (y/N)? ").lower()
586 if confirm == "y":
587 for c in get_new_comments(upload.policy_queue, upload.changes.source, upload.changes.version, session=session):
588 session.delete(c)
589 session.commit()
590 elif answer == 'O' and not Options["Trainee"]: 590 ↛ 591line 590 didn't jump to line 591, because the condition on line 590 was never true
591 confirm = utils.input_or_exit("Really clear all notes (y/N)? ").lower()
592 if confirm == "y":
593 for c in get_new_comments(upload.policy_queue, upload.changes.source, session=session):
594 session.delete(c)
595 session.commit()
597 elif answer == 'S': 597 ↛ 599line 597 didn't jump to line 599, because the condition on line 597 was never false
598 done = True
599 elif answer == 'Q':
600 end()
601 sys.exit(0)
603 if handler.get_action():
604 print("PENDING %s\n" % handler.get_action())
606################################################################################
607################################################################################
608################################################################################
611def usage(exit_code: int = 0) -> NoReturn:
612 print("""Usage: dak process-new [OPTION]... [CHANGES]...
613 -a, --automatic automatic run
614 -b, --no-binaries do not sort binary-NEW packages first
615 -c, --comments show NEW comments
616 -h, --help show this help and exit.
617 -m, --manual-reject=MSG manual reject with `msg'
618 -n, --no-action don't do anything
619 -q, --queue=QUEUE operate on a different queue
620 -t, --trainee FTP Trainee mode
621 -V, --version display the version number and exit
623ENVIRONMENT VARIABLES
625 DAK_INSPECT_UPLOAD: shell command to run to inspect a package
626 The command is automatically run in a shell when an upload
627 is checked. The following substitutions are available:
629 {directory}: directory the upload is contained in
630 {dsc}: name of the included dsc or the empty string
631 {changes}: name of the changes file
633 Note that Python's 'format' method is used to format the command.
635 Example: run mc in a tmux session to inspect the upload
637 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"'
639 and run
641 tmux attach -t process-new
643 in a separate terminal session.
644""")
645 sys.exit(exit_code)
647################################################################################
650@contextlib.contextmanager
651def lock_package(package: str):
652 """
653 Lock `package` so that noone else jumps in processing it.
655 :param package: source package name to lock
656 """
658 cnf = Config()
660 path = os.path.join(cnf.get("Process-New::LockDir", cnf['Dir::Lock']), package)
662 try:
663 fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDONLY, 0o644)
664 except OSError as e:
665 if e.errno == errno.EEXIST or e.errno == errno.EACCES:
666 try:
667 user = pwd.getpwuid(os.stat(path)[stat.ST_UID])[4].split(',')[0].replace('.', '')
668 except KeyError:
669 user = "TotallyUnknown"
670 raise AlreadyLockedError(user)
671 raise
673 try:
674 yield fd
675 finally:
676 os.unlink(path)
679def do_pkg(upload: daklib.dbconn.PolicyQueueUpload, session):
680 # Try to get an included dsc
681 dsc = upload.source
683 cnf = Config()
684 group = cnf.get('Dinstall::UnprivGroup') or None
686 try:
687 with lock_package(upload.changes.source), \
688 UploadCopy(upload, group=group) as upload_copy:
689 handler = PolicyQueueUploadHandler(upload, session)
690 if handler.get_action() is not None:
691 print("PENDING %s\n" % handler.get_action())
692 return
694 do_new(upload, upload_copy, handler, session)
695 except AlreadyLockedError as e:
696 print("Seems to be locked by %s already, skipping..." % (e))
699def show_new_comments(uploads: Iterable[daklib.dbconn.PolicyQueueUpload], session):
700 sources = [upload.changes.source for upload in uploads]
701 if len(sources) == 0:
702 return
704 query = """SELECT package, version, comment, author
705 FROM new_comments
706 WHERE package IN :sources
707 ORDER BY package, version"""
709 r = session.execute(query, params=dict(sources=tuple(sources)))
711 for i in r:
712 print("%s_%s\n%s\n(%s)\n\n\n" % (i[0], i[1], i[2], i[3]))
714 session.rollback()
716################################################################################
719def sort_uploads(
720 new_queue: PolicyQueue,
721 uploads: Iterable[daklib.dbconn.PolicyQueueUpload],
722 session,
723 nobinaries: bool = False
724) -> list[daklib.dbconn.PolicyQueueUpload]:
725 sources = defaultdict(list)
726 sorteduploads = []
727 suitesrc = [s.source for s in session.query(DBSource.source).
728 filter(DBSource.suites.any(Suite.suite_name.in_(['unstable', 'experimental'])))]
729 comments = [p.package for p in session.query(NewComment.package).
730 filter_by(trainee=False, policy_queue=new_queue).distinct()]
731 for upload in uploads:
732 source = upload.changes.source
733 sources[source].append({'upload': upload,
734 'date': upload.changes.created,
735 'stack': 1,
736 'binary': True if source in suitesrc else False,
737 'comments': True if source in comments else False})
738 for src in sources:
739 if len(sources[src]) > 1:
740 changes = sources[src]
741 firstseen = sorted(changes, key=lambda k: (k['date']))[0]['date']
742 changes.sort(key=lambda item: item['date'])
743 for i in range(0, len(changes)):
744 changes[i]['date'] = firstseen
745 changes[i]['stack'] = i + 1
746 sorteduploads += sources[src]
747 if nobinaries: 747 ↛ 748line 747 didn't jump to line 748, because the condition on line 747 was never true
748 sorteduploads = [u["upload"] for u in sorted(sorteduploads,
749 key=lambda k: (k["comments"], k["binary"],
750 k["date"], -k["stack"]))]
751 else:
752 sorteduploads = [u["upload"] for u in sorted(sorteduploads,
753 key=lambda k: (k["comments"], -k["binary"],
754 k["date"], -k["stack"]))]
755 return sorteduploads
757################################################################################
760def end():
761 accept_count = SummaryStats().accept_count
762 accept_bytes = SummaryStats().accept_bytes
764 if accept_count: 764 ↛ 765line 764 didn't jump to line 765, because the condition on line 764 was never true
765 sets = "set"
766 if accept_count > 1:
767 sets = "sets"
768 print("Accepted %d package %s, %s." % (accept_count, sets, utils.size_type(int(accept_bytes))), file=sys.stderr)
769 Logger.log(["total", accept_count, accept_bytes])
771 if not Options["No-Action"] and not Options["Trainee"]: 771 ↛ exitline 771 didn't return from function 'end', because the condition on line 771 was never false
772 Logger.close()
774################################################################################
777def main():
778 global Options, Logger, Sections, Priorities
780 cnf = Config()
781 session = DBConn().session()
783 Arguments = [('a', "automatic", "Process-New::Options::Automatic"),
784 ('b', "no-binaries", "Process-New::Options::No-Binaries"),
785 ('c', "comments", "Process-New::Options::Comments"),
786 ('h', "help", "Process-New::Options::Help"),
787 ('m', "manual-reject", "Process-New::Options::Manual-Reject", "HasArg"),
788 ('t', "trainee", "Process-New::Options::Trainee"),
789 ('q', 'queue', 'Process-New::Options::Queue', 'HasArg'),
790 ('n', "no-action", "Process-New::Options::No-Action")]
792 changes_files = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
794 for i in ["automatic", "no-binaries", "comments", "help", "manual-reject", "no-action", "version", "trainee"]:
795 key = "Process-New::Options::%s" % i
796 if key not in cnf:
797 cnf[key] = ""
799 queue_name = cnf.get('Process-New::Options::Queue', 'new')
800 new_queue = session.query(PolicyQueue).filter_by(queue_name=queue_name).one()
801 if len(changes_files) == 0:
802 uploads = new_queue.uploads
803 else:
804 uploads = session.query(PolicyQueueUpload).filter_by(policy_queue=new_queue) \
805 .join(DBChange).filter(DBChange.changesname.in_(changes_files)).all()
807 Options = cnf.subtree("Process-New::Options")
809 if Options["Help"]:
810 usage()
812 if not Options["No-Action"]: 812 ↛ 818line 812 didn't jump to line 818, because the condition on line 812 was never false
813 try:
814 Logger = daklog.Logger("process-new")
815 except OSError:
816 Options["Trainee"] = "True"
818 Sections = Section_Completer(session)
819 Priorities = Priority_Completer(session)
820 readline.parse_and_bind("tab: complete")
822 if len(uploads) > 1:
823 print("Sorting changes...", file=sys.stderr)
824 uploads = sort_uploads(new_queue, uploads, session, Options["No-Binaries"])
826 if Options["Comments"]: 826 ↛ 827line 826 didn't jump to line 827, because the condition on line 826 was never true
827 show_new_comments(uploads, session)
828 else:
829 for upload in uploads:
830 do_pkg(upload, session)
832 end()
834################################################################################
837if __name__ == '__main__':
838 main()