Coverage for dak/process_policy.py: 81%
331 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-08-03 16:46 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-08-03 16:46 +0000
1"""Handles packages from policy queues
3@contact: Debian FTP Master <ftpmaster@debian.org>
4@copyright: 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org>
5@copyright: 2009 Joerg Jaspert <joerg@debian.org>
6@copyright: 2009 Frank Lichtenheld <djpig@debian.org>
7@copyright: 2009 Mark Hymers <mhy@debian.org>
8@license: GNU General Public License version 2 or later
9"""
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25################################################################################
27# <mhy> So how do we handle that at the moment?
28# <stew> Probably incorrectly.
30################################################################################
32import datetime
33import functools
34import os
35import re
36import sys
37import traceback
38from collections.abc import Callable
39from typing import NoReturn
41import apt_pkg
42from sqlalchemy import select, sql
44import daklib.announce
45import daklib.upload
46import daklib.utils
47from daklib import daklog, utils
48from daklib.archive import ArchiveTransaction, source_component_from_package_list
49from daklib.config import Config
50from daklib.dbconn import (
51 ArchiveFile,
52 Component,
53 DBBinary,
54 DBChange,
55 DBConn,
56 DBSource,
57 Override,
58 OverrideType,
59 PolicyQueue,
60 PolicyQueueUpload,
61 PoolFile,
62 get_mapped_component,
63 get_suite_by_name,
64)
65from daklib.externalsignature import check_upload_for_external_signature_request
66from daklib.packagelist import PackageList
67from daklib.urgencylog import UrgencyLog
69# Globals
70Options: apt_pkg.Configuration
71Logger: daklog.Logger
73################################################################################
75ProcessingCallable = Callable[
76 [PolicyQueueUpload, PolicyQueue, str, ArchiveTransaction], None
77]
80def do_comments(
81 dir: str,
82 srcqueue: PolicyQueue,
83 opref: str,
84 npref: str,
85 line: str,
86 fn: ProcessingCallable,
87 transaction: ArchiveTransaction,
88) -> None:
89 session = transaction.session
90 actions: list[tuple[PolicyQueueUpload, str]] = []
91 for comm in [x for x in os.listdir(dir) if x.startswith(opref)]:
92 with open(os.path.join(dir, comm)) as fd:
93 lines = fd.readlines()
94 if len(lines) == 0 or lines[0] != line + "\n": 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 continue
97 # If the ACCEPT includes a _<arch> we only accept that .changes.
98 # Otherwise we accept all .changes that start with the given prefix
99 changes_prefix = comm[len(opref) :]
100 if changes_prefix.count("_") < 2:
101 changes_prefix = changes_prefix + "_"
102 else:
103 changes_prefix = changes_prefix + ".changes"
105 # We need to escape "_" as we use it with the LIKE operator (via the
106 # SQLA startwith) later.
107 changes_prefix = changes_prefix.replace("_", r"\_")
109 uploads = session.scalars(
110 select(PolicyQueueUpload)
111 .filter_by(policy_queue=srcqueue)
112 .join(PolicyQueueUpload.changes)
113 .where(DBChange.changesname.startswith(changes_prefix))
114 .order_by(PolicyQueueUpload.source_id)
115 )
116 reason = "".join(lines[1:])
117 actions.extend((u, reason) for u in uploads)
119 if opref != npref:
120 newcomm = npref + comm[len(opref) :]
121 newcomm = utils.find_next_free(os.path.join(dir, newcomm))
122 transaction.fs.move(os.path.join(dir, comm), newcomm)
124 actions.sort()
126 for u, reason in actions:
127 print("Processing changes file: {0}".format(u.changes.changesname))
128 fn(u, srcqueue, reason, transaction)
131################################################################################
134def try_or_reject(function: ProcessingCallable) -> ProcessingCallable:
135 @functools.wraps(function)
136 def wrapper(
137 upload: PolicyQueueUpload,
138 srcqueue: PolicyQueue,
139 comments: str,
140 transaction: ArchiveTransaction,
141 ) -> None:
142 try:
143 function(upload, srcqueue, comments, transaction)
144 except Exception:
145 comments = "An exception was raised while processing the package:\n{0}\nOriginal comments:\n{1}".format(
146 traceback.format_exc(), comments
147 )
148 try:
149 transaction.rollback()
150 real_comment_reject(upload, srcqueue, comments, transaction)
151 except Exception:
152 comments = "In addition an exception was raised while trying to reject the upload:\n{0}\nOriginal rejection:\n{1}".format(
153 traceback.format_exc(), comments
154 )
155 transaction.rollback()
156 real_comment_reject(
157 upload, srcqueue, comments, transaction, notify=False
158 )
159 if not Options["No-Action"]: 159 ↛ 162line 159 didn't jump to line 162 because the condition on line 159 was always true
160 transaction.commit()
161 else:
162 transaction.rollback()
164 return wrapper
167################################################################################
170@try_or_reject
171def comment_accept(
172 upload: PolicyQueueUpload,
173 srcqueue: PolicyQueue,
174 comments: str,
175 transaction: ArchiveTransaction,
176) -> None:
177 for byhand in upload.byhand: 177 ↛ 178line 177 didn't jump to line 178 because the loop on line 177 never started
178 path = os.path.join(srcqueue.path, byhand.filename)
179 if os.path.exists(path):
180 raise Exception(
181 "E: cannot ACCEPT upload with unprocessed byhand file {0}".format(
182 byhand.filename
183 )
184 )
186 cnf = Config()
188 fs = transaction.fs
189 session = transaction.session
190 changesname = upload.changes.changesname
191 allow_tainted = srcqueue.suite.archive.tainted
193 # We need overrides to get the target component
194 overridesuite = upload.target_suite
195 if overridesuite.overridesuite is not None:
196 overridesuite = get_suite_by_name(overridesuite.overridesuite, session)
198 def binary_component_func(db_binary: DBBinary) -> Component:
199 section = db_binary.proxy["Section"]
200 component_name = "main"
201 if section.find("/") != -1:
202 component_name = section.split("/", 1)[0]
203 component = get_mapped_component(component_name, session=session)
204 assert component is not None
205 return component
207 def is_debug_binary(db_binary: DBBinary) -> bool:
208 return daklib.utils.is_in_debug_section(db_binary.proxy)
210 def has_debug_binaries(upload: PolicyQueueUpload) -> bool:
211 return any(is_debug_binary(x) for x in upload.binaries)
213 def source_component_func(db_source: DBSource) -> Component:
214 package_list = PackageList(db_source.proxy)
215 component = source_component_from_package_list(
216 package_list, upload.target_suite
217 )
218 if component is not None: 218 ↛ 224line 218 didn't jump to line 224
219 component = get_mapped_component(component.component_name, session=session)
220 assert component is not None
221 return component
223 # Fallback for packages without Package-List field
224 query = (
225 select(Override)
226 .filter_by(suite=overridesuite, package=db_source.source)
227 .join(OverrideType)
228 .where(OverrideType.overridetype == "dsc")
229 .join(Component)
230 )
231 return session.execute(query).scalar_one().component
233 policy_queue = upload.target_suite.policy_queue
234 if policy_queue == srcqueue:
235 policy_queue = None
237 all_target_suites = [
238 upload.target_suite if policy_queue is None else policy_queue.suite
239 ]
240 if policy_queue is None or policy_queue.send_to_build_queues: 240 ↛ 243line 240 didn't jump to line 243 because the condition on line 240 was always true
241 all_target_suites.extend([q.suite for q in upload.target_suite.copy_queues])
243 throw_away_binaries = False
244 if upload.source is not None:
245 source_component = source_component_func(upload.source)
246 if upload.target_suite.suite_name in cnf.value_list(
247 "Dinstall::ThrowAwayNewBinarySuites"
248 ) and source_component.component_name in cnf.value_list(
249 "Dinstall::ThrowAwayNewBinaryComponents"
250 ):
251 throw_away_binaries = True
253 for suite in all_target_suites:
254 debug_suite = suite.debug_suite
256 if upload.source is not None:
257 # If we have Source in this upload, let's include it into
258 # upload suite.
259 transaction.copy_source(
260 upload.source,
261 suite,
262 source_component,
263 allow_tainted=allow_tainted,
264 )
266 if (
267 not throw_away_binaries
268 and debug_suite is not None
269 and has_debug_binaries(upload)
270 ):
271 # If we're handing a debug package, we also need to include the
272 # source in the debug suite as well.
273 transaction.copy_source(
274 upload.source,
275 debug_suite,
276 source_component_func(upload.source),
277 allow_tainted=allow_tainted,
278 )
280 if not throw_away_binaries:
281 for db_binary in upload.binaries:
282 # Now, let's work out where to copy this guy to -- if it's
283 # a debug binary, and the suite has a debug suite, let's go
284 # ahead and target the debug suite rather then the stock
285 # suite.
286 copy_to_suite = suite
287 if debug_suite is not None and is_debug_binary(db_binary):
288 copy_to_suite = debug_suite
290 # build queues and debug suites may miss the source package
291 # if this is a binary-only upload.
292 if copy_to_suite != upload.target_suite:
293 transaction.copy_source(
294 db_binary.source,
295 copy_to_suite,
296 source_component_func(db_binary.source),
297 allow_tainted=allow_tainted,
298 )
300 transaction.copy_binary(
301 db_binary,
302 copy_to_suite,
303 binary_component_func(db_binary),
304 allow_tainted=allow_tainted,
305 extra_archives=[upload.target_suite.archive],
306 )
308 check_upload_for_external_signature_request(
309 session, suite, copy_to_suite, db_binary
310 )
312 suite.update_last_changed()
314 # Copy .changes if needed
315 if policy_queue is None and upload.target_suite.copychanges: 315 ↛ 316line 315 didn't jump to line 316 because the condition on line 315 was never true
316 src = os.path.join(upload.policy_queue.path, upload.changes.changesname)
317 dst = os.path.join(upload.target_suite.path, upload.changes.changesname)
318 fs.copy(src, dst, mode=upload.target_suite.archive.mode)
320 # List of files in the queue directory
321 queue_files = [changesname]
322 chg = daklib.upload.Changes(
323 upload.policy_queue.path, changesname, keyrings=[], require_signature=False
324 )
325 queue_files.extend(f.filename for f in chg.buildinfo_files)
327 # TODO: similar code exists in archive.py's `ArchiveUpload._install_policy`
328 if policy_queue is not None:
329 # register upload in policy queue
330 new_upload = PolicyQueueUpload()
331 new_upload.policy_queue = policy_queue
332 new_upload.target_suite = upload.target_suite
333 new_upload.changes = upload.changes
334 new_upload.source = upload.source
335 new_upload.binaries = upload.binaries
336 session.add(new_upload)
337 session.flush()
339 # copy .changes & similar to policy queue
340 for fn in queue_files:
341 src = os.path.join(upload.policy_queue.path, fn)
342 dst = os.path.join(policy_queue.path, fn)
343 transaction.fs.copy(src, dst, mode=policy_queue.change_perms)
345 # Copy upload to Process-Policy::CopyDir
346 # Used on security.d.o to sync accepted packages to ftp-master, but this
347 # should eventually be replaced by something else.
348 copydir = cnf.get("Process-Policy::CopyDir") or None
349 if policy_queue is None and copydir is not None: 349 ↛ 350line 349 didn't jump to line 350 because the condition on line 349 was never true
350 mode = upload.target_suite.archive.mode
351 if upload.source is not None:
352 for f in [df.poolfile for df in upload.source.srcfiles]:
353 dst = os.path.join(copydir, f.basename)
354 if not os.path.exists(dst):
355 fs.copy(f.fullpath, dst, mode=mode)
357 for db_binary in upload.binaries:
358 f = db_binary.poolfile
359 dst = os.path.join(copydir, f.basename)
360 if not os.path.exists(dst):
361 fs.copy(f.fullpath, dst, mode=mode)
363 for fn in queue_files:
364 src = os.path.join(upload.policy_queue.path, fn)
365 dst = os.path.join(copydir, fn)
366 # We check for `src` to exist as old uploads in policy queues
367 # might still miss the `.buildinfo` files.
368 if os.path.exists(src) and not os.path.exists(dst):
369 fs.copy(src, dst, mode=mode)
371 if policy_queue is None:
372 utils.process_buildinfos(
373 upload.policy_queue.path, chg.buildinfo_files, fs, Logger
374 )
376 if policy_queue is None and upload.source is not None and not Options["No-Action"]:
377 urgency = upload.changes.urgency
378 # As per policy 5.6.17, the urgency can be followed by a space and a
379 # comment. Extract only the urgency from the string.
380 if " " in urgency: 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true
381 urgency, comment = urgency.split(" ", 1)
382 if urgency not in cnf.value_list("Urgency::Valid"): 382 ↛ 383line 382 didn't jump to line 383 because the condition on line 382 was never true
383 urgency = cnf["Urgency::Default"]
384 UrgencyLog().log(upload.source.source, upload.source.version, urgency)
386 if policy_queue is None:
387 print(" ACCEPT")
388 else:
389 print(" ACCEPT-TO-QUEUE")
390 if not Options["No-Action"]: 390 ↛ 393line 390 didn't jump to line 393 because the condition on line 390 was always true
391 Logger.log(["Policy Queue ACCEPT", srcqueue.queue_name, changesname])
393 if policy_queue is None:
394 pu = get_processed_upload(upload)
395 daklib.announce.announce_accept(pu)
397 # TODO: code duplication. Similar code is in process-upload.
398 # Move .changes to done
399 now = datetime.datetime.now()
400 donedir = os.path.join(cnf["Dir::Done"], now.strftime("%Y/%m/%d"))
401 if policy_queue is None:
402 for fn in queue_files:
403 src = os.path.join(upload.policy_queue.path, fn)
404 if os.path.exists(src): 404 ↛ 402line 404 didn't jump to line 402 because the condition on line 404 was always true
405 dst = os.path.join(donedir, fn)
406 dst = utils.find_next_free(dst)
407 fs.copy(src, dst, mode=0o644)
409 if throw_away_binaries and upload.target_suite.archive.use_morgue:
410 morguesubdir = cnf.get("New::MorgueSubDir", "new")
412 utils.move_to_morgue(
413 morguesubdir,
414 [db_binary.poolfile.fullpath for db_binary in upload.binaries],
415 fs,
416 Logger,
417 )
419 remove_upload(upload, transaction)
422################################################################################
425@try_or_reject
426def comment_reject(*args) -> None:
427 real_comment_reject(*args, manual=True)
430def real_comment_reject(
431 upload: PolicyQueueUpload,
432 srcqueue: PolicyQueue,
433 comments: str,
434 transaction: ArchiveTransaction,
435 notify=True,
436 *,
437 manual=False,
438) -> None:
439 cnf = Config()
441 fs = transaction.fs
442 session = transaction.session
443 changesname = upload.changes.changesname
444 queuedir = upload.policy_queue.path
445 rejectdir = cnf["Dir::Reject"]
447 ### Copy files to reject/
449 poolfiles = [b.poolfile for b in upload.binaries]
450 if upload.source is not None: 450 ↛ 453line 450 didn't jump to line 453 because the condition on line 450 was always true
451 poolfiles.extend([df.poolfile for df in upload.source.srcfiles])
452 # Not beautiful...
453 files = [
454 af.path
455 for af in session.scalars(
456 select(ArchiveFile)
457 .filter_by(archive=upload.policy_queue.suite.archive)
458 .join(ArchiveFile.file)
459 .where(PoolFile.file_id.in_([f.file_id for f in poolfiles]))
460 )
461 ]
462 for byhand in upload.byhand: 462 ↛ 463line 462 didn't jump to line 463 because the loop on line 462 never started
463 path = os.path.join(queuedir, byhand.filename)
464 if os.path.exists(path):
465 files.append(path)
466 chg = daklib.upload.Changes(
467 queuedir, changesname, keyrings=[], require_signature=False
468 )
469 for f in chg.buildinfo_files:
470 path = os.path.join(queuedir, f.filename)
471 if os.path.exists(path): 471 ↛ 469line 471 didn't jump to line 469 because the condition on line 471 was always true
472 files.append(path)
473 files.append(os.path.join(queuedir, changesname))
475 for fn in files:
476 dst = utils.find_next_free(os.path.join(rejectdir, os.path.basename(fn)))
477 fs.copy(fn, dst, link=True)
479 ### Write reason
481 dst = utils.find_next_free(
482 os.path.join(rejectdir, "{0}.reason".format(changesname))
483 )
484 fh = fs.create(dst)
485 fh.write(comments)
486 fh.close()
488 ### Send mail notification
490 if notify: 490 ↛ 504line 490 didn't jump to line 504 because the condition on line 490 was always true
491 rejected_by = None
492 reason = comments
494 # Try to use From: from comment file if there is one.
495 # This is not very elegant...
496 match = re.match(r"\AFrom: ([^\n]+)\n\n", comments)
497 if match: 497 ↛ 501line 497 didn't jump to line 501 because the condition on line 497 was always true
498 rejected_by = match.group(1)
499 reason = "\n".join(comments.splitlines()[2:])
501 pu = get_processed_upload(upload)
502 daklib.announce.announce_reject(pu, reason, rejected_by)
504 print(" REJECT")
505 if not Options["No-Action"]: 505 ↛ 510line 505 didn't jump to line 510 because the condition on line 505 was always true
506 Logger.log(
507 ["Policy Queue REJECT", srcqueue.queue_name, upload.changes.changesname]
508 )
510 changes = upload.changes
511 remove_upload(upload, transaction)
512 session.delete(changes)
515################################################################################
518def remove_upload(upload: PolicyQueueUpload, transaction: ArchiveTransaction) -> None:
519 fs = transaction.fs
520 session = transaction.session
522 # Remove byhand and changes files. Binary and source packages will be
523 # removed from {bin,src}_associations and eventually removed by clean-suites automatically.
524 queuedir = upload.policy_queue.path
525 for byhand in upload.byhand: 525 ↛ 526line 525 didn't jump to line 526 because the loop on line 525 never started
526 path = os.path.join(queuedir, byhand.filename)
527 if os.path.exists(path):
528 fs.unlink(path)
529 session.delete(byhand)
531 chg = daklib.upload.Changes(
532 queuedir, upload.changes.changesname, keyrings=[], require_signature=False
533 )
534 queue_files = [upload.changes.changesname]
535 queue_files.extend(f.filename for f in chg.buildinfo_files)
536 for fn in queue_files:
537 # We check for `path` to exist as old uploads in policy queues
538 # might still miss the `.buildinfo` files.
539 path = os.path.join(queuedir, fn)
540 if os.path.exists(path): 540 ↛ 536line 540 didn't jump to line 536 because the condition on line 540 was always true
541 fs.unlink(path)
543 session.delete(upload)
544 session.flush()
547################################################################################
550def get_processed_upload(upload: PolicyQueueUpload) -> daklib.announce.ProcessedUpload:
551 pu = daklib.announce.ProcessedUpload()
553 pu.maintainer = upload.changes.maintainer
554 pu.changed_by = upload.changes.changedby
555 pu.fingerprint = upload.changes.fingerprint
556 pu.authorized_by_fingerprint = upload.changes.authorized_by_fingerprint
558 pu.suites = [upload.target_suite]
559 pu.from_policy_suites = [upload.target_suite]
561 changes_path = os.path.join(upload.policy_queue.path, upload.changes.changesname)
562 with open(changes_path, "r") as fd:
563 pu.changes = fd.read()
564 pu.changes_filename = upload.changes.changesname
565 pu.sourceful = upload.source is not None
566 pu.source = upload.changes.source
567 pu.version = upload.changes.version
568 pu.architecture = upload.changes.architecture
569 pu.bugs = upload.changes.closes
571 pu.program = "process-policy"
573 return pu
576################################################################################
579def remove_unreferenced_binaries(
580 policy_queue: PolicyQueue, transaction: ArchiveTransaction
581) -> None:
582 """Remove binaries that are no longer referenced by an upload"""
583 session = transaction.session
584 suite = policy_queue.suite
586 query = sql.text(
587 """
588 SELECT b.*
589 FROM binaries b
590 JOIN bin_associations ba ON b.id = ba.bin
591 WHERE ba.suite = :suite_id
592 AND NOT EXISTS (SELECT 1 FROM policy_queue_upload_binaries_map pqubm
593 JOIN policy_queue_upload pqu ON pqubm.policy_queue_upload_id = pqu.id
594 WHERE pqu.policy_queue_id = :policy_queue_id
595 AND pqubm.binary_id = b.id)"""
596 )
597 binaries = session.scalars(
598 select(DBBinary).from_statement(query),
599 {
600 "suite_id": policy_queue.suite_id,
601 "policy_queue_id": policy_queue.policy_queue_id,
602 },
603 )
605 for binary in binaries:
606 Logger.log(
607 [
608 "removed binary from policy queue",
609 policy_queue.queue_name,
610 binary.package,
611 binary.version,
612 ]
613 )
614 transaction.remove_binary(binary, suite)
617def remove_unreferenced_sources(
618 policy_queue: PolicyQueue, transaction: ArchiveTransaction
619) -> None:
620 """Remove sources that are no longer referenced by an upload or a binary"""
621 session = transaction.session
622 suite = policy_queue.suite
624 query = sql.text(
625 """
626 SELECT s.*
627 FROM source s
628 JOIN src_associations sa ON s.id = sa.source
629 WHERE sa.suite = :suite_id
630 AND NOT EXISTS (SELECT 1 FROM policy_queue_upload pqu
631 WHERE pqu.policy_queue_id = :policy_queue_id
632 AND pqu.source_id = s.id)
633 AND NOT EXISTS (SELECT 1 FROM binaries b
634 JOIN bin_associations ba ON b.id = ba.bin
635 WHERE b.source = s.id
636 AND ba.suite = :suite_id)"""
637 )
638 sources = session.scalars(
639 select(DBSource).from_statement(query),
640 {
641 "suite_id": policy_queue.suite_id,
642 "policy_queue_id": policy_queue.policy_queue_id,
643 },
644 )
646 for source in sources:
647 Logger.log(
648 [
649 "removed source from policy queue",
650 policy_queue.queue_name,
651 source.source,
652 source.version,
653 ]
654 )
655 transaction.remove_source(source, suite)
658################################################################################
661def usage(status=0) -> NoReturn:
662 print("""Usage: dak process-policy QUEUE""")
663 sys.exit(status)
666################################################################################
669def main() -> None:
670 global Options, Logger
672 cnf = Config()
673 session = DBConn().session()
675 Arguments = [
676 ("h", "help", "Process-Policy::Options::Help"),
677 ("n", "no-action", "Process-Policy::Options::No-Action"),
678 ]
680 for i in ["help", "no-action"]:
681 key = "Process-Policy::Options::%s" % i
682 if key not in cnf: 682 ↛ 680line 682 didn't jump to line 680 because the condition on line 682 was always true
683 cnf[key] = ""
685 queue_name = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
687 Options = cnf.subtree("Process-Policy::Options")
688 if Options["Help"]:
689 usage()
691 if len(queue_name) != 1: 691 ↛ 692line 691 didn't jump to line 692 because the condition on line 691 was never true
692 print("E: Specify exactly one policy queue")
693 sys.exit(1)
695 queue_name = queue_name[0]
697 Logger = daklog.Logger("process-policy")
698 if not Options["No-Action"]: 698 ↛ 701line 698 didn't jump to line 701 because the condition on line 698 was always true
699 urgencylog = UrgencyLog()
701 with ArchiveTransaction() as transaction:
702 session = transaction.session
703 pq = session.execute(
704 select(PolicyQueue).filter_by(queue_name=queue_name)
705 ).scalar_one_or_none()
706 if pq is None: 706 ↛ 707line 706 didn't jump to line 707 because the condition on line 706 was never true
707 print("E: Cannot find policy queue %s" % queue_name)
708 sys.exit(1)
710 commentsdir = os.path.join(pq.path, "COMMENTS")
711 # The comments stuff relies on being in the right directory
712 os.chdir(pq.path)
714 do_comments(
715 commentsdir,
716 pq,
717 "REJECT.",
718 "REJECTED.",
719 "NOTOK",
720 comment_reject,
721 transaction,
722 )
723 do_comments(
724 commentsdir, pq, "ACCEPT.", "ACCEPTED.", "OK", comment_accept, transaction
725 )
726 do_comments(
727 commentsdir, pq, "ACCEPTED.", "ACCEPTED.", "OK", comment_accept, transaction
728 )
730 remove_unreferenced_binaries(pq, transaction)
731 remove_unreferenced_sources(pq, transaction)
733 if not Options["No-Action"]: 733 ↛ exitline 733 didn't return from function 'main' because the condition on line 733 was always true
734 urgencylog.close()