1#! /usr/bin/env python3 

2# vim:set et ts=4 sw=4: 

3 

4"""Handles NEW and BYHAND packages 

5 

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. 

16 

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. 

21 

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 

25 

26################################################################################ 

27 

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!!! 

42 

43################################################################################ 

44 

45import contextlib 

46import errno 

47import os 

48import pwd 

49import readline 

50import stat 

51import subprocess 

52import sys 

53import tempfile 

54from collections import defaultdict 

55from collections.abc import Iterable 

56from typing import NoReturn, Optional 

57 

58import apt_pkg 

59from sqlalchemy import or_ 

60 

61import dak.examine_package 

62import daklib.dbconn 

63from daklib import daklog, utils 

64from daklib.config import Config 

65from daklib.dak_exceptions import AlreadyLockedError 

66from daklib.dbconn import ( 

67 BinaryMetadata, 

68 DBBinary, 

69 DBChange, 

70 DBConn, 

71 DBSource, 

72 MetadataKey, 

73 NewComment, 

74 PolicyQueue, 

75 PolicyQueueUpload, 

76 Priority, 

77 Section, 

78 Suite, 

79 get_new_comments, 

80) 

81from daklib.policy import PolicyQueueUploadHandler, UploadCopy 

82from daklib.queue import check_valid, edit_note, prod_maintainer 

83from daklib.regexes import re_default_answer, re_isanum 

84from daklib.summarystats import SummaryStats 

85from daklib.termcolor import colorize as Color 

86 

87# Globals 

88Options = None 

89Logger = None 

90 

91Priorities = None 

92Sections = None 

93 

94################################################################################ 

95################################################################################ 

96################################################################################ 

97 

98 

99class Section_Completer: 

100 def __init__(self, session): 

101 self.sections = [] 

102 self.matches = [] 

103 for (s,) in session.query(Section.section): 

104 self.sections.append(s) 

105 

106 def complete(self, text, state): 

107 if state == 0: 

108 self.matches = [] 

109 n = len(text) 

110 for word in self.sections: 

111 if word[:n] == text: 

112 self.matches.append(word) 

113 try: 

114 return self.matches[state] 

115 except IndexError: 

116 return None 

117 

118 

119############################################################ 

120 

121 

122class Priority_Completer: 

123 def __init__(self, session): 

124 self.priorities = [] 

125 self.matches = [] 

126 for (p,) in session.query(Priority.priority): 

127 self.priorities.append(p) 

128 

129 def complete(self, text, state): 

130 if state == 0: 

131 self.matches = [] 

132 n = len(text) 

133 for word in self.priorities: 

134 if word[:n] == text: 

135 self.matches.append(word) 

136 try: 

137 return self.matches[state] 

138 except IndexError: 

139 return None 

140 

141 

142################################################################################ 

143 

144 

145def takenover_binaries(upload, missing, session): 

146 rows = [] 

147 binaries = set([x.package for x in upload.binaries]) 

148 for m in missing: 

149 if m["type"] != "dsc": 

150 binaries.discard(m["package"]) 

151 if binaries: 

152 source = upload.binaries[0].source.source 

153 suite = upload.target_suite.overridesuite or upload.target_suite.suite_name 

154 suites = [ 

155 s[0] 

156 for s in session.query(Suite.suite_name) 

157 .filter(or_(Suite.suite_name == suite, Suite.overridesuite == suite)) 

158 .all() 

159 ] 

160 rows = ( 

161 session.query(DBSource.source, DBBinary.package) 

162 .distinct() 

163 .filter(DBBinary.package.in_(binaries)) 

164 .join(DBBinary.source) 

165 .filter(DBSource.source != source) 

166 .join(DBBinary.suites) 

167 .filter(Suite.suite_name.in_(suites)) 

168 .order_by(DBSource.source, DBBinary.package) 

169 .all() 

170 ) 

171 return rows 

172 

173 

174################################################################################ 

175 

176 

177def print_new( 

178 upload: daklib.dbconn.PolicyQueueUpload, 

179 missing, 

180 indexed: bool, 

181 session, 

182 file=sys.stdout, 

183): 

184 check_valid(missing, session) 

185 for index, m in enumerate(missing, 1): 

186 if m["type"] != "deb": 

187 package = "{0}:{1}".format(m["type"], m["package"]) 

188 else: 

189 package = m["package"] 

190 section = m["section"] 

191 priority = m["priority"] 

192 if m["type"] == "deb" and priority != "optional": 192 ↛ 193line 192 didn't jump to line 193, because the condition on line 192 was never true

193 priority = Color(priority, "red") 

194 included = "" if m["included"] else "NOT UPLOADED" 

195 if indexed: 195 ↛ 196line 195 didn't jump to line 196, because the condition on line 195 was never true

196 line = "(%s): %-20s %-20s %-20s %s" % ( 

197 index, 

198 package, 

199 priority, 

200 section, 

201 included, 

202 ) 

203 else: 

204 line = "%-20s %-20s %-20s %s" % (package, priority, section, included) 

205 line = line.strip() 

206 if not m["valid"]: 206 ↛ 207line 206 didn't jump to line 207, because the condition on line 206 was never true

207 line = line + " [!]" 

208 print(line, file=file) 

209 takenover = takenover_binaries(upload, missing, session) 

210 if takenover: 210 ↛ 211line 210 didn't jump to line 211, because the condition on line 210 was never true

211 print("\n\nBINARIES TAKEN OVER\n") 

212 for t in takenover: 

213 print("%s: %s" % (t[0], t[1])) 

214 notes = get_new_comments(upload.policy_queue, upload.changes.source) 

215 for note in notes: 215 ↛ 216line 215 didn't jump to line 216, because the loop on line 215 never started

216 print("\n") 

217 print(Color("Author:", "yellow"), "%s" % note.author) 

218 print(Color("Version:", "yellow"), "%s" % note.version) 

219 print(Color("Timestamp:", "yellow"), "%s" % note.notedate) 

220 print("\n\n") 

221 print(note.comment) 

222 print("-" * 72) 

223 return len(notes) > 0 

224 

225 

226################################################################################ 

227 

228 

229def index_range(index: int) -> str: 

230 if index == 1: 

231 return "1" 

232 else: 

233 return "1-%s" % (index) 

234 

235 

236################################################################################ 

237################################################################################ 

238 

239 

240def edit_new(overrides, upload: daklib.dbconn.PolicyQueueUpload, session): 

241 with tempfile.NamedTemporaryFile(mode="w+t") as fh: 

242 # Write the current data to a temporary file 

243 print_new(upload, overrides, indexed=False, session=session, file=fh) 

244 fh.flush() 

245 utils.call_editor_for_file(fh.name) 

246 # Read the edited data back in 

247 fh.seek(0) 

248 lines = fh.readlines() 

249 

250 overrides_map = dict([((o["type"], o["package"]), o) for o in overrides]) 

251 new_overrides = [] 

252 # Parse the new data 

253 for line in lines: 

254 line = line.strip() 

255 if line == "" or line[0] == "#": 

256 continue 

257 s = line.split() 

258 # Pad the list if necessary 

259 s[len(s) : 3] = [None] * (3 - len(s)) 

260 (pkg, priority, section) = s[:3] 

261 if pkg.find(":") != -1: 

262 type, pkg = pkg.split(":", 1) 

263 else: 

264 type = "deb" 

265 o = overrides_map.get((type, pkg), None) 

266 if o is None: 

267 utils.warn("Ignoring unknown package '%s'" % (pkg)) 

268 else: 

269 if section.find("/") != -1: 

270 component = section.split("/", 1)[0] 

271 else: 

272 component = "main" 

273 new_overrides.append( 

274 dict( 

275 package=pkg, 

276 type=type, 

277 section=section, 

278 component=component, 

279 priority=priority, 

280 included=o["included"], 

281 ) 

282 ) 

283 return new_overrides 

284 

285 

286################################################################################ 

287 

288 

289def edit_index(new, upload: daklib.dbconn.PolicyQueueUpload, index): 

290 package = new[index]["package"] 

291 priority = new[index]["priority"] 

292 section = new[index]["section"] 

293 ftype = new[index]["type"] 

294 done = False 

295 while not done: 

296 print("\t".join([package, priority, section])) 

297 

298 answer = "XXX" 

299 if ftype != "dsc": 

300 prompt = "[B]oth, Priority, Section, Done ? " 

301 else: 

302 prompt = "[S]ection, Done ? " 

303 edit_priority = edit_section = 0 

304 

305 while prompt.find(answer) == -1: 

306 answer = utils.input_or_exit(prompt) 

307 m = re_default_answer.match(prompt) 

308 if answer == "": 

309 answer = m.group(1) 

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

311 

312 if answer == "P": 

313 edit_priority = 1 

314 elif answer == "S": 

315 edit_section = 1 

316 elif answer == "B": 

317 edit_priority = edit_section = 1 

318 elif answer == "D": 

319 done = True 

320 

321 # Edit the priority 

322 if edit_priority: 

323 readline.set_completer(Priorities.complete) 

324 got_priority = 0 

325 while not got_priority: 

326 new_priority = utils.input_or_exit("New priority: ").strip() 

327 if new_priority not in Priorities.priorities: 

328 print( 

329 "E: '%s' is not a valid priority, try again." % (new_priority) 

330 ) 

331 else: 

332 got_priority = 1 

333 priority = new_priority 

334 

335 # Edit the section 

336 if edit_section: 

337 readline.set_completer(Sections.complete) 

338 got_section = 0 

339 while not got_section: 

340 new_section = utils.input_or_exit("New section: ").strip() 

341 if new_section not in Sections.sections: 

342 print("E: '%s' is not a valid section, try again." % (new_section)) 

343 else: 

344 got_section = 1 

345 section = new_section 

346 

347 # Reset the readline completer 

348 readline.set_completer(None) 

349 

350 new[index]["priority"] = priority 

351 new[index]["section"] = section 

352 if section.find("/") != -1: 

353 component = section.split("/", 1)[0] 

354 else: 

355 component = "main" 

356 new[index]["component"] = component 

357 

358 return new 

359 

360 

361################################################################################ 

362 

363 

364def edit_overrides(new, upload: daklib.dbconn.PolicyQueueUpload, session): 

365 print() 

366 done = False 

367 while not done: 

368 print_new(upload, new, indexed=True, session=session) 

369 prompt = "edit override <n>, Editor, Done ? " 

370 

371 got_answer = 0 

372 while not got_answer: 

373 answer = utils.input_or_exit(prompt) 

374 if not answer.isdigit(): 

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

376 if answer == "E" or answer == "D": 

377 got_answer = 1 

378 elif re_isanum.match(answer): 

379 answer = int(answer) 

380 if answer < 1 or answer > len(new): 

381 print("{0} is not a valid index. Please retry.".format(answer)) 

382 else: 

383 got_answer = 1 

384 

385 if answer == "E": 

386 new = edit_new(new, upload, session) 

387 elif answer == "D": 

388 done = True 

389 else: 

390 edit_index(new, upload, answer - 1) 

391 

392 return new 

393 

394 

395################################################################################ 

396 

397 

398def check_pkg(upload, upload_copy: UploadCopy, session): 

399 missing = [] 

400 changes = os.path.join(upload_copy.directory, upload.changes.changesname) 

401 suite_name = upload.target_suite.suite_name 

402 handler = PolicyQueueUploadHandler(upload, session) 

403 missing = [ 

404 (m["type"], m["package"]) for m in handler.missing_overrides(hints=missing) 

405 ] 

406 

407 less_cmd = ("less", "-r", "-") 

408 less_process = subprocess.Popen( 

409 less_cmd, bufsize=0, stdin=subprocess.PIPE, text=True 

410 ) 

411 try: 

412 less_fd = less_process.stdin 

413 less_fd.write(dak.examine_package.display_changes(suite_name, changes)) 

414 

415 source = upload.source 

416 if source is not None: 416 ↛ 422line 416 didn't jump to line 422, because the condition on line 416 was never false

417 source_file = os.path.join( 

418 upload_copy.directory, os.path.basename(source.poolfile.filename) 

419 ) 

420 less_fd.write(dak.examine_package.check_dsc(suite_name, source_file)) 

421 

422 for binary in upload.binaries: 

423 binary_file = os.path.join( 

424 upload_copy.directory, os.path.basename(binary.poolfile.filename) 

425 ) 

426 examined = dak.examine_package.check_deb(suite_name, binary_file) 

427 # We always need to call check_deb to display package relations for every binary, 

428 # but we print its output only if new overrides are being added. 

429 if ("deb", binary.package) in missing: 429 ↛ 422line 429 didn't jump to line 422, because the condition on line 429 was never false

430 less_fd.write(examined) 

431 

432 less_fd.write(dak.examine_package.output_package_relations()) 

433 less_process.stdin.close() 

434 except OSError as e: 

435 if e.errno == errno.EPIPE: 

436 utils.warn("[examine_package] Caught EPIPE; skipping.") 

437 else: 

438 raise 

439 except KeyboardInterrupt: 

440 utils.warn("[examine_package] Caught C-c; skipping.") 

441 finally: 

442 less_process.communicate() 

443 

444 

445################################################################################ 

446 

447## FIXME: horribly Debian specific 

448 

449 

450def do_bxa_notification(new, upload: daklib.dbconn.PolicyQueueUpload, session): 

451 cnf = Config() 

452 

453 new = set([o["package"] for o in new if o["type"] == "deb"]) 

454 if len(new) == 0: 

455 return 

456 

457 key = session.query(MetadataKey).filter_by(key="Description").one() 

458 summary = "" 

459 for binary in upload.binaries: 

460 if binary.package not in new: 

461 continue 

462 description = ( 

463 session.query(BinaryMetadata).filter_by(binary=binary, key=key).one().value 

464 ) 

465 summary += "\n" 

466 summary += "Package: {0}\n".format(binary.package) 

467 summary += "Description: {0}\n".format(description) 

468 

469 subst = { 

470 "__DISTRO__": cnf["Dinstall::MyDistribution"], 

471 "__BCC__": "X-DAK: dak process-new", 

472 "__BINARY_DESCRIPTIONS__": summary, 

473 "__CHANGES_FILENAME__": upload.changes.changesname, 

474 "__SOURCE__": upload.changes.source, 

475 "__VERSION__": upload.changes.version, 

476 "__ARCHITECTURE__": upload.changes.architecture, 

477 } 

478 

479 bxa_mail = utils.TemplateSubst( 

480 subst, os.path.join(cnf["Dir::Templates"], "process-new.bxa_notification") 

481 ) 

482 utils.send_mail(bxa_mail) 

483 

484 

485################################################################################ 

486 

487 

488def run_user_inspect_command( 

489 upload: daklib.dbconn.PolicyQueueUpload, upload_copy: UploadCopy 

490): 

491 command = os.environ.get("DAK_INSPECT_UPLOAD") 

492 if command is None: 492 ↛ 495line 492 didn't jump to line 495, because the condition on line 492 was never false

493 return 

494 

495 directory = upload_copy.directory 

496 if upload.source: 

497 dsc = os.path.basename(upload.source.poolfile.filename) 

498 else: 

499 dsc = "" 

500 changes = upload.changes.changesname 

501 

502 shell_command = command.format( 

503 directory=directory, 

504 dsc=dsc, 

505 changes=changes, 

506 ) 

507 

508 subprocess.check_call(shell_command, shell=True) 

509 

510 

511################################################################################ 

512 

513 

514def get_reject_reason(reason: str = "") -> Optional[str]: 

515 """get reason for rejection 

