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
« prev ^ index » next coverage.py v7.6.0, created at 2026-08-03 16:46 +0000
1"""Bulk manipulation of the overrides"""
3# Copyright (C) 2000, 2001, 2002, 2003, 2006 James Troup <james@nocrew.org>
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.
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.
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
19################################################################################
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.
47# <Pine.LNX.3.96.981130011300.30365Z-100000@wakko>
49################################################################################
51import sys
52import time
53from collections.abc import Iterable
54from typing import TYPE_CHECKING, NoReturn
56import apt_pkg
57from sqlalchemy import sql
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
71if TYPE_CHECKING:
72 from sqlalchemy.orm import Session
74################################################################################
76Logger: daklog.Logger
78################################################################################
81def usage(exit_code=0) -> NoReturn:
82 print(
83 """Usage: dak control-overrides [OPTIONS]
84 -h, --help print this help and exit
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)
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
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
102 starred (*) values are default"""
103 )
104 sys.exit(exit_code)
107################################################################################
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()
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
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
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
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.
142 original = {}
143 new = {}
144 c_skipped = 0
145 c_added = 0
146 c_updated = 0
147 c_removed = 0
148 c_error = 0
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:]
162 start_time = time.time()
164 section_cache = get_sections(session)
165 priority_cache = get_priorities(session)
167 # Our session is already in a transaction
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
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
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
211 section_id = section_cache[section]
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
221 priority_id = priority_cache[priority]
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] = ""
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 )
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
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 )
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 )
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])
349 if action: 349 ↛ 352line 349 didn't jump to line 352 because the condition on line 349 was always true
350 session.commit()
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 )
365 Logger.log(["set complete", c_updated, c_added, c_removed, c_skipped, c_error])
368################################################################################
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))
377 dat["suiteid"] = s.suite_id
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))
383 dat["componentid"] = c.component_id
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 )
391 dat["typeid"] = o.overridetype_id
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]))
420################################################################################
423def main() -> None:
424 global Logger
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 ]
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"
453 file_list = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
455 if cnf["Control-Overrides::Options::Help"]:
456 usage()
458 session = DBConn().session()
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
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.")
471 (suite_name, component, otype) = (
472 cnf["Control-Overrides::Options::Suite"],
473 cnf["Control-Overrides::Options::Component"],
474 cnf["Control-Overrides::Options::Type"],
475 )
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)
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
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()