Coverage for dak/control_overrides.py: 59%

181 statements  

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

1"""Bulk manipulation of the overrides""" 

2 

3# Copyright (C) 2000, 2001, 2002, 2003, 2006 James Troup <james@nocrew.org> 

4 

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

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

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

8# (at your option) any later version. 

9 

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

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

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

13# GNU General Public License for more details. 

14 

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

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

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

18 

19################################################################################ 

20 

21# On 30 Nov 1998, James Troup wrote: 

22# 

23# > James Troup<2> <troup2@debian.org> 

24# > 

25# > James is a clone of James; he's going to take over the world. 

26# > After he gets some sleep. 

27# 

28# Could you clone other things too? Sheep? Llamas? Giant mutant turnips? 

29# 

30# Your clone will need some help to take over the world, maybe clone up an 

31# army of penguins and threaten to unleash them on the world, forcing 

32# governments to sway to the new James' will! 

33# 

34# Yes, I can envision a day when James' duplicate decides to take a horrific 

35# vengance on the James that spawned him and unleashes his fury in the form 

36# of thousands upon thousands of chickens that look just like Captin Blue 

37# Eye! Oh the horror. 

38# 

39# Now you'll have to were name tags to people can tell you apart, unless of 

40# course the new clone is truely evil in which case he should be easy to 

41# identify! 

42# 

43# Jason 

44# Chicken. Black. Helicopters. 

45# Be afraid. 

46 

47# <Pine.LNX.3.96.981130011300.30365Z-100000@wakko> 

48 

49################################################################################ 

50 

51import sys 

52import time 

53from collections.abc import Iterable 

54from typing import TYPE_CHECKING, NoReturn 

55 

56import apt_pkg 

57from sqlalchemy import sql 

58 

59from daklib import daklog, utils 

60from daklib.config import Config 

61from daklib.dbconn import ( 

62 DBConn, 

63 get_component, 

64 get_override_type, 

65 get_priorities, 

66 get_sections, 

67 get_suite, 

68) 

69from daklib.regexes import re_comments 

70 

71if TYPE_CHECKING: 

72 from sqlalchemy.orm import Session 

73 

74################################################################################ 

75 

76Logger: daklog.Logger 

77 

78################################################################################ 

79 

80 

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

82 print( 

83 """Usage: dak control-overrides [OPTIONS] 

84 -h, --help print this help and exit 

85 

86 -c, --component=CMPT list/set overrides by component 

87 (contrib,*main,non-free) 

88 -s, --suite=SUITE list/set overrides by suite 

89 (experimental,stable,testing,*unstable) 

90 -t, --type=TYPE list/set overrides by type 

91 (*deb,dsc,udeb) 

92 

93 -a, --add add overrides (changes and deletions are ignored) 

94 -S, --set set overrides 

95 -C, --change change overrides (additions and deletions are ignored) 

96 -l, --list list overrides 

97 

98 -q, --quiet be less verbose 

99 -n, --no-action only list the action that would have been done 

100 -f, --force also work on untouchable suites 

101 

102 starred (*) values are default""" 

103 ) 

104 sys.exit(exit_code) 

105 

106 

107################################################################################ 

108 

109 

110def process_file( 

111 file: Iterable[str], 

112 suite: str, 

113 component: str, 

114 otype: str, 

115 mode: str, 

116 action: bool, 

117 session: "Session", 

118) -> None: 

119 cnf = Config() 

120 

121 s = get_suite(suite, session=session) 

122 if s is None: 122 ↛ 123line 122 didn't jump to line 123 because the condition on line 122 was never true

123 utils.fubar("Suite '%s' not recognised." % (suite)) 

124 suite_id = s.suite_id 

125 

126 c = get_component(component, session=session) 

127 if c is None: 127 ↛ 128line 127 didn't jump to line 128 because the condition on line 127 was never true

128 utils.fubar("Component '%s' not recognised." % (component)) 

129 component_id = c.component_id 

130 

131 o = get_override_type(otype) 

132 if o is None: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true

133 utils.fubar( 

134 "Type '%s' not recognised. (Valid types are deb, udeb and dsc.)" % (otype) 

135 ) 

136 type_id = o.overridetype_id 

137 

138 # --set is done mostly internal for performance reasons; most 

139 # invocations of --set will be updates and making people wait 2-3 

140 # minutes while 6000 select+inserts are run needlessly isn't cool. 

141 

142 original = {} 

143 new = {} 

144 c_skipped = 0 

145 c_added = 0 

