Coverage for dak/process_upload.py: 83%

299 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-01-04 16:18 +0000

1#! /usr/bin/env python3 

2 

3""" 

4Checks Debian packages from Incoming 

5@contact: Debian FTP Master <ftpmaster@debian.org> 

6@copyright: 2000, 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org> 

7@copyright: 2009 Joerg Jaspert <joerg@debian.org> 

8@copyright: 2009 Mark Hymers <mhy@debian.org> 

9@copyright: 2009 Frank Lichtenheld <djpig@debian.org> 

10@license: GNU General Public License version 2 or later 

11""" 

12 

13# This program is free software; you can redistribute it and/or modify 

14# it under the terms of the GNU General Public License as published by 

15# the Free Software Foundation; either version 2 of the License, or 

16# (at your option) any later version. 

17 

18# This program is distributed in the hope that it will be useful, 

19# but WITHOUT ANY WARRANTY; without even the implied warranty of 

20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21# GNU General Public License for more details. 

22 

23# You should have received a copy of the GNU General Public License 

24# along with this program; if not, write to the Free Software 

25# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

26 

27# based on process-unchecked and process-accepted 

28 

29## pu|pa: locking (daily.lock) 

30## pu|pa: parse arguments -> list of changes files 

31## pa: initialize urgency log 

32## pu|pa: sort changes list 

33 

34## foreach changes: 

35### pa: load dak file 

36## pu: copy CHG to tempdir 

37## pu: check CHG signature 

38## pu: parse changes file 

39## pu: checks: 

40## pu: check distribution (mappings, rejects) 

41## pu: copy FILES to tempdir 

42## pu: check whether CHG already exists in CopyChanges 

43## pu: check whether FILES already exist in one of the policy queues 

44## for deb in FILES: 

45## pu: extract control information 

46## pu: various checks on control information 

47## pu|pa: search for source (in CHG, projectb, policy queues) 

48## pu|pa: check whether "Version" fulfills target suite requirements/suite propagation 

49## pu|pa: check whether deb already exists in the pool 

50## for src in FILES: 

51## pu: various checks on filenames and CHG consistency 

52## pu: if isdsc: check signature 

53## for file in FILES: 

54## pu: various checks 

55## pu: NEW? 

56## //pu: check whether file already exists in the pool 

57## pu: store what "Component" the package is currently in 

58## pu: check whether we found everything we were looking for in CHG 

59## pu: check the DSC: 

60## pu: check whether we need and have ONE DSC 

61## pu: parse the DSC 

62## pu: various checks //maybe drop some of the in favor of lintian 

63## pu|pa: check whether "Version" fulfills target suite requirements/suite propagation 

64## pu: check whether DSC_FILES is consistent with "Format" 

65## for src in DSC_FILES: 

66## pu|pa: check whether file already exists in the pool (with special handling for .orig.tar.gz) 

67## pu: create new tempdir 

68## pu: create symlink mirror of source 

69## pu: unpack source 

70## pu: extract changelog information for BTS 

71## //pu: create missing .orig symlink 

72## pu: check with lintian 

73## for file in FILES: 

74## pu: check checksums and sizes 

75## for file in DSC_FILES: 

76## pu: check checksums and sizes 

77## pu: CHG: check urgency 

78## for deb in FILES: 

79## pu: extract contents list and check for dubious timestamps 

80## pu: check that the uploader is actually allowed to upload the package 

81### pa: install: 

82### if stable_install: 

83### pa: remove from p-u 

84### pa: add to stable 

85### pa: move CHG to morgue 

86### pa: append data to ChangeLog 

87### pa: send mail 

88### pa: remove .dak file 

89### else: 

90### pa: add dsc to db: 

91### for file in DSC_FILES: 

92### pa: add file to file 

93### pa: add file to dsc_files 

94### pa: create source entry 

95### pa: update source associations 

96### pa: update src_uploaders 

97### for deb in FILES: 

98### pa: add deb to db: 

99### pa: add file to file 

100### pa: find source entry 

101### pa: create binaries entry 

102### pa: update binary associations 

103### pa: .orig component move 

104### pa: move files to pool 

105### pa: save CHG 

106### pa: move CHG to done/ 

107### pa: change entry in queue_build 

108## pu: use dispatch table to choose target queue: 

109## if NEW: 

