1#! /usr/bin/env python3
3"""
4Remove obsolete source and binary associations from suites.
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2009 Torsten Werner <twerner@debian.org>
8@license: GNU General Public License version 2 or later
9"""
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.
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.
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
25import sys
27import apt_pkg
28from sqlalchemy.sql import exists, text
29from tabulate import tabulate
31from daklib import daklog, utils
32from daklib.config import Config
33from daklib.dbconn import DBConn, PolicyQueue, Suite
35Options = None
36Logger = None
39def retrieve_associations(suites, session):
40 return session.execute(
41 text(
42 """
43WITH
44 -- Provide (source, suite) tuple of all source packages to remain
45 remain_source AS (
46 SELECT
47 *
48 FROM (
49 SELECT
50 source.id AS source_id,
51 src_associations.suite AS suite_id,
52 -- generate rank over versions of a source package in one suite
53 -- "1" being the newest
54 dense_rank() OVER (
55 PARTITION BY source.source, src_associations.suite
56 ORDER BY source.version DESC
57 ) AS version_rank
58 FROM
59 source
60 INNER JOIN src_associations ON
61 src_associations.source = source.id
62 AND src_associations.suite = ANY(:suite_ids)
63 ) AS source_ranked
64 WHERE
65 version_rank = 1
66 ),
67 -- Provide (source, arch, suite) tuple of all binary packages to remain
68 remain_binaries AS (
69 SELECT
70 *
71 FROM (
72 SELECT
73 binaries.id,
74 binaries.architecture AS arch_id,
75 bin_associations.suite AS suite_id,
76 source.id AS source_id,
77 architecture.arch_string AS arch,
78 -- arch of newest version
79 first_value(architecture.arch_string) OVER (
80 PARTITION BY binaries.package, bin_associations.suite
81 ORDER BY binaries.version DESC
82 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
83 ) as arch_first,
84 -- generate rank over versions of a source package in one suite
85 -- "1" being the newest
86 -- if newest package is arch-any, we use the rank only over current arch
87 dense_rank() OVER (
88 PARTITION BY binaries.package, binaries.architecture, bin_associations.suite
89 ORDER BY binaries.version DESC
90 ) AS version_rank_any,
91 -- if newest package is arch-all, we use the rank over all arches
92 -- this makes it possible to replace all by any and any by all
93 dense_rank() OVER (
94 PARTITION BY binaries.package, bin_associations.suite
95 ORDER BY binaries.version DESC
96 ) AS version_rank_all
97 FROM
98 binaries
99 INNER JOIN source ON source.id = binaries.source
100 INNER JOIN bin_associations ON
101 bin_associations.bin = binaries.id
102 AND bin_associations.suite = ANY(:suite_ids)
103 INNER JOIN architecture ON architecture.id = binaries.architecture
104 ) AS source_rank
105 WHERE
106 -- we only want to retain the newest of each
107 CASE
108 WHEN arch != 'all' AND arch_first != 'all' THEN version_rank_any = 1
109 ELSE version_rank_all = 1
110 END
111 ),
112 -- Figure out which source we should remove
113 -- A binary forces the corresponding source to remain
114 dominate_source AS (
115 SELECT
116 source.source AS source_package,
117 source.version AS source_version,
118 source.source AS package,
119 source.version,
120 'source'::text AS arch,
121 suite.suite_name AS suite,
122 src_associations.id AS assoc_id
123 FROM
124 source
125 INNER JOIN src_associations ON
126 src_associations.source = source.id
127 AND src_associations.suite = ANY(:suite_ids)
128 INNER join suite ON suite.id = src_associations.suite
129 LEFT JOIN remain_binaries ON
130 remain_binaries.source_id = source.id
131 AND remain_binaries.suite_id = src_associations.suite
132 LEFT JOIN remain_source ON
133 remain_source.source_id = source.id
134 AND remain_source.suite_id = src_associations.suite
135 WHERE
136 remain_binaries.source_id IS NULL
137 AND remain_source.source_id IS NULL
138 ),
139 -- Figure out which arch-any binaries we should remove
140 dominate_binaries AS (
141 SELECT
142 source.source AS source_package,
143 source.version AS source_version,
144 binaries.package AS package,
145 binaries.version,
146 architecture.arch_string AS arch,
147 suite.suite_name AS suite,
148 bin_associations.id AS assoc_id
149 FROM
150 binaries
151 INNER JOIN source ON source.id = binaries.source
152 INNER JOIN bin_associations ON
153 bin_associations.bin = binaries.id
154 AND bin_associations.suite = ANY(:suite_ids)
155 INNER JOIN architecture ON architecture.id = binaries.architecture
156 INNER join suite ON suite.id = bin_associations.suite
157 LEFT JOIN remain_binaries ON
158 remain_binaries.id = binaries.id
159 AND remain_binaries.arch_id = binaries.architecture
160 AND remain_binaries.suite_id = bin_associations.suite
161 WHERE
162 remain_binaries.source_id IS NULL
163 AND binaries.architecture != (SELECT id from architecture WHERE arch_string = 'all')
164 ),
165 -- Figure out which arch-all binaries we should remove
166 -- A arch-any binary forces the related arch-all binaries to remain
167 dominate_binaries_all AS (
168 SELECT
169 source.source AS source_package,
170 source.version AS source_version,
171 binaries.package AS package,
172 binaries.version,
173 architecture.arch_string AS arch,
174 suite.suite_name AS suite,
175 bin_associations.id AS assoc_id
176 FROM
177 binaries
178 INNER JOIN source ON source.id = binaries.source
179 INNER JOIN bin_associations ON
180 bin_associations.bin = binaries.id
181 AND bin_associations.suite = ANY(:suite_ids)
182 INNER JOIN architecture ON architecture.id = binaries.architecture
183 INNER join suite ON suite.id = bin_associations.suite
184 LEFT JOIN remain_binaries ON
185 remain_binaries.id = binaries.id
186 AND remain_binaries.arch_id = binaries.architecture
187 AND remain_binaries.suite_id = bin_associations.suite
188 LEFT JOIN remain_binaries AS remain_binaries_any ON
189 remain_binaries_any.source_id = source.id
190 AND remain_binaries_any.suite_id = bin_associations.suite
191 AND remain_binaries_any.arch_id != (SELECT id from architecture WHERE arch_string = 'all')
192 WHERE
193 remain_binaries.source_id IS NULL
194 AND remain_binaries_any.source_id IS NULL
195 AND binaries.architecture = (SELECT id from architecture WHERE arch_string = 'all')
196 )
197SELECT
198 *
199 FROM
200 dominate_source
201 UNION SELECT
202 *
203 FROM
204 dominate_binaries
205 UNION SELECT
206 *
207 FROM
208 dominate_binaries_all
209 ORDER BY
210 source_package, source_version, package, version, arch, suite
211"""
212 ).params(
213 suite_ids=[s.suite_id for s in suites],
214 )
215 )
218def delete_associations_table(table, ids, session):
219 result = session.execute(
220 text(
221 """
222 DELETE
223 FROM {}
224 WHERE id = ANY(:assoc_ids)
225 """.format(
226 table
227 )
228 ).params(
229 assoc_ids=list(ids),
230 )
231 )
233 assert result.rowcount == len(
234 ids
235 ), "Rows deleted are not equal to deletion requests"
238def delete_associations(assocs, session):
239 ids_bin = set()
240 ids_src = set()
242 for e in assocs:
243 Logger.log(["newer", e.package, e.version, e.suite, e.arch, e.assoc_id])
245 if e.arch == "source":
246 ids_src.add(e.assoc_id)
247 else:
248 ids_bin.add(e.assoc_id)
250 delete_associations_table("bin_associations", ids_bin, session)
251 delete_associations_table("src_associations", ids_src, session)
254def usage():
255 print(
256 """Usage: dak dominate [OPTIONS]
257Remove obsolete source and binary associations from suites.
259 -s, --suite=SUITE act on this suite
260 -h, --help show this help and exit
261 -n, --no-action don't commit changes
262 -f, --force also clean up untouchable suites
264SUITE can be comma (or space) separated list, e.g.
265 --suite=testing,unstable"""
266 )
267 sys.exit()
270def main():
271 global Options, Logger
272 cnf = Config()
273 Arguments = [
274 ("h", "help", "Obsolete::Options::Help"),
275 ("s", "suite", "Obsolete::Options::Suite", "HasArg"),
276 ("n", "no-action", "Obsolete::Options::No-Action"),
277 ("f", "force", "Obsolete::Options::Force"),
278 ]
279 cnf["Obsolete::Options::Help"] = ""
280 cnf["Obsolete::Options::No-Action"] = ""
281 cnf["Obsolete::Options::Force"] = ""
282 apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
283 Options = cnf.subtree("Obsolete::Options")
284 if Options["Help"]:
285 usage()
287 if not Options["No-Action"]: 287 ↛ 289line 287 didn't jump to line 289, because the condition on line 287 was never false
288 Logger = daklog.Logger("dominate")
289 session = DBConn().session()
291 suites_query = (
292 session.query(Suite)
293 .order_by(Suite.suite_name)
294 .filter(~exists().where(Suite.suite_id == PolicyQueue.suite_id))
295 )
296 if "Suite" in Options: 296 ↛ 297line 296 didn't jump to line 297, because the condition on line 296 was never true
297 suites_query = suites_query.filter(
298 Suite.suite_name.in_(utils.split_args(Options["Suite"]))
299 )
300 if not Options["Force"]: 300 ↛ 302line 300 didn't jump to line 302, because the condition on line 300 was never false
301 suites_query = suites_query.filter_by(untouchable=False)
302 suites = suites_query.all()
304 assocs = list(retrieve_associations(suites, session))
306 if Options["No-Action"]: 306 ↛ 307line 306 didn't jump to line 307, because the condition on line 306 was never true
307 headers = (
308 "source package",
309 "source version",
310 "package",
311 "version",
312 "arch",
313 "suite",
314 "id",
315 )
316 print((tabulate(assocs, headers, tablefmt="orgtbl")))
317 session.rollback()
319 else:
320 delete_associations(assocs, session)
321 session.commit()
323 if Logger: 323 ↛ exitline 323 didn't return from function 'main', because the condition on line 323 was never false
324 Logger.close()
327if __name__ == "__main__":
328 main()