146 c_updated = 0 

147 c_removed = 0 

148 c_error = 0 

149 

150 q = session.execute( 

151 sql.text( 

152 """SELECT o.package, o.priority, o.section, o.maintainer, p.priority, s.section 

153 FROM override o, priority p, section s 

154 WHERE o.suite = :suiteid AND o.component = :componentid AND o.type = :typeid 

155 and o.priority = p.id and o.section = s.id""" 

156 ), 

157 {"suiteid": suite_id, "componentid": component_id, "typeid": type_id}, 

158 ) 

159 for i in q.fetchall(): 

160 original[i[0]] = i[1:] 

161 

162 start_time = time.time() 

163 

164 section_cache = get_sections(session) 

165 priority_cache = get_priorities(session) 

166 

167 # Our session is already in a transaction 

168 

169 for line in file: 

170 line = re_comments.sub("", line).strip() 

171 if line == "": 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true

172 continue 

173 

174 maintainer_override = None 

175 if otype == "dsc": 

176 split_line = line.split(None, 2) 

177 if len(split_line) == 2: 177 ↛ 179line 177 didn't jump to line 179 because the condition on line 177 was always true

178 (package, section) = split_line 

179 elif len(split_line) == 3: 

180 (package, section, maintainer_override) = split_line 

181 else: 

182 utils.warn( 

183 "'%s' does not break into 'package section [maintainer-override]'." 

184 % (line) 

185 ) 

186 c_error += 1 

187 continue 

188 priority = "optional" 

189 else: # binary or udeb 

190 split_line = line.split(None, 3) 

191 if len(split_line) == 3: 191 ↛ 193line 191 didn't jump to line 193 because the condition on line 191 was always true

192 (package, priority, section) = split_line 

193 elif len(split_line) == 4: 

194 (package, priority, section, maintainer_override) = split_line 

195 else: 

196 utils.warn( 

197 "'%s' does not break into 'package priority section [maintainer-override]'." 

198 % (line) 

199 ) 

200 c_error += 1 

201 continue 

202 

203 if section not in section_cache: 203 ↛ 204line 203 didn't jump to line 204 because the condition on line 203 was never true

204 utils.warn( 

205 "'%s' is not a valid section. ['%s' in suite %s, component %s]." 

206 % (section, package, suite, component) 

207 ) 

208 c_error += 1 

209 continue 

210 

211 section_id = section_cache[section] 

212 

213 if priority not in priority_cache: 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true

214 utils.warn( 

215 "'%s' is not a valid priority. ['%s' in suite %s, component %s]." 

216 % (priority, package, suite, component) 

217 ) 

218 c_error += 1 

219 continue 

220 

221 priority_id = priority_cache[priority] 

222 

223 if package in new: 223 ↛ 224line 223 didn't jump to line 224 because the condition on line 223 was never true

224 utils.warn( 

225 "Can't insert duplicate entry for '%s'; ignoring all but the first. [suite %s, component %s]" 

226 % (package, suite, component) 

227 ) 

228 c_error += 1 

229 continue 

230 new[package] = "" 

231 

232 if package in original: 232 ↛ 233line 232 didn't jump to line 233

233 ( 

234 old_priority_id, 

235 old_section_id, 

236 old_maintainer_override, 

237 old_priority, 

238 old_section, 

239 ) = original[package] 

240 if ( 

241 mode == "add" 

242 or old_priority_id == priority_id 

243 and old_section_id == section_id 

244 and old_maintainer_override == maintainer_override 

245 ): 

246 # If it's unchanged or we're in 'add only' mode, ignore it 

247 c_skipped += 1 

248 continue 

249 else: 

250 # If it's changed, delete the old one so we can 

251 # reinsert it with the new information 

252 c_updated += 1 

253 if action: 

254 session.execute( 

255 sql.text( 

256 """DELETE FROM override WHERE suite = :suite AND component = :component 

257 AND package = :package AND type = :typeid""" 

258 ), 

259 { 

260 "suite": suite_id, 

261 "component": component_id, 

262 "package": package, 

263 "typeid": type_id, 

264 }, 

265 ) 

266 

267 # Log changes 

268 if old_priority_id != priority_id: 

269 Logger.log(["changed priority", package, old_priority, priority]) 

270 if old_section_id != section_id: 

271 Logger.log(["changed section", package, old_section, section]) 

272 if old_maintainer_override != maintainer_override: 

273 Logger.log( 

274 [ 

275 "changed maintainer override", 

276 package, 

277 old_maintainer_override, 

278 maintainer_override, 

279 ] 

280 ) 