110## pu: write .dak file 

111## pu: move to NEW 

112## pu: send mail 

113## elsif AUTOBYHAND: 

114## pu: run autobyhand script 

115## pu: if stuff left, do byhand or accept 

116## elsif targetqueue in (oldstable, stable, embargo, unembargo): 

117## pu: write .dak file 

118## pu: check overrides 

119## pu: move to queue 

120## pu: send mail 

121## else: 

122## pu: write .dak file 

123## pu: move to ACCEPTED 

124## pu: send mails 

125## pu: create files for BTS 

126## pu: create entry in queue_build 

127## pu: check overrides 

128 

129# Integrity checks 

130## GPG 

131## Parsing changes (check for duplicates) 

132## Parse dsc 

133## file list checks 

134 

135# New check layout (TODO: Implement) 

136## Permission checks 

137### suite mappings 

138### ACLs 

139### version checks (suite) 

140### override checks 

141 

142## Source checks 

143### copy orig 

144### unpack 

145### BTS changelog 

146### src contents 

147### lintian 

148### urgency log 

149 

150## Binary checks 

151### timestamps 

152### control checks 

153### src relation check 

154### contents 

155 

156## Database insertion (? copy from stuff) 

157### BYHAND / NEW / Policy queues 

158### Pool 

159 

160## Queue builds 

161 

162import datetime 

163import errno 

164import fcntl 

165import functools 

166import os 

167import sys 

168import time 

169import traceback 

170from collections.abc import Callable, Iterable 

171from typing import Concatenate, NoReturn 

172 

173import apt_pkg 

174 

175import daklib.announce 

176import daklib.archive 

177import daklib.checks 

178import daklib.upload 

179import daklib.utils as utils 

180from daklib import daklog 

181from daklib.config import Config 

182from daklib.dbconn import DBConn, Keyring, SignatureHistory 

183from daklib.regexes import re_default_answer 

184from daklib.summarystats import SummaryStats 

185from daklib.urgencylog import UrgencyLog 

186 

187############################################################################### 

188 

189Options: apt_pkg.Configuration 

190Logger: daklog.Logger 

191 

192############################################################################### 

193 

194 

195def usage(exit_code=0) -> NoReturn: 

196 print( 

197 """Usage: dak process-upload [OPTION]... [CHANGES]... 

198 -a, --automatic automatic run 

199 -d, --directory <DIR> process uploads in <DIR> 

200 -h, --help show this help and exit. 

201 -n, --no-action don't do anything 

202 -p, --no-lock don't check lockfile !! for cron.daily only !! 

203 -s, --no-mail don't send any mail 

204 -V, --version display the version number and exit""" 

205 ) 

206 sys.exit(exit_code) 

207 

208 

209############################################################################### 

210 

211type Handler[**P, R] = Callable[Concatenate[str, daklib.archive.ArchiveUpload, P], R] 

212 

213 

214def try_or_reject[**P, R](function: Handler[P, R]) -> Handler[P, R]: 

215 """Try to call function or reject the upload if that fails""" 

216 

217 @functools.wraps(function) 

218 def wrapper(directory: str, upload: daklib.archive.ArchiveUpload, *args, **kwargs): 

219 reason = "No exception caught. This should not happen." 

220 

221 try: 

222 return function(directory, upload, *args, **kwargs) 

223 except (daklib.archive.ArchiveException, daklib.checks.Reject) as e: 

224 reason = str(e) 

225 except Exception: 

226 reason = "There was an uncaught exception when processing your upload:\n{0}\nAny original reject reason follows below.".format( 

227 traceback.format_exc() 

228 ) 

229 

230 try: 

231 upload.rollback() 

232 return real_reject(directory, upload, reason=reason) 

233 except Exception: 

234 reason = "In addition there was an exception when rejecting the package:\n{0}\nPrevious reasons:\n{1}".format( 

235 traceback.format_exc(), reason 

236 ) 

237 upload.rollback() 

238 return real_reject(directory, upload, reason=reason, notify=False) 

239 

240 raise Exception( 

241 "Rejecting upload failed after multiple tries. Giving up. Last reason:\n{0}".format( 

242 reason 

243 ) 

244 ) 

245 

246 return wrapper 

247 

248 