516 

517 :return: string giving the reason for the rejection or :const:`None` if the 

518 rejection should be cancelled 

519 """ 

520 answer = "E" 

521 if Options["Automatic"]: 521 ↛ 522line 521 didn't jump to line 522, because the condition on line 521 was never true

522 answer = "R" 

523 

524 while answer == "E": 

525 reason = utils.call_editor(reason) 

526 print("Reject message:") 

527 print(utils.prefix_multi_line_string(reason, " ", include_blank_lines=True)) 

528 prompt = "[R]eject, Edit, Abandon, Quit ?" 

529 answer = "XXX" 

530 while prompt.find(answer) == -1: 

531 answer = utils.input_or_exit(prompt) 

532 m = re_default_answer.search(prompt) 

533 if answer == "": 533 ↛ 534line 533 didn't jump to line 534, because the condition on line 533 was never true

534 answer = m.group(1) 

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

536 

537 if answer == "Q": 537 ↛ 538line 537 didn't jump to line 538, because the condition on line 537 was never true

538 sys.exit(0) 

539 

540 if answer == "R": 540 ↛ 542line 540 didn't jump to line 542, because the condition on line 540 was never false

541 return reason 

542 return None 

543 

544 

545################################################################################ 

546 

547 

548def do_new( 

549 upload: daklib.dbconn.PolicyQueueUpload, upload_copy: UploadCopy, handler, session 

550): 

551 run_user_inspect_command(upload, upload_copy) 

552 

553 # The main NEW processing loop 

554 done = False 

555 missing = [] 

556 while not done: 

557 queuedir = upload.policy_queue.path 

558 byhand = upload.byhand 

559 

560 missing = handler.missing_overrides(hints=missing) 

561 broken = not check_valid(missing, session) 

562 

563 changesname = os.path.basename(upload.changes.changesname) 

564 

565 print() 

566 print(changesname) 

567 print("-" * len(changesname)) 

568 print() 

569 print(" Target: {0}".format(upload.target_suite.suite_name)) 

570 print(" Changed-By: {0}".format(upload.changes.changedby)) 

571 print(" Date: {0}".format(upload.changes.date)) 

572 print() 

573 

574 if missing: 

575 print("NEW\n") 

576 

577 for package in missing: 

578 if package["type"] == "deb" and package["priority"] == "extra": 578 ↛ 579line 578 didn't jump to line 579, because the condition on line 578 was never true

579 package["priority"] = "optional" 

580 

581 answer = "XXX" 

582 if Options["No-Action"] or Options["Automatic"]: 

583 answer = "S" 

584 

585 note = print_new(upload, missing, indexed=False, session=session) 

586 prompt = "" 

587 

588 has_unprocessed_byhand = False 

589 for f in byhand: 589 ↛ 590line 589 didn't jump to line 590, because the loop on line 589 never started

590 path = os.path.join(queuedir, f.filename) 

591 if not f.processed and os.path.exists(path): 

592 print( 

593 "W: {0} still present; please process byhand components and try again".format( 

594 f.filename 

595 ) 

596 ) 

597 has_unprocessed_byhand = True 

598 

599 if not has_unprocessed_byhand and not broken and not note: 599 ↛ 605line 599 didn't jump to line 605, because the condition on line 599 was never false

600 if len(missing) == 0: 

601 prompt = "Accept, " 

602 answer = "A" 

603 else: 

604 prompt = "Add overrides, " 

605 if broken: 605 ↛ 606line 605 didn't jump to line 606, because the condition on line 605 was never true

606 print( 

607 "W: [!] marked entries must be fixed before package can be processed." 

608 ) 

609 if note: 609 ↛ 610line 609 didn't jump to line 610, because the condition on line 609 was never true

610 print("W: note must be removed before package can be processed.") 

611 prompt += "RemOve all notes, Remove note, " 

612 

613 prompt += ( 

614 "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?" 

615 ) 

616 

617 while prompt.find(answer) == -1: 

618 answer = utils.input_or_exit(prompt) 

619 m = re_default_answer.search(prompt) 

620 if answer == "": 620 ↛ 621line 620 didn't jump to line 621, because the condition on line 620 was never true

621 answer = m.group(1) 

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

623 

624 if answer in ("A", "E", "M", "O", "R") and Options["Trainee"]: 624 ↛ 625line 624 didn't jump to line 625, because the condition on line 624 was never true

625 utils.warn("Trainees can't do that") 

626 continue 

627 

628 if answer == "A" and not Options["Trainee"]: 

629 handler.add_overrides(missing, upload.target_suite) 

630 if Config().find_b("Dinstall::BXANotify"): 630 ↛ 632line 630 didn't jump to line 632, because the condition on line 630 was never false

631 do_bxa_notification(missing, upload, session) 

632 handler.accept() 

633 done = True 

634 Logger.log(["NEW ACCEPT", upload.changes.changesname]) 

635 elif answer == "C": 

636 check_pkg(upload, upload_copy, session) 

637 elif answer == "E" and not Options["Trainee"]: 637 ↛ 638line 637 didn't jump to line 638, because the condition on line 637 was never true

638 missing = edit_overrides(missing, upload, session) 

639 elif answer == "M" and not Options["Trainee"]: 

640 reason = Options.get("Manual-Reject", "") + "\n" 

641 reason = reason + "\n\n=====\n\n".join( 

642 [ 

643 n.comment 

644 for n in get_new_comments( 

645 upload.policy_queue, upload.changes.source, session=session 

646 ) 

647 ] 

648 ) 

649 reason = get_reject_reason(reason) 

650 if reason is not None: 650 ↛ 709line 650 didn't jump to line 709, because the condition on line 650 was never false

651 Logger.log(["NEW REJECT", upload.changes.changesname]) 

652 handler.reject(reason) 

653 done = True 

654 elif answer == "N": 654 ↛ 655line 654 didn't jump to line 655, because the condition on line 654 was never true

655 if ( 

656 edit_note( 

657 get_new_comments( 

658 upload.policy_queue, upload.changes.source, session=session 

659 ), 

660 upload, 

661 session, 

662 bool(Options["Trainee"]), 

663 ) 

664 == 0 

665 ): 

666 end() 

667 sys.exit(0) 

668 elif answer == "P" and not Options["Trainee"]: 668 ↛ 669line 668 didn't jump to line 669, because the condition on line 668 was never true

669 if ( 

670 prod_maintainer( 

671 get_new_comments( 

672 upload.policy_queue, upload.changes.source, session=session 

673 ), 

674 upload, 

675 session, 

676 bool(Options["Trainee"]), 

677 ) 

678 == 0 

679 ): 

680 end() 

681 sys.exit(0) 

682 Logger.log(["NEW PROD", upload.changes.changesname]) 

683 elif answer == "R" and not Options["Trainee"]: 683 ↛ 684line 683 didn't jump to line 684, because the condition on line 683 was never true

684 confirm = utils.input_or_exit("Really clear note (y/N)? ").lower() 

685 if confirm == "y": 

686 for c in get_new_comments( 

687 upload.policy_queue, 

688 upload.changes.source, 

689 upload.changes.version, 

690 session=session, 

691 ): 

692 session.delete(c) 

693 session.commit() 

694 elif answer == "O" and not Options["Trainee"]: 694 ↛ 695line 694 didn't jump to line 695, because the condition on line 694 was never true

695 confirm = utils.input_or_exit("Really clear all notes (y/N)? ").lower() 

696 if confirm == "y": 

697 for c in get_new_comments( 

698 upload.policy_queue, upload.changes.source, session=session 

699 ): 

700 session.delete(c) 

701 session.commit() 

702 

703 elif answer == "S": 703 ↛ 705line 703 didn't jump to line 705, because the condition on line 703 was never false

704 done = True 

705 elif answer == "Q": 

706 end() 

707 sys.exit(0) 

708 

709 if handler.get_action(): 

710 print("PENDING %s\n" % handler.get_action()) 

711 

712 

713################################################################################ 

714################################################################################ 

715################################################################################ 

716 

717 

718def usage(exit_code: int = 0) -> NoReturn: 

719 print( 

720 """Usage: dak process-new [OPTION]... [CHANGES]... 