281 update_p = 1 

282 elif mode == "change": 282 ↛ 284line 282 didn't jump to line 284 because the condition on line 282 was never true

283 # Ignore additions in 'change only' mode 

284 c_skipped += 1 

285 continue 

286 else: 

287 c_added += 1 

288 update_p = 0 

289 

290 if action: 290 ↛ 314line 290 didn't jump to line 314 because the condition on line 290 was always true

291 if not maintainer_override: 291 ↛ 294line 291 didn't jump to line 294 because the condition on line 291 was always true

292 m_o = None 

293 else: 

294 m_o = maintainer_override 

295 session.execute( 

296 sql.text( 

297 """INSERT INTO override (suite, component, type, package, 

298 priority, section, maintainer) 

299 VALUES (:suiteid, :componentid, :typeid, 

300 :package, :priorityid, :sectionid, 

301 :maintainer)""" 

302 ), 

303 { 

304 "suiteid": suite_id, 

305 "componentid": component_id, 

306 "typeid": type_id, 

307 "package": package, 

308 "priorityid": priority_id, 

309 "sectionid": section_id, 

310 "maintainer": m_o, 

311 }, 

312 ) 

313 

314 if not update_p: 314 ↛ 169line 314 didn't jump to line 169 because the condition on line 314 was always true

315 Logger.log( 

316 [ 

317 "new override", 

318 suite, 

319 component, 

320 otype, 

321 package, 

322 priority, 

323 section, 

324 maintainer_override, 

325 ] 

326 ) 

327 

328 if mode == "set": 328 ↛ 330line 328 didn't jump to line 330 because the condition on line 328 was never true

329 # Delete any packages which were removed 

330 for package in original.keys(): 

331 if package not in new: 

332 if action: 

333 session.execute( 

334 sql.text( 

335 """DELETE FROM override 

336 WHERE suite = :suiteid AND component = :componentid 

337 AND package = :package AND type = :typeid""" 

338 ), 

339 { 

340 "suiteid": suite_id, 

341 "componentid": component_id, 

342 "package": package, 

343 "typeid": type_id, 

344 }, 

345 ) 

346 c_removed += 1 

347 Logger.log(["removed override", suite, component, otype, package]) 

348 

349 if action: 349 ↛ 352line 349 didn't jump to line 352 because the condition on line 349 was always true

350 session.commit() 

351 

352 if not cnf["Control-Overrides::Options::Quiet"]: 352 ↛ 365line 352 didn't jump to line 365 because the condition on line 352 was always true

353 print( 

354 "Done in %d seconds. [Updated = %d, Added = %d, Removed = %d, Skipped = %d, Errors = %d]" 

355 % ( 

356 int(time.time() - start_time), 

357 c_updated, 

358 c_added, 

359 c_removed, 

360 c_skipped, 

361 c_error, 

362 ) 

363 ) 

364 

365 Logger.log(["set complete", c_updated, c_added, c_removed, c_skipped, c_error]) 

366 

367 

368################################################################################ 

369 

370 

371def list_overrides(suite: str, component: str, otype: str, session: "Session") -> None: 

372 dat = {} 

373 s = get_suite(suite, session) 

374 if s is None: 374 ↛ 375line 374 didn't jump to line 375 because the condition on line 374 was never true

375 utils.fubar("Suite '%s' not recognised." % (suite)) 

376 

377 dat["suiteid"] = s.suite_id 

378 

379 c = get_component(component, session) 

380 if c is None: 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true

381 utils.fubar("Component '%s' not recognised." % (component)) 

382 

383 dat["componentid"] = c.component_id 

384 

385 o = get_override_type(otype) 

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

387 utils.fubar( 

388 "Type '%s' not recognised. (Valid types are deb, udeb and dsc)" % (otype) 

389 ) 

390 

391 dat["typeid"] = o.overridetype_id 

392 

393 if otype == "dsc": 393 ↛ 394line 393 didn't jump to line 394 because the condition on line 393 was never true

394 q = session.execute( 

395 sql.text( 

396 """SELECT o.package, s.section, o.maintainer FROM override o, section s 

397 WHERE o.suite = :suiteid AND o.component = :componentid 

398 AND o.type = :typeid AND o.section = s.id 

399 ORDER BY s.section, o.package""" 

400 ), 

401 dat, 

402 ) 

403 for i in q.fetchall(): 

404 print(utils.result_join(i)) 

405 else: 