249def get_processed_upload( 

250 upload: daklib.archive.ArchiveUpload, 

251) -> daklib.announce.ProcessedUpload: 

252 changes = upload.changes 

253 control = upload.changes.changes 

254 

255 pu = daklib.announce.ProcessedUpload() 

256 

257 pu.maintainer = control.get("Maintainer") 

258 pu.changed_by = control.get("Changed-By") 

259 pu.fingerprint = changes.primary_fingerprint 

260 pu.authorized_by_fingerprint = upload.authorized_by_fingerprint.fingerprint 

261 

262 pu.suites = upload.final_suites or [] 

263 pu.from_policy_suites = [] 

264 

265 with open(upload.changes.path, "r") as fd: 

266 pu.changes = fd.read() 

267 pu.changes_filename = upload.changes.filename 

268 pu.sourceful = upload.changes.sourceful 

269 pu.source = control.get("Source") 

270 pu.version = control.get("Version") 

271 pu.architecture = control.get("Architecture") 

272 pu.bugs = changes.closed_bugs 

273 

274 pu.program = "process-upload" 

275 

276 pu.warnings = upload.warnings 

277 

278 return pu 

279 

280 

281@try_or_reject 

282def accept(directory: str, upload: daklib.archive.ArchiveUpload) -> None: 

283 cnf = Config() 

284 

285 Logger.log(["ACCEPT", upload.changes.filename]) 

286 print("ACCEPT") 

287 

288 upload.install() 

289 utils.process_buildinfos( 

290 upload.directory, upload.changes.buildinfo_files, upload.transaction.fs, Logger 

291 ) 

292 

293 assert upload.final_suites is not None 

294 accepted_to_real_suite = any( 

295 suite.policy_queue is None for suite in upload.final_suites 

296 ) 

297 sourceful_upload = upload.changes.sourceful 

298 

299 control = upload.changes.changes 

300 if sourceful_upload and not Options["No-Action"]: 

301 urgency = control.get("Urgency") 

302 # As per policy 5.6.17, the urgency can be followed by a space and a 

303 # comment. Extract only the urgency from the string. 

304 if " " in urgency: 304 ↛ 305line 304 didn't jump to line 305 because the condition on line 304 was never true

305 urgency, comment = urgency.split(" ", 1) 

306 if urgency not in cnf.value_list("Urgency::Valid"): 306 ↛ 307line 306 didn't jump to line 307 because the condition on line 306 was never true

307 urgency = cnf["Urgency::Default"] 

308 UrgencyLog().log(control["Source"], control["Version"], urgency) 

309 

310 pu = get_processed_upload(upload) 

311 daklib.announce.announce_accept(pu) 

312 

313 # Move .changes to done, but only for uploads that were accepted to a 

314 # real suite. process-policy will handle this for uploads to queues. 

315 if accepted_to_real_suite: 

316 src = os.path.join(upload.directory, upload.changes.filename) 

317 

318 now = datetime.datetime.now() 

319 donedir = os.path.join(cnf["Dir::Done"], now.strftime("%Y/%m/%d")) 

320 dst = os.path.join(donedir, upload.changes.filename) 

321 dst = utils.find_next_free(dst) 

322 

323 upload.transaction.fs.copy(src, dst, mode=0o644) 

324 

325 SummaryStats().accept_count += 1 

326 SummaryStats().accept_bytes += upload.changes.bytes 

327 

328 

329@try_or_reject 

330def accept_to_new(directory: str, upload: daklib.archive.ArchiveUpload) -> None: 

331 

332 Logger.log(["ACCEPT-TO-NEW", upload.changes.filename]) 

333 print("ACCEPT-TO-NEW") 

334 

335 upload.install_to_new() 

336 # TODO: tag bugs pending 

337 

338 pu = get_processed_upload(upload) 

339 daklib.announce.announce_new(pu) 

340 

341 SummaryStats().accept_count += 1 

342 SummaryStats().accept_bytes += upload.changes.bytes 

343 

344 

345@try_or_reject 

346def reject( 

347 directory: str, 

348 upload: daklib.archive.ArchiveUpload, 

349 reason: str | None = None, 

350 notify=True, 

351) -> None: 

352 real_reject(directory, upload, reason, notify) 

353 

354 

355def real_reject( 

356 directory: str, 

357 upload: daklib.archive.ArchiveUpload, 

358 reason: str | None = None, 

359 notify=True, 

360) -> None: 