721 -a, --automatic automatic run 

722 -b, --no-binaries do not sort binary-NEW packages first 

723 -c, --comments show NEW comments 

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

725 -m, --manual-reject=MSG manual reject with `msg' 

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

727 -q, --queue=QUEUE operate on a different queue 

728 -t, --trainee FTP Trainee mode 

729 -V, --version display the version number and exit 

730 

731ENVIRONMENT VARIABLES 

732 

733 DAK_INSPECT_UPLOAD: shell command to run to inspect a package 

734 The command is automatically run in a shell when an upload 

735 is checked. The following substitutions are available: 

736 

737 {directory}: directory the upload is contained in 

738 {dsc}: name of the included dsc or the empty string 

739 {changes}: name of the changes file 

740 

741 Note that Python's 'format' method is used to format the command. 

742 

743 Example: run mc in a tmux session to inspect the upload 

744 

745 export DAK_INSPECT_UPLOAD='tmux new-session -d -s process-new 2>/dev/null; tmux new-window -n "{changes}" -t process-new:0 -k "cd {directory}; mc"' 

746 

747 and run 

748 

749 tmux attach -t process-new 

750 

751 in a separate terminal session. 

752""" 

753 ) 

754 sys.exit(exit_code) 

755 

756 

757################################################################################ 

758 

759 

760@contextlib.contextmanager 

761def lock_package(package: str): 

762 """ 

763 Lock `package` so that noone else jumps in processing it. 

764 

765 :param package: source package name to lock 

766 """ 

767 

768 cnf = Config() 

769 

770 path = os.path.join(cnf.get("Process-New::LockDir", cnf["Dir::Lock"]), package) 

771 

772 try: 

773 fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDONLY, 0o644) 

774 except OSError as e: 

775 if e.errno == errno.EEXIST or e.errno == errno.EACCES: 

776 try: 

777 user = ( 

778 pwd.getpwuid(os.stat(path)[stat.ST_UID])[4] 

779 .split(",")[0] 

780 .replace(".", "") 

781 ) 

782 except KeyError: 

783 user = "TotallyUnknown" 

784 raise AlreadyLockedError(user) 

785 raise 

786 

787 try: 

788 yield fd 

789 finally: 

790 os.unlink(path) 

791 

792 

793def do_pkg(upload: daklib.dbconn.PolicyQueueUpload, session): 

794 cnf = Config() 

795 group = cnf.get("Dinstall::UnprivGroup") or None 

796 

797 try: 

798 with lock_package(upload.changes.source), UploadCopy( 

799 upload, group=group 

800 ) as upload_copy: 

801 handler = PolicyQueueUploadHandler(upload, session) 

802 if handler.get_action() is not None: 

803 print("PENDING %s\n" % handler.get_action()) 

804 return 

805 

806 do_new(upload, upload_copy, handler, session) 

807 except AlreadyLockedError as e: 

808 print("Seems to be locked by %s already, skipping..." % (e)) 

809 

810 

811def show_new_comments(uploads: Iterable[daklib.dbconn.PolicyQueueUpload], session): 

812 sources = [upload.changes.source for upload in uploads] 

813 if len(sources) == 0: 

814 return 

815 

816 query = """SELECT package, version, comment, author 

817 FROM new_comments 

818 WHERE package IN :sources 

819 ORDER BY package, version""" 

820 

821 r = session.execute(query, params=dict(sources=tuple(sources))) 

822 

823 for i in r: 

824 print("%s_%s\n%s\n(%s)\n\n\n" % (i[0], i[1], i[2], i[3])) 

825 

826 session.rollback() 

827 

828 

829################################################################################ 

830 

831 

832def sort_uploads( 

833 new_queue: PolicyQueue, 

834 uploads: Iterable[daklib.dbconn.PolicyQueueUpload], 

835 session, 

836 nobinaries: bool = False, 

837) -> list[daklib.dbconn.PolicyQueueUpload]: 

838 sources = defaultdict(list) 

839 sorteduploads = [] 

840 suitesrc = [ 

841 s.source 

842 for s in session.query(DBSource.source).filter( 

843 DBSource.suites.any(Suite.suite_name.in_(["unstable", "experimental"])) 

844 ) 

845 ] 

846 comments = [ 

847 p.package 

848 for p in session.query(NewComment.package) 

849 .filter_by(trainee=False, policy_queue=new_queue) 

850 .distinct() 

851 ] 

852 for upload in uploads: 

853 source = upload.changes.source 

854 sources[source].append( 

855 { 

856 "upload": upload, 

857 "date": upload.changes.created, 

858 "stack": 1, 

859 "binary": True if source in suitesrc else False, 

860 "comments": True if source in comments else False, 

861 } 

862 ) 

863 for src in sources: 

864 if len(sources[src]) > 1: 

865 changes = sources[src] 

866 firstseen = sorted(changes, key=lambda k: (k["date"]))[0]["date"] 

867 changes.sort(key=lambda item: item["date"]) 

868 for i in range(0, len(changes)): 

869 changes[i]["date"] = firstseen 

870 changes[i]["stack"] = i + 1 

871 sorteduploads += sources[src] 

872 if nobinaries: 872 ↛ 873line 872 didn't jump to line 873, because the condition on line 872 was never true

873 sorteduploads = [ 

874 u["upload"] 

875 for u in sorted( 

876 sorteduploads, 

877 key=lambda k: (k["comments"], k["binary"], k["date"], -k["stack"]), 

878 ) 

879 ] 

880 else: 

881 sorteduploads = [ 

882 u["upload"] 

883 for u in sorted( 

884 sorteduploads, 

885 key=lambda k: (k["comments"], -k["binary"], k["date"], -k["stack"]), 

886 ) 

887 ] 

888 return sorteduploads 

889 

890 

891################################################################################ 

892 

893 

894def end(): 

895 accept_count = SummaryStats().accept_count 

896 accept_bytes = SummaryStats().accept_bytes 

897 

898 if accept_count: 898 ↛ 899line 898 didn't jump to line 899, because the condition on line 898 was never true

899 sets = "set" 

900 if accept_count > 1: 

901 sets = "sets" 

902 print( 

903 "Accepted %d package %s, %s." 

904 % (accept_count, sets, utils.size_type(int(accept_bytes))), 

905 file=sys.stderr, 

906 ) 

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

908 

909 if not Options["No-Action"] and not Options["Trainee"]: 909 ↛ exitline 909 didn't return from function 'end', because the condition on line 909 was never false

910 Logger.close() 

911 

912 

913################################################################################ 

914 

915 

916def main(): 

917 global Options, Logger, Sections, Priorities 

918 

919 cnf = Config() 

920 session = DBConn().session() 

921 

922 Arguments = [ 

923 ("a", "automatic", "Process-New::Options::Automatic"), 

924 ("b", "no-binaries", "Process-New::Options::No-Binaries"), 

925 ("c", "comments", "Process-New::Options::Comments"), 

926 ("h", "help", "Process-New::Options::Help"), 

927 ("m", "manual-reject", "Process-New::Options::Manual-Reject", "HasArg"), 

928 ("t", "trainee", "Process-New::Options::Trainee"), 

929 ("q", "queue", "Process-New::Options::Queue", "HasArg"), 

930 ("n", "no-action", "Process-New::Options::No-Action"), 

931 ] 

932 

933 changes_files = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) 

934 

935 for i in [ 

936 "automatic", 

937 "no-binaries", 

938 "comments", 

939 "help", 

940 "manual-reject", 

941 "no-action", 

942 "version", 

943 "trainee", 

944 ]: 

945 key = "Process-New::Options::%s" % i 

946 if key not in cnf: 

947 cnf[key] = "" 

948 

949 queue_name = cnf.get("Process-New::Options::Queue", "new") 

950 new_queue = session.query(PolicyQueue).filter_by(queue_name=queue_name).one() 

951 if len(changes_files) == 0: 

952 uploads = new_queue.uploads 

953 else: 

954 uploads = ( 

955 session.query(PolicyQueueUpload) 

956 .filter_by(policy_queue=new_queue) 

957 .join(DBChange) 

958 .filter(DBChange.changesname.in_(changes_files)) 

959 .all() 

960 ) 

961 

962 Options = cnf.subtree("Process-New::Options") 

963 

964 if Options["Help"]: 

965 usage() 

966 

967 if not Options["No-Action"]: 967 ↛ 973line 967 didn't jump to line 973, because the condition on line 967 was never false

968 try: 

969 Logger = daklog.Logger("process-new") 

970 except OSError: 

971 Options["Trainee"] = "True" 

972 

973 Sections = Section_Completer(session) 

974 Priorities = Priority_Completer(session) 

975 readline.parse_and_bind("tab: complete") 

976 

977 if len(uploads) > 1: 

978 print("Sorting changes...", file=sys.stderr) 

979 uploads = sort_uploads(new_queue, uploads, session, Options["No-Binaries"]) 

980 

981 if Options["Comments"]: 981 ↛ 982line 981 didn't jump to line 982, because the condition on line 981 was never true

982 show_new_comments(uploads, session) 

983 else: 

984 for upload in uploads: 

985 do_pkg(upload, session) 

986 

987 end() 

988 

989 

990################################################################################ 

991 

992 

993if __name__ == "__main__": 

994 main()