Coverage for dak/control_overrides.py: 59%
181 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
1#! /usr/bin/env python3
3"""Bulk manipulation of the overrides"""
4# Copyright (C) 2000, 2001, 2002, 2003, 2006 James Troup <james@nocrew.org>
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20################################################################################
22# On 30 Nov 1998, James Troup wrote:
23#
24# > James Troup<2> <troup2@debian.org>
25# >
26# > James is a clone of James; he's going to take over the world.
27# > After he gets some sleep.
28#
29# Could you clone other things too? Sheep? Llamas? Giant mutant turnips?
30#
31# Your clone will need some help to take over the world, maybe clone up an
32# army of penguins and threaten to unleash them on the world, forcing
33# governments to sway to the new James' will!
34#
35# Yes, I can envision a day when James' duplicate decides to take a horrific
36# vengance on the James that spawned him and unleashes his fury in the form
37# of thousands upon thousands of chickens that look just like Captin Blue
38# Eye! Oh the horror.
39#
40# Now you'll have to were name tags to people can tell you apart, unless of
41# course the new clone is truely evil in which case he should be easy to
42# identify!
43#
44# Jason
45# Chicken. Black. Helicopters.
46# Be afraid.
48# <Pine.LNX.3.96.981130011300.30365Z-100000@wakko>
50################################################################################
52import sys
53import time
54from collections.abc import Iterable
55from typing import TYPE_CHECKING, NoReturn
57import apt_pkg
58from sqlalchemy import sql
60from daklib import daklog, utils
61from daklib.config import Config
62from daklib.dbconn import (
63 DBConn,
64 get_component,
65 get_override_type,
66 get_priorities,
67 get_sections,
68 get_suite,
69)
70from daklib.regexes import re_comments
72if TYPE_CHECKING:
73 from sqlalchemy.orm import Session
75################################################################################
77Logger: daklog.Logger
79################################################################################
82def usage(exit_code=0) -> NoReturn:
83 print(
84 """Usage: dak control-overrides [OPTIONS]
85 -h, --help print this help and exit
87 -c, --component=CMPT list/set overrides by component
88 (contrib,*main,non-free)
89 -s, --suite=SUITE list/set overrides by suite
90 (experimental,stable,testing,*unstable)
91 -t, --type=TYPE list/set overrides by type
92 (*deb,dsc,udeb)
94 -a, --add add overrides (changes and deletions are ignored)
95 -S, --set set overrides
96 -C, --change change overrides (additions and deletions are ignored)
97 -l, --list list overrides
99 -q, --quiet be less verbose
100 -n, --no-action only list the action that would have been done
101 -f, --force also work on untouchable suites
103 starred (*) values are default"""
104 )
105 sys.exit(exit_code)
108################################################################################
111def process_file(
112 file: Iterable[str],
113 suite: str,
114 component: str,
115 otype: str,
116 mode: str,
117 action: bool,
118 session: "Session",
119) -> None:
120 cnf = Config()
122 s = get_suite(suite, session=session)
123 if s is None: 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true
124 utils.fubar("Suite '%s' not recognised." % (suite))
125 suite_id = s.suite_id
127 c = get_component(component, session=session)
128 if c is None: 128 ↛ 129line 128 didn't jump to line 129 because the condition on line 128 was never true
129 utils.fubar("Component '%s' not recognised." % (component))
130 component_id = c.component_id
132 o = get_override_type(otype)
133 if o is None: 133 ↛ 134line 133 didn't jump to line 134 because the condition on line 133 was never true
134 utils.fubar(
135 "Type '%s' not recognised. (Valid types are deb, udeb and dsc.)" % (otype)
136 )
137 type_id = o.overridetype_id
139 # --set is done mostly internal for performance reasons; most
140 # invocations of --set will be updates and making people wait 2-3
141 # minutes while 6000 select+inserts are run needlessly isn't cool.
143 original = {}
144 new = {}
145 c_skipped = 0
146 c_added = 0
147 c_updated = 0
148 c_removed = 0
149 c_error = 0
151 q = session.execute(
152 sql.text(
153 """SELECT o.package, o.priority, o.section, o.maintainer, p.priority, s.section
154 FROM override o, priority p, section s
155 WHERE o.suite = :suiteid AND o.component = :componentid AND o.type = :typeid
156 and o.priority = p.id and o.section = s.id"""
157 ),
158 {"suiteid": suite_id, "componentid": component_id, "typeid": type_id},
159 )
160 for i in q.fetchall():
161 original[i[0]] = i[1:]
163 start_time = time.time()
165 section_cache = get_sections(session)
166 priority_cache = get_priorities(session)
168 # Our session is already in a transaction
170 for line in file:
171 line = re_comments.sub("", line).strip()
172 if line == "": 172 ↛ 173line 172 didn't jump to line 173 because the condition on line 172 was never true
173 continue
175 maintainer_override = None
176 if otype == "dsc":
177 split_line = line.split(None, 2)
178 if len(split_line) == 2: 178 ↛ 180line 178 didn't jump to line 180 because the condition on line 178 was always true
179 (package, section) = split_line
180 elif len(split_line) == 3:
181 (package, section, maintainer_override) = split_line
182 else:
183 utils.warn(
184 "'%s' does not break into 'package section [maintainer-override]'."
185 % (line)
186 )
187 c_error += 1
188 continue
189 priority = "optional"
190 else: # binary or udeb
191 split_line = line.split(None, 3)
192 if len(split_line) == 3: 192 ↛ 194line 192 didn't jump to line 194 because the condition on line 192 was always true
193 (package, priority, section) = split_line
194 elif len(split_line) == 4:
195 (package, priority, section, maintainer_override) = split_line
196 else:
197 utils.warn(
198 "'%s' does not break into 'package priority section [maintainer-override]'."
199 % (line)
200 )
201 c_error += 1
202 continue
204 if section not in section_cache: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true
205 utils.warn(
206 "'%s' is not a valid section. ['%s' in suite %s, component %s]."
207 % (section, package, suite, component)
208 )
209 c_error += 1
210 continue
212 section_id = section_cache[section]
214 if priority not in priority_cache: 214 ↛ 215line 214 didn't jump to line 215 because the condition on line 214 was never true
215 utils.warn(
216 "'%s' is not a valid priority. ['%s' in suite %s, component %s]."
217 % (priority, package, suite, component)
218 )
219 c_error += 1
220 continue
222 priority_id = priority_cache[priority]
224 if package in new: 224 ↛ 225line 224 didn't jump to line 225 because the condition on line 224 was never true
225 utils.warn(
226 "Can't insert duplicate entry for '%s'; ignoring all but the first. [suite %s, component %s]"
227 % (package, suite, component)
228 )
229 c_error += 1
230 continue
231 new[package] = ""
233 if package in original: 233 ↛ 234line 233 didn't jump to line 234
234 (
235 old_priority_id,
236 old_section_id,
237 old_maintainer_override,
238 old_priority,
239 old_section,
240 ) = original[package]
241 if (
242 mode == "add"
243 or old_priority_id == priority_id
244 and old_section_id == section_id
245 and old_maintainer_override == maintainer_override
246 ):
247 # If it's unchanged or we're in 'add only' mode, ignore it
248 c_skipped += 1
249 continue
250 else:
251 # If it's changed, delete the old one so we can
252 # reinsert it with the new information
253 c_updated += 1
254 if action:
255 session.execute(
256 sql.text(
257 """DELETE FROM override WHERE suite = :suite AND component = :component
258 AND package = :package AND type = :typeid"""
259 ),
260 {
261 "suite": suite_id,
262 "component": component_id,
263 "package": package,
264 "typeid": type_id,
265 },
266 )
268 # Log changes
269 if old_priority_id != priority_id:
270 Logger.log(["changed priority", package, old_priority, priority])
271 if old_section_id != section_id:
272 Logger.log(["changed section", package, old_section, section])
273 if old_maintainer_override != maintainer_override:
274 Logger.log(
275 [
276 "changed maintainer override",
277 package,
278 old_maintainer_override,
279 maintainer_override,
280 ]
281 )
282 update_p = 1
283 elif mode == "change": 283 ↛ 285line 283 didn't jump to line 285 because the condition on line 283 was never true
284 # Ignore additions in 'change only' mode
285 c_skipped += 1
286 continue
287 else:
288 c_added += 1
289 update_p = 0
291 if action: 291 ↛ 315line 291 didn't jump to line 315 because the condition on line 291 was always true
292 if not maintainer_override: 292 ↛ 295line 292 didn't jump to line 295 because the condition on line 292 was always true
293 m_o = None
294 else:
295 m_o = maintainer_override
296 session.execute(
297 sql.text(
298 """INSERT INTO override (suite, component, type, package,
299 priority, section, maintainer)
300 VALUES (:suiteid, :componentid, :typeid,
301 :package, :priorityid, :sectionid,
302 :maintainer)"""
303 ),
304 {
305 "suiteid": suite_id,
306 "componentid": component_id,
307 "typeid": type_id,
308 "package": package,
309 "priorityid": priority_id,
310 "sectionid": section_id,
311 "maintainer": m_o,
312 },
313 )
315 if not update_p: 315 ↛ 170line 315 didn't jump to line 170 because the condition on line 315 was always true
316 Logger.log(
317 [
318 "new override",
319 suite,
320 component,
321 otype,
322 package,
323 priority,
324 section,
325 maintainer_override,
326 ]
327 )
329 if mode == "set": 329 ↛ 331line 329 didn't jump to line 331 because the condition on line 329 was never true
330 # Delete any packages which were removed
331 for package in original.keys():
332 if package not in new:
333 if action:
334 session.execute(
335 sql.text(
336 """DELETE FROM override
337 WHERE suite = :suiteid AND component = :componentid
338 AND package = :package AND type = :typeid"""
339 ),
340 {
341 "suiteid": suite_id,
342 "componentid": component_id,
343 "package": package,
344 "typeid": type_id,
345 },
346 )
347 c_removed += 1
348 Logger.log(["removed override", suite, component, otype, package])
350 if action: 350 ↛ 353line 350 didn't jump to line 353 because the condition on line 350 was always true
351 session.commit()
353 if not cnf["Control-Overrides::Options::Quiet"]: 353 ↛ 366line 353 didn't jump to line 366 because the condition on line 353 was always true
354 print(
355 "Done in %d seconds. [Updated = %d, Added = %d, Removed = %d, Skipped = %d, Errors = %d]"
356 % (
357 int(time.time() - start_time),
358 c_updated,
359 c_added,
360 c_removed,
361 c_skipped,
362 c_error,
363 )
364 )
366 Logger.log(["set complete", c_updated, c_added, c_removed, c_skipped, c_error])
369################################################################################
372def list_overrides(suite: str, component: str, otype: str, session: "Session") -> None:
373 dat = {}
374 s = get_suite(suite, session)
375 if s is None: 375 ↛ 376line 375 didn't jump to line 376 because the condition on line 375 was never true
376 utils.fubar("Suite '%s' not recognised." % (suite))
378 dat["suiteid"] = s.suite_id
380 c = get_component(component, session)
381 if c is None: 381 ↛ 382line 381 didn't jump to line 382 because the condition on line 381 was never true
382 utils.fubar("Component '%s' not recognised." % (component))
384 dat["componentid"] = c.component_id
386 o = get_override_type(otype)
387 if o is None: 387 ↛ 388line 387 didn't jump to line 388 because the condition on line 387 was never true
388 utils.fubar(
389 "Type '%s' not recognised. (Valid types are deb, udeb and dsc)" % (otype)
390 )
392 dat["typeid"] = o.overridetype_id
394 if otype == "dsc": 394 ↛ 395line 394 didn't jump to line 395 because the condition on line 394 was never true
395 q = session.execute(
396 sql.text(
397 """SELECT o.package, s.section, o.maintainer FROM override o, section s
398 WHERE o.suite = :suiteid AND o.component = :componentid
399 AND o.type = :typeid AND o.section = s.id
400 ORDER BY s.section, o.package"""
401 ),
402 dat,
403 )
404 for i in q.fetchall():
405 print(utils.result_join(i))
406 else:
407 q = session.execute(
408 sql.text(
409 """SELECT o.package, p.priority, s.section, o.maintainer, p.level
410 FROM override o, priority p, section s
411 WHERE o.suite = :suiteid AND o.component = :componentid
412 AND o.type = :typeid AND o.priority = p.id AND o.section = s.id
413 ORDER BY s.section, p.level, o.package"""
414 ),
415 dat,
416 )
417 for i in q.fetchall(): 417 ↛ 418line 417 didn't jump to line 418 because the loop on line 417 never started
418 print(utils.result_join(i[:-1]))
421################################################################################
424def main() -> None:
425 global Logger
427 cnf = Config()
428 Arguments = [
429 ("a", "add", "Control-Overrides::Options::Add"),
430 ("c", "component", "Control-Overrides::Options::Component", "HasArg"),
431 ("h", "help", "Control-Overrides::Options::Help"),
432 ("l", "list", "Control-Overrides::Options::List"),
433 ("q", "quiet", "Control-Overrides::Options::Quiet"),
434 ("s", "suite", "Control-Overrides::Options::Suite", "HasArg"),
435 ("S", "set", "Control-Overrides::Options::Set"),
436 ("C", "change", "Control-Overrides::Options::Change"),
437 ("n", "no-action", "Control-Overrides::Options::No-Action"),
438 ("t", "type", "Control-Overrides::Options::Type", "HasArg"),
439 ("f", "force", "Control-Overrides::Options::Force"),
440 ]
442 # Default arguments
443 for i in ["add", "help", "list", "quiet", "set", "change", "no-action"]:
444 key = "Control-Overrides::Options::%s" % i
445 if key not in cnf: 445 ↛ 443line 445 didn't jump to line 443 because the condition on line 445 was always true
446 cnf[key] = ""
447 if "Control-Overrides::Options::Component" not in cnf: 447 ↛ 449line 447 didn't jump to line 449 because the condition on line 447 was always true
448 cnf["Control-Overrides::Options::Component"] = "main"
449 if "Control-Overrides::Options::Suite" not in cnf: 449 ↛ 451line 449 didn't jump to line 451 because the condition on line 449 was always true
450 cnf["Control-Overrides::Options::Suite"] = "unstable"
451 if "Control-Overrides::Options::Type" not in cnf: 451 ↛ 454line 451 didn't jump to line 454 because the condition on line 451 was always true
452 cnf["Control-Overrides::Options::Type"] = "deb"
454 file_list = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
456 if cnf["Control-Overrides::Options::Help"]:
457 usage()
459 session = DBConn().session()
461 mode = None
462 for i in ["add", "list", "set", "change"]:
463 if cnf["Control-Overrides::Options::%s" % (i)]:
464 if mode: 464 ↛ 465line 464 didn't jump to line 465 because the condition on line 464 was never true
465 utils.fubar("Can not perform more than one action at once.")
466 mode = i
468 # Need an action...
469 if mode is None: 469 ↛ 470line 469 didn't jump to line 470 because the condition on line 469 was never true
470 utils.fubar("No action specified.")
472 (suite_name, component, otype) = (
473 cnf["Control-Overrides::Options::Suite"],
474 cnf["Control-Overrides::Options::Component"],
475 cnf["Control-Overrides::Options::Type"],
476 )
478 if mode == "list":
479 list_overrides(suite_name, component, otype, session)
480 else:
481 suite = get_suite(suite_name, session)
482 assert suite is not None
483 if suite.untouchable and not cnf["Control-Overrides::Options::Force"]: 483 ↛ 484line 483 didn't jump to line 484 because the condition on line 483 was never true
484 utils.fubar("%s: suite is untouchable" % suite_name)
486 action = True
487 if cnf["Control-Overrides::Options::No-Action"]: 487 ↛ 488line 487 didn't jump to line 488 because the condition on line 487 was never true
488 utils.warn("In No-Action Mode")
489 action = False
491 Logger = daklog.Logger("control-overrides", mode)
492 if file_list: 492 ↛ 493line 492 didn't jump to line 493 because the condition on line 492 was never true
493 for f in file_list:
494 process_file(
495 open(f), suite_name, component, otype, mode, action, session
496 )
497 else:
498 process_file(sys.stdin, suite_name, component, otype, mode, action, session)
499 Logger.close()
502#######################################################################################
505if __name__ == "__main__":
506 main()