Coverage for dak/process_upload.py: 82%

322 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-08-03 16:46 +0000

1""" 

2Checks Debian packages from Incoming 

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

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

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

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

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

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

9""" 

10 

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. 

15 

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. 

20 

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 

24 

25# based on process-unchecked and process-accepted 

26 

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

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

29## pa: initialize urgency log 

30## pu|pa: sort changes list 

31 

32## foreach changes: 

33### pa: load dak file 

34## pu: copy CHG to tempdir 

35## pu: check CHG signature 

36## pu: parse changes file 

37## pu: checks: 

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

39## pu: copy FILES to tempdir 

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

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

42## for deb in FILES: 

43## pu: extract control information 

44## pu: various checks on control information 

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

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

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

48## for src in FILES: 

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

50## pu: if isdsc: check signature 

51## for file in FILES: 

52## pu: various checks 

53## pu: NEW? 

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

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

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

57## pu: check the DSC: 

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

59## pu: parse the DSC 

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

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

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

63## for src in DSC_FILES: 

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

65## pu: create new tempdir 

66## pu: create symlink mirror of source 

67## pu: unpack source 

68## pu: extract changelog information for BTS 

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

70## pu: check with lintian 

71## for file in FILES: 

72## pu: check checksums and sizes 

73## for file in DSC_FILES: 

74## pu: check checksums and sizes 

75## pu: CHG: check urgency 

76## for deb in FILES: 

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

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

79### pa: install: 

80### if stable_install: 

81### pa: remove from p-u 

82### pa: add to stable 

83### pa: move CHG to morgue 

84### pa: append data to ChangeLog 

85### pa: send mail 

86### pa: remove .dak file 

87### else: 

88### pa: add dsc to db: 

89### for file in DSC_FILES: 

90### pa: add file to file 

91### pa: add file to dsc_files 

92### pa: create source entry 

93### pa: update source associations 

94### pa: update src_uploaders 

95### for deb in FILES: 

96### pa: add deb to db: 

97### pa: add file to file 

98### pa: find source entry 

99### pa: create binaries entry 

100### pa: update binary associations 

101### pa: .orig component move 

102### pa: move files to pool 

103### pa: save CHG 

104### pa: move CHG to done/ 

105### pa: change entry in queue_build 

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

107## if NEW: 

108## pu: write .dak file 

109## pu: move to NEW 

110## pu: send mail 

111## elsif AUTOBYHAND: 

112## pu: run autobyhand script 

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

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

115## pu: write .dak file 

116## pu: check overrides 

117## pu: move to queue 

118## pu: send mail 

119## else: 

120## pu: write .dak file 

121## pu: move to ACCEPTED 

122## pu: send mails 

123## pu: create files for BTS 

124## pu: create entry in queue_build 

125## pu: check overrides 

126 

127# Integrity checks 

128## GPG 

129## Parsing changes (check for duplicates) 

130## Parse dsc 

131## file list checks 

132 

133# New check layout (TODO: Implement) 

134## Permission checks 

135### suite mappings 

136### ACLs 

137### version checks (suite) 

138### override checks 

139 

140## Source checks 

141### copy orig 

142### unpack 

143### BTS changelog 

144### src contents 

145### lintian 

146### urgency log 

147 

148## Binary checks 

149### timestamps 

150### control checks 

151### src relation check 

152### contents 

153 

154## Database insertion (? copy from stuff) 

155### BYHAND / NEW / Policy queues 

156### Pool 

157 

158## Queue builds 

159 

160import datetime 

161import errno 

162import fcntl 

163import functools 

164import os 

165import random 

166import sys 

167import time 

168import traceback 

169from collections.abc import Callable, Iterable 

170from typing import Concatenate, NoReturn 

171 

172import apt_pkg 

173 

174import daklib.announce 

175import daklib.archive 

176import daklib.checks 

177import daklib.upload 

178from daklib import daklog, utils 

179from daklib.config import Config 