361 # XXX: rejection itself should go to daklib.archive.ArchiveUpload 

362 cnf = Config() 

363 

364 Logger.log(["REJECT", upload.changes.filename]) 

365 print("REJECT") 

366 

367 fs = upload.transaction.fs 

368 rejectdir = cnf["Dir::Reject"] 

369 

370 files = [f.filename for f in upload.changes.files.values()] 

371 files.append(upload.changes.filename) 

372 

373 for fn in files: 

374 src = os.path.join(upload.directory, fn) 

375 dst = utils.find_next_free(os.path.join(rejectdir, fn)) 

376 if not os.path.exists(src): 

377 continue 

378 fs.copy(src, dst) 

379 

380 if upload.reject_reasons is not None: 380 ↛ 385line 380 didn't jump to line 385 because the condition on line 380 was always true

381 if reason is None: 381 ↛ 383line 381 didn't jump to line 383 because the condition on line 381 was always true

382 reason = "" 

383 reason = reason + "\n" + "\n".join(upload.reject_reasons) 

384 

385 if reason is None: 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true

386 reason = "(Unknown reason. Please check logs.)" 

387 

388 dst = utils.find_next_free( 

389 os.path.join(rejectdir, "{0}.reason".format(upload.changes.filename)) 

390 ) 

391 fh = fs.create(dst) 

392 fh.write(reason) 

393 fh.close() 

394 

395 if notify: 395 ↛ 399line 395 didn't jump to line 399 because the condition on line 395 was always true

396 pu = get_processed_upload(upload) 

397 daklib.announce.announce_reject(pu, reason) 

398 

399 SummaryStats().reject_count += 1 

400 

401 

402############################################################################### 

403 

404 

405def action(directory: str, upload: daklib.archive.ArchiveUpload) -> bool: 

406 changes = upload.changes 

407 processed = True 

408 

409 global Logger 

410 

411 cnf = Config() 

412 

413 okay = upload.check() 

414 

415 try: 

416 summary = changes.changes.get("Changes", "") 

417 except UnicodeDecodeError as e: 

418 summary = "Reading changes failed: %s" % (e) 

419 # the upload checks should have detected this, but make sure this 

420 # upload gets rejected in any case 

421 upload.reject_reasons.append(summary) 

422 

423 package_info = [] 

424 if okay: 

425 if changes.source is not None: 

426 package_info.append("source:{0}".format(changes.source.dsc["Source"])) 

427 for binary in changes.binaries: 

428 package_info.append("binary:{0}".format(binary.control["Package"])) 

429 

430 (prompt, answer) = ("", "XXX") 

431 if Options["No-Action"] or Options["Automatic"]: 431 ↛ 434line 431 didn't jump to line 434 because the condition on line 431 was always true

432 answer = "S" 

433 

434 print(summary) 

435 print() 

436 print("\n".join(package_info)) 

437 print() 

438 if len(upload.warnings) > 0: 

439 print("\n".join(upload.warnings)) 

440 print() 

441 

442 if len(upload.reject_reasons) > 0: 

443 print("Reason:") 

444 print("\n".join(upload.reject_reasons)) 

445 print() 

446 

447 path = os.path.join(directory, changes.filename) 

448 created = os.stat(path).st_mtime 

449 now = time.time() 

450 too_new = now - created < int(cnf["Dinstall::SkipTime"]) 

451 

452 if too_new: 

453 print("SKIP (too new)") 

454 prompt = "[S]kip, Quit ?" 

455 else: 

456 prompt = "[R]eject, Skip, Quit ?" 

457 if Options["Automatic"]: 457 ↛ 468line 457 didn't jump to line 468 because the condition on line 457 was always true

458 answer = "R" 

459 elif upload.new: 

460 prompt = "[N]ew, Skip, Quit ?" 

461 if Options["Automatic"]: 461 ↛ 468line 461 didn't jump to line 468 because the condition on line 461 was always true

462 answer = "N" 

463 else: 

464 prompt = "[A]ccept, Skip, Quit ?" 

465 if Options["Automatic"]: 465 ↛ 468line 465 didn't jump to line 468 because the condition on line 465 was always true

466 answer = "A" 

467 

468 while prompt.find(answer) == -1: 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true