406 q = session.execute( 

407 sql.text( 

408 """SELECT o.package, p.priority, s.section, o.maintainer, p.level 

409 FROM override o, priority p, section s 

410 WHERE o.suite = :suiteid AND o.component = :componentid 

411 AND o.type = :typeid AND o.priority = p.id AND o.section = s.id 

412 ORDER BY s.section, p.level, o.package""" 

413 ), 

414 dat, 

415 ) 

416 for i in q.fetchall(): 416 ↛ 417line 416 didn't jump to line 417 because the loop on line 416 never started

417 print(utils.result_join(i[:-1])) 

418 

419 

420################################################################################ 

421 

422 

423def main() -> None: 

424 global Logger 

425 

426 cnf = Config() 

427 Arguments = [ 

428 ("a", "add", "Control-Overrides::Options::Add"), 

429 ("c", "component", "Control-Overrides::Options::Component", "HasArg"), 

430 ("h", "help", "Control-Overrides::Options::Help"), 

431 ("l", "list", "Control-Overrides::Options::List"), 

432 ("q", "quiet", "Control-Overrides::Options::Quiet"), 

433 ("s", "suite", "Control-Overrides::Options::Suite", "HasArg"), 

434 ("S", "set", "Control-Overrides::Options::Set"), 

435 ("C", "change", "Control-Overrides::Options::Change"), 

436 ("n", "no-action", "Control-Overrides::Options::No-Action"), 

437 ("t", "type", "Control-Overrides::Options::Type", "HasArg"), 

438 ("f", "force", "Control-Overrides::Options::Force"), 

439 ] 

440 

441 # Default arguments 

442 for i in ["add", "help", "list", "quiet", "set", "change", "no-action"]: 

443 key = "Control-Overrides::Options::%s" % i 

444 if key not in cnf: 444 ↛ 442line 444 didn't jump to line 442 because the condition on line 444 was always true

445 cnf[key] = "" 

446 if "Control-Overrides::Options::Component" not in cnf: 446 ↛ 448line 446 didn't jump to line 448 because the condition on line 446 was always true

447 cnf["Control-Overrides::Options::Component"] = "main" 

448 if "Control-Overrides::Options::Suite" not in cnf: 448 ↛ 450line 448 didn't jump to line 450 because the condition on line 448 was always true

449 cnf["Control-Overrides::Options::Suite"] = "unstable" 

450 if "Control-Overrides::Options::Type" not in cnf: 450 ↛ 453line 450 didn't jump to line 453 because the condition on line 450 was always true

451 cnf["Control-Overrides::Options::Type"] = "deb" 

452 

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

454 

455 if cnf["Control-Overrides::Options::Help"]: 

456 usage() 

457 

458 session = DBConn().session() 

459 

460 mode = None 

461 for i in ["add", "list", "set", "change"]: 

462 if cnf["Control-Overrides::Options::%s" % (i)]: 

463 if mode: 463 ↛ 464line 463 didn't jump to line 464 because the condition on line 463 was never true

464 utils.fubar("Can not perform more than one action at once.") 

465 mode = i 

466 

467 # Need an action... 

468 if mode is None: 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true

469 utils.fubar("No action specified.") 

470 

471 (suite_name, component, otype) = ( 

472 cnf["Control-Overrides::Options::Suite"], 

473 cnf["Control-Overrides::Options::Component"], 

474 cnf["Control-Overrides::Options::Type"], 

475 ) 

476 

477 if mode == "list": 

478 list_overrides(suite_name, component, otype, session) 

479 else: 

480 suite = get_suite(suite_name, session) 

481 assert suite is not None 

482 if suite.untouchable and not cnf["Control-Overrides::Options::Force"]: 482 ↛ 483line 482 didn't jump to line 483 because the condition on line 482 was never true

483 utils.fubar("%s: suite is untouchable" % suite_name) 

484 

485 action = True 

486 if cnf["Control-Overrides::Options::No-Action"]: 486 ↛ 487line 486 didn't jump to line 487 because the condition on line 486 was never true

487 utils.warn("In No-Action Mode") 

488 action = False 

489 

490 Logger = daklog.Logger("control-overrides", mode) 

491 if file_list: 491 ↛ 492line 491 didn't jump to line 492 because the condition on line 491 was never true

492 for f in file_list: 

493 process_file( 

494 open(f), suite_name, component, otype, mode, action, session 

495 ) 

496 else: 

497 process_file(sys.stdin, suite_name, component, otype, mode, action, session) 

498 Logger.close()