180from daklib.dbconn import DBConn, SignatureHistory, get_active_keyring_files 

181from daklib.regexes import re_default_answer 

182from daklib.summarystats import SummaryStats 

183from daklib.urgencylog import UrgencyLog 

184 

185############################################################################### 

186 

187Options: apt_pkg.Configuration 

188Logger: daklog.Logger 

189 

190############################################################################### 

191 

192 

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

194 print( 

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

196 -a, --automatic automatic run 

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

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

199 --max-duration <D> stop processing after duration (e.g. 10m, 1h 5m) 

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

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

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

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

204 ) 

205 sys.exit(exit_code) 

206 

207 

208############################################################################### 

209 

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

211 

212 

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

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

215 

216 @functools.wraps(function) 

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

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

219 

220 try: 

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

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

223 reason = str(e) 

224 except Exception: 

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

226 traceback.format_exc() 

227 ) 

228 

229 try: 

230 upload.rollback() 

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

232 except Exception: 

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

234 traceback.format_exc(), reason 

235 ) 

236 upload.rollback() 

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

238 

239 raise Exception( 

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

241 reason 

242 ) 

243 ) 

244 

245 return wrapper 

246 

247 

248def get_processed_upload( 

249 upload: daklib.archive.ArchiveUpload, 

250) -> daklib.announce.ProcessedUpload: 

251 changes = upload.changes 

252 control = upload.changes.changes 

253 

254 pu = daklib.announce.ProcessedUpload() 

255 

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

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

258 pu.fingerprint = changes.primary_fingerprint 

259 pu.authorized_by_fingerprint = upload.authorized_by_fingerprint.fingerprint 

260 

261 pu.suites = upload.final_suites or [] 

262 pu.from_policy_suites = [] 

263 

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

265 pu.changes = fd.read() 

266 pu.changes_filename = upload.changes.filename 

267 pu.sourceful = upload.changes.sourceful 

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

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

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

271 pu.bugs = changes.closed_bugs 

272 

273 pu.program = "process-upload" 

274 

275 pu.warnings = upload.warnings 

276 

277 return pu 

278 

279 

280@try_or_reject 

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

282 cnf = Config() 

283 

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

285 print("ACCEPT") 

286 

287 upload.install() 

288 utils.process_buildinfos( 

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

290 ) 

291 

292 assert upload.final_suites is not None 

293 accepted_to_real_suite = any( 

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

295 ) 

296 sourceful_upload = upload.changes.sourceful 

297 

298 control = upload.changes.changes 

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

300 urgency = control.get("Urgency") 

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

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

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

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

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

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

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

308 

309 pu = get_processed_upload(upload) 

310 daklib.announce.announce_accept(pu) 

311 

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

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

314 if accepted_to_real_suite: 

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

316 

317 now = datetime.datetime.now() 

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

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

320 dst = utils.find_next_free(dst) 

321 

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

323 

324 SummaryStats().accept_count += 1 

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

326 

327 

328@try_or_reject 

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

330 

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

332 print("ACCEPT-TO-NEW") 

333 

334 upload.install_to_new() 

335 # TODO: tag bugs pending 

336 

337 pu = get_processed_upload(upload) 

338 daklib.announce.announce_new(pu) 

339 

340 SummaryStats().accept_count += 1 

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

342 

343 

344@try_or_reject 

345def reject( 

346 directory: str, 

347 upload: daklib.archive.ArchiveUpload, 

348 reason: str | None = None, 

349 notify=True, 

350) -> None: 

351 real_reject(directory, upload, reason, notify) 

352 

353 

354def real_reject( 

355 directory: str, 

356 upload: daklib.archive.ArchiveUpload, 

357 reason: str | None = None, 

358 notify=True, 

359) -> None: 

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

361 cnf = Config() 

362 

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

364 print("REJECT") 

365 

366 fs = upload.transaction.fs 

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

368 

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

370 files.append(upload.changes.filename) 

371 

372 for fn in files: 

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

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

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

376 continue 

377 fs.copy(src, dst) 

378 

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

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

381 reason = "" 

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

383 

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

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

386 

387 dst = utils.find_next_free( 

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

389 ) 

390 fh = fs.create(dst) 

391 fh.write(reason) 

392 fh.close() 

393 

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

395 pu = get_processed_upload(upload) 

396 daklib.announce.announce_reject(pu, reason) 

397 

398 SummaryStats().reject_count += 1 

399 

400 

401############################################################################### 

402 

403 

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

405 changes = upload.changes 

406 processed = True 

407 

408 global Logger 

409 

410 cnf = Config() 

411 

412 okay = upload.check() 

413 

414 try: 

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

416 except UnicodeDecodeError as e: 

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

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

419 # upload gets rejected in any case 

420 upload.reject_reasons.append(summary) 

421 

422 package_info = [] 

423 if okay: 

424 if changes.source is not None: 

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

426 for binary in changes.binaries: 

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

428 

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

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

431 answer = "S" 

432 

433 print(summary) 

434 print() 

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

436 print() 

437 if len(upload.warnings) > 0: 

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

439 print() 

440 

441 if len(upload.reject_reasons) > 0: 

442 print("Reason:") 

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

444 print() 

445 

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

447 created = os.stat(path).st_mtime 

448 now = time.time() 

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

450 

451 if too_new: 

452 print("SKIP (too new)") 

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

454 else: 

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

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

457 answer = "R" 

458 elif upload.new: 

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

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

461 answer = "N" 

462 else: 

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

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

465 answer = "A" 

466 

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

468 answer = utils.input_or_exit(prompt) 

469 m = re_default_answer.match(prompt) 

470 if answer == "": 

471 assert m is not None 

472 answer = m.group(1) 

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

474 

475 if answer == "R": 

476 reject(directory, upload) 

477 elif answer == "A": 

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

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

480 accept(directory, upload) 

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

482 accept(directory, upload) 

483 else: 

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

485 accept_to_new(directory, upload) 

486 elif answer == "N": 

487 accept_to_new(directory, upload) 

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

489 sys.exit(0) 

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

491 processed = False 

492 

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

494 upload.commit() 

495 

496 return processed 

497 

498 

499############################################################################### 

500 

501 

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

503 try: 

504 os.unlink(path) 

505 except OSError as e: 

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

507 raise 

508 

509 

510def process_it( 

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

512) -> None: 

513 global Logger 

514 

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

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

517 

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

519 processed = action(directory, upload) 

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

521 session = DBConn().session() 

522 history = SignatureHistory.from_signed_file(upload.changes) 

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

524 session.add(history) 

525 session.commit() 

526 session.close() 

527 

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

529 for fn in changes.files: 

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

531 

532 

533############################################################################### 

534 

535 

536def _source_group(source: str) -> str: 

537 """decide group for given source name 

538 

539 This is mostly for Secure Boot signing where "X" should be 

540 processed in the same group as (and before) "X-signed-*". 

541 

542 As a further special case, "grub2" needs to be processed in the 

543 same group as (and before) "grub-efi-*-signed". 

544 """ 

545 

546 group = source.split("-", 1)[0] 

547 if group == "grub2": 547 ↛ 548line 547 didn't jump to line 548 because the condition on line 547 was never true

548 return "grub" 

549 return group 

550 

551 

552def _group_changes_by_source_and_shuffle( 

553 changes: list[tuple[str, daklib.upload.Changes]], 

554) -> list[tuple[str, daklib.upload.Changes]]: 

555 """Group changes by Source, sort each group, and shuffle group order.""" 

556 grouped: dict[str, list[tuple[str, daklib.upload.Changes]]] = {} 

557 for directory, change in changes: 

558 source = _source_group(change.changes.get("Source", "")) 

559 grouped.setdefault(source, []).append((directory, change)) 