469 answer = utils.input_or_exit(prompt) 

470 m = re_default_answer.match(prompt) 

471 if answer == "": 

472 assert m is not None 

473 answer = m.group(1) 

474 answer = answer[:1].upper() 

475 

476 if answer == "R": 

477 reject(directory, upload) 

478 elif answer == "A": 

479 # upload.try_autobyhand must not be run with No-Action. 

480 if Options["No-Action"]: 480 ↛ 481line 480 didn't jump to line 481 because the condition on line 480 was never true

481 accept(directory, upload) 

482 elif upload.try_autobyhand(): 482 ↛ 485line 482 didn't jump to line 485 because the condition on line 482 was always true

483 accept(directory, upload) 

484 else: 

485 print("W: redirecting to BYHAND as automatic processing failed.") 

486 accept_to_new(directory, upload) 

487 elif answer == "N": 

488 accept_to_new(directory, upload) 

489 elif answer == "Q": 489 ↛ 490line 489 didn't jump to line 490 because the condition on line 489 was never true

490 sys.exit(0) 

491 elif answer == "S": 491 ↛ 494line 491 didn't jump to line 494 because the condition on line 491 was always true

492 processed = False 

493 

494 if not Options["No-Action"]: 494 ↛ 497line 494 didn't jump to line 497 because the condition on line 494 was always true

495 upload.commit() 

496 

497 return processed 

498 

499 

500############################################################################### 

501 

502 

503def unlink_if_exists(path: str) -> None: 

504 try: 

505 os.unlink(path) 

506 except OSError as e: 

507 if e.errno != errno.ENOENT: 507 ↛ 508line 507 didn't jump to line 508 because the condition on line 507 was never true

508 raise 

509 

510 

511def process_it( 

512 directory: str, changes: daklib.upload.Changes, keyrings: list[str] 

513) -> None: 

514 global Logger 

515 

516 print("\n{0}\n".format(changes.filename)) 

517 Logger.log(["Processing changes file", changes.filename]) 

518 

519 with daklib.archive.ArchiveUpload(directory, changes, keyrings) as upload: 

520 processed = action(directory, upload) 

521 if processed and not Options["No-Action"]: 

522 session = DBConn().session() 

523 history = SignatureHistory.from_signed_file(upload.changes) 

524 if history.query(session) is None: 524 ↛ 527line 524 didn't jump to line 527 because the condition on line 524 was always true

525 session.add(history) 

526 session.commit() 

527 session.close() 

528 

529 unlink_if_exists(os.path.join(directory, changes.filename)) 

530 for fn in changes.files: 

531 unlink_if_exists(os.path.join(directory, fn)) 

532 

533 

534############################################################################### 

535 

536 

537def process_changes(changes_filenames: Iterable[str]) -> None: 

538 session = DBConn().session() 

539 keyrings = session.query(Keyring).filter_by(active=True).order_by(Keyring.priority) 

540 keyring_files = [k.keyring_name for k in keyrings] 

541 session.close() 

542 

543 changes = [] 

544 for fn in changes_filenames: 

545 try: 

546 directory, filename = os.path.split(fn) 

547 c = daklib.upload.Changes(directory, filename, keyring_files) 

548 changes.append((directory, c)) 

549 except Exception as e: 

550 try: 

551 Logger.log( 

552 [ 

553 filename, 

554 "Error while loading changes file {0}: {1}".format(fn, e), 

555 ] 

556 ) 

557 except Exception as e: 

558 Logger.log( 

559 [ 

560 filename, 

561 "Error while loading changes file {0}, with additional error while printing exception: {1}".format( 

562 fn, repr(e) 

563 ), 

564 ] 

565 ) 

566 

567 changes.sort(key=lambda x: x[1]) 

568 

569 for directory, c in changes: 

570 process_it(directory, c, keyring_files) 

571 

572 

573############################################################################### 

574 

575 

576def main() -> None: 

577 global Options, Logger 

578 

579 cnf = Config() 

580 summarystats = SummaryStats() 

581 

582 Arguments = [ 

583 ("a", "automatic", "Dinstall::Options::Automatic"), 

584 ("h", "help", "Dinstall::Options::Help"), 

585 ("n", "no-action", "Dinstall::Options::No-Action"), 

586 ("p", "no-lock", "Dinstall::Options::No-Lock"), 

587 ("s", "no-mail", "Dinstall::Options::No-Mail"), 

588 ("d", "directory", "Dinstall::Options::Directory", "HasArg"), 

589 ] 

590 

591 for i in [ 

592 "automatic", 

593 "help", 

594 "no-action", 

595 "no-lock", 

596 "no-mail", 

597 "version", 

598 "directory", 

599 ]: 

600 key = "Dinstall::Options::%s" % i 

601 if key not in cnf: 

602 cnf[key] = "" 

603 

604 changes_files = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined] 

605 Options = cnf.subtree("Dinstall::Options") 

606 

607 if Options["Help"]: 

608 usage() 

609 

610 # -n/--dry-run invalidates some other options which would involve things happening 

611 if Options["No-Action"]: 611 ↛ 612line 611 didn't jump to line 612 because the condition on line 611 was never true

612 Options["Automatic"] = "" # type: ignore[index] 

613 

614 # Obtain lock if not in no-action mode and initialize the log 

615 if not Options["No-Action"]: 615 ↛ 634line 615 didn't jump to line 634 because the condition on line 615 was always true

616 lock_fd = os.open( 

617 os.path.join(cnf["Dir::Lock"], "process-upload.lock"), 

618 os.O_RDWR | os.O_CREAT, 

619 ) 

620 try: 

621 fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) 

622 except OSError as e: 

623 if e.errno in (errno.EACCES, errno.EAGAIN): 

624 utils.fubar( 

625 "Couldn't obtain lock; assuming another 'dak process-upload' is already running." 

626 ) 

627 else: 

628 raise 

629 

630 # Initialise UrgencyLog() - it will deal with the case where we don't 

631 # want to log urgencies 

632 urgencylog = UrgencyLog() 

633 

634 Logger = daklog.Logger("process-upload", Options["No-Action"]) 

635 

636 # If we have a directory flag, use it to find our files 

637 if cnf["Dinstall::Options::Directory"] != "": 637 ↛ 657line 637 didn't jump to line 657 because the condition on line 637 was always true

638 # Note that we clobber the list of files we were given in this case 

639 # so warn if the user has done both 

640 if len(changes_files) > 0: 640 ↛ 641line 640 didn't jump to line 641 because the condition on line 640 was never true

641 utils.warn("Directory provided so ignoring files given on command line") 

642 

643 changes_files = utils.get_changes_files(cnf["Dinstall::Options::Directory"]) 

644 # FIXME: quick hack to NOT have p-u run for ages, if some binnmu fun uploads thousands of changes 

645 # might want to make the number configurable at some point 

646 if len(changes_files) > 200: 646 ↛ 647line 646 didn't jump to line 647 because the condition on line 646 was never true

647 import random 

648 

649 changes_files = random.sample(changes_files, k=200) 

650 Logger.log( 

651 [ 

652 "Using changes files from directory", 

653 cnf["Dinstall::Options::Directory"], 

654 len(changes_files), 

655 ] 

656 ) 

657 elif not len(changes_files) > 0: 

658 utils.fubar("No changes files given and no directory specified") 

659 else: 

660 Logger.log(["Using changes files from command-line", len(changes_files)]) 

661 

662 process_changes(changes_files) 

663 

664 if summarystats.accept_count: 

665 sets = "set" 

666 if summarystats.accept_count > 1: 

667 sets = "sets" 

668 print( 

669 "Installed %d package %s, %s." 

670 % ( 

671 summarystats.accept_count, 

672 sets, 

673 utils.size_type(int(summarystats.accept_bytes)), 

674 ) 

675 ) 

676 Logger.log(["total", summarystats.accept_count, summarystats.accept_bytes]) 

677 

678 if summarystats.reject_count: 

679 sets = "set" 

680 if summarystats.reject_count > 1: 

681 sets = "sets" 

682 print("Rejected %d package %s." % (summarystats.reject_count, sets)) 

683 Logger.log(["rejected", summarystats.reject_count]) 

684 

685 if not Options["No-Action"]: 685 ↛ 688line 685 didn't jump to line 688 because the condition on line 685 was always true

686 urgencylog.close() 

687 

688 Logger.close() 

689 

690 

691############################################################################### 

692 

693 

694if __name__ == "__main__": 

695 main()