560 

561 for group in grouped.values(): 

562 group.sort(key=lambda item: item[1]) 

563 

564 source_names = list(grouped) 

565 random.shuffle(source_names) 

566 return [item for source in source_names for item in grouped[source]] 

567 

568 

569############################################################################### 

570 

571 

572def process_changes( 

573 changes_filenames: Iterable[str], 

574 max_duration: datetime.timedelta | None = None, 

575) -> None: 

576 deadline: float | None = None 

577 if max_duration is not None: 577 ↛ 578line 577 didn't jump to line 578 because the condition on line 577 was never true

578 deadline = time.monotonic() + max_duration.total_seconds() 

579 

580 session = DBConn().session() 

581 keyring_files = get_active_keyring_files(session) 

582 session.close() 

583 

584 changes = [] 

585 for fn in changes_filenames: 

586 try: 

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

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

589 changes.append((directory, c)) 

590 except Exception as e: 

591 try: 

592 Logger.log( 

593 [ 

594 filename, 

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

596 ] 

597 ) 

598 except Exception as e: 

599 Logger.log( 

600 [ 

601 filename, 

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

603 fn, repr(e) 

604 ), 

605 ] 

606 ) 

607 

608 changes = _group_changes_by_source_and_shuffle(changes) 

609 

610 for directory, c in changes: 

611 if deadline is not None and time.monotonic() >= deadline: 611 ↛ 612line 611 didn't jump to line 612 because the condition on line 611 was never true

612 Logger.log(["Max duration reached; stopping processing loop"]) 

613 break 

614 process_it(directory, c, keyring_files) 

615 

616 

617############################################################################### 

618 

619 

620def main() -> None: 

621 global Options, Logger 

622 

623 cnf = Config() 

624 summarystats = SummaryStats() 

625 

626 Arguments = [ 

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

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

629 ("\0", "max-duration", "Dinstall::Options::Max-Duration", "HasArg"), 

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

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

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

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

634 ] 

635 

636 for i in [ 

637 "automatic", 

638 "help", 

639 "max-duration", 

640 "no-action", 

641 "no-lock", 

642 "no-mail", 

643 "version", 

644 "directory", 

645 ]: 

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

647 if key not in cnf: 

648 cnf[key] = "" 

649 

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

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

652 

653 if Options["Help"]: 

654 usage() 

655 

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

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

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

659 

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

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

662 lock_fd = os.open( 

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

664 os.O_RDWR | os.O_CREAT, 

665 ) 

666 try: 

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

668 except OSError as e: 

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

670 utils.fubar( 

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

672 ) 

673 else: 

674 raise 

675 

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

677 # want to log urgencies 

678 urgencylog = UrgencyLog() 

679 

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

681 

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

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

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

685 # so warn if the user has done both 

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

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

688 

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

690 Logger.log( 

691 [ 

692 "Using changes files from directory", 

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

694 len(changes_files), 

695 ] 

696 ) 

697 elif not len(changes_files) > 0: 

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

699 else: 

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

701 

702 max_duration = None 

703 if Options["Max-Duration"]: 703 ↛ 704line 703 didn't jump to line 704 because the condition on line 703 was never true

704 try: 

705 max_duration = utils.parse_duration(Options["Max-Duration"]) 

706 except ValueError as e: 

707 utils.fubar("Invalid --max-duration: %s" % e) 

708 

709 process_changes(changes_files, max_duration=max_duration) 

710 

711 if summarystats.accept_count: 

712 sets = "set" 

713 if summarystats.accept_count > 1: 

714 sets = "sets" 

715 print( 

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

717 % ( 

718 summarystats.accept_count, 

719 sets, 

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

721 ) 

722 ) 

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

724 

725 if summarystats.reject_count: 

726 sets = "set" 

727 if summarystats.reject_count > 1: 

728 sets = "sets" 

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

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

731 

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

733 urgencylog.close() 

734 

735 Logger.close()