]> git.decadent.org.uk Git - dak.git/blob - dak/override.py
Merge remote-tracking branch 'nthykier/auto-decruft'
[dak.git] / dak / override.py
1 #!/usr/bin/env python
2
3 """ Microscopic modification and query tool for overrides in projectb """
4 # Copyright (C) 2004, 2006  Daniel Silverstone <dsilvers@digital-scurf.org>
5
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.
10
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.
15
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
19
20
21 ################################################################################
22 ## So line up your soldiers and she'll shoot them all down
23 ## Coz Alisha Rules The World
24 ## You think you found a dream, then it shatters and it seems,
25 ## That Alisha Rules The World
26 ################################################################################
27
28 import os
29 import sys
30 import apt_pkg
31
32 from daklib.config import Config
33 from daklib.dbconn import *
34 from daklib import daklog
35 from daklib import utils
36
37 ################################################################################
38
39 # Shamelessly stolen from 'dak rm'. Should probably end up in utils.py
40 def game_over():
41     answer = utils.our_raw_input("Continue (y/N)? ").lower()
42     if answer != "y":
43         print "Aborted."
44         sys.exit(1)
45
46
47 def usage (exit_code=0):
48     print """Usage: dak override [OPTIONS] package [section] [priority]
49 Make microchanges or microqueries of the binary overrides
50
51   -h, --help                 show this help and exit
52   -c, --check                check override compliance
53   -d, --done=BUG#            send priority/section change as closure to bug#
54   -n, --no-action            don't do anything
55   -s, --suite                specify the suite to use
56 """
57     sys.exit(exit_code)
58
59 def check_override_compliance(package, priority, archive_path, suite_name, cnf, session):
60     print "Checking compliance with related overrides..."
61
62     depends = set()
63     rdepends = set()
64     components = get_component_names(session)
65     arches = set([x.arch_string for x in get_suite_architectures(suite_name)])
66     arches -= set(["source", "all"])
67     for arch in arches:
68         for component in components:
69             Packages = utils.get_packages_from_ftp(archive_path, suite_name, component, arch)
70             while Packages.step():
71                 package_name = Packages.section.find("Package")
72                 dep_list = Packages.section.find("Depends")
73                 if dep_list:
74                     if package_name == package:
75                         for d in apt_pkg.parse_depends(dep_list):
76                             for i in d:
77                                 depends.add(i[0])
78                     else:
79                         for d in apt_pkg.parse_depends(dep_list):
80                             for i in d:
81                                 if i[0] == package:
82                                     rdepends.add(package_name)
83
84     query = """SELECT o.package, p.level, p.priority
85                FROM override o
86                JOIN suite s ON s.id = o.suite
87                JOIN priority p ON p.id = o.priority
88                WHERE s.suite_name = '%s'
89                AND o.package in ('%s')""" \
90                % (suite_name, "', '".join(depends.union(rdepends)))
91     packages = session.execute(query)
92
93     excuses = []
94     for p in packages:
95         if p[0] == package or not p[1]:
96             continue
97         if p[0] in depends:
98             if priority.level < p[1]:
99                 excuses.append("%s would have priority %s, its dependency %s has priority %s" \
100                       % (package, priority.priority, p[0], p[2]))
101         if p[0] in rdepends:
102             if priority.level > p[1]:
103                 excuses.append("%s would have priority %s, its reverse dependency %s has priority %s" \
104                       % (package, priority.priority, p[0], p[2]))
105
106     if excuses:
107         for ex in excuses:
108             print ex
109     else:
110         print "Proposed override change complies with Debian Policy"
111
112 def main ():
113     cnf = Config()
114
115     Arguments = [('h',"help","Override::Options::Help"),
116                  ('c',"check","Override::Options::Check"),
117                  ('d',"done","Override::Options::Done", "HasArg"),
118                  ('n',"no-action","Override::Options::No-Action"),
119                  ('s',"suite","Override::Options::Suite", "HasArg"),
120                  ]
121     for i in ["help", "check", "no-action"]:
122         if not cnf.has_key("Override::Options::%s" % (i)):
123             cnf["Override::Options::%s" % (i)] = ""
124     if not cnf.has_key("Override::Options::Suite"):
125         cnf["Override::Options::Suite"] = "unstable"
126
127     arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
128     Options = cnf.subtree("Override::Options")
129
130     if Options["Help"]:
131         usage()
132
133     session = DBConn().session()
134
135     if not arguments:
136         utils.fubar("package name is a required argument.")
137
138     package = arguments.pop(0)
139     suite_name = Options["Suite"]
140     if arguments and len(arguments) > 2:
141         utils.fubar("Too many arguments")
142
143     suite = get_suite(suite_name, session)
144     if suite is None:
145         utils.fubar("Unknown suite '{0}'".format(suite_name))
146
147     if arguments and len(arguments) == 1:
148         # Determine if the argument is a priority or a section...
149         arg = arguments.pop()
150         q = session.execute("""
151         SELECT ( SELECT COUNT(*) FROM section WHERE section = :arg ) AS secs,
152                ( SELECT COUNT(*) FROM priority WHERE priority = :arg ) AS prios
153                """, {'arg': arg})
154         r = q.fetchall()
155         if r[0][0] == 1:
156             arguments = (arg, ".")
157         elif r[0][1] == 1:
158             arguments = (".", arg)
159         else:
160             utils.fubar("%s is not a valid section or priority" % (arg))
161
162     # Retrieve current section/priority...
163     oldsection, oldsourcesection, oldpriority = None, None, None
164     for packagetype in ['source', 'binary']:
165         eqdsc = '!='
166         if packagetype == 'source':
167             eqdsc = '='
168         q = session.execute("""
169     SELECT priority.priority AS prio, section.section AS sect, override_type.type AS type
170       FROM override, priority, section, suite, override_type
171      WHERE override.priority = priority.id
172        AND override.type = override_type.id
173        AND override_type.type %s 'dsc'
174        AND override.section = section.id
175        AND override.package = :package
176        AND override.suite = suite.id
177        AND suite.suite_name = :suite_name
178         """ % (eqdsc), {'package': package, 'suite_name': suite_name})
179
180         if q.rowcount == 0:
181             continue
182         if q.rowcount > 1:
183             utils.fubar("%s is ambiguous. Matches %d packages" % (package,q.rowcount))
184
185         r = q.fetchone()
186         if packagetype == 'binary':
187             oldsection = r[1]
188             oldpriority = r[0]
189         else:
190             oldsourcesection = r[1]
191             oldpriority = 'source'
192
193     if not oldpriority and not oldsourcesection:
194         utils.fubar("Unable to find package %s" % (package))
195
196     if oldsection and oldsourcesection and oldsection != oldsourcesection:
197         # When setting overrides, both source & binary will become the same section
198         utils.warn("Source is in section '%s' instead of '%s'" % (oldsourcesection, oldsection))
199
200     if not oldsection:
201         oldsection = oldsourcesection
202
203     if not arguments:
204         print "%s is in section '%s' at priority '%s'" % (
205             package, oldsection, oldpriority)
206         sys.exit(0)
207
208     # At this point, we have a new section and priority... check they're valid...
209     newsection, newpriority = arguments
210
211     if newsection == ".":
212         newsection = oldsection
213     if newpriority == ".":
214         newpriority = oldpriority
215
216     s = get_section(newsection, session)
217     if s is None:
218         utils.fubar("Supplied section %s is invalid" % (newsection))
219     newsecid = s.section_id
220
221     p = get_priority(newpriority, session)
222     if p is None:
223         utils.fubar("Supplied priority %s is invalid" % (newpriority))
224     newprioid = p.priority_id
225
226     if newpriority == oldpriority and newsection == oldsection:
227         print "I: Doing nothing"
228         sys.exit(0)
229
230     if oldpriority == 'source' and newpriority != 'source':
231         utils.fubar("Trying to change priority of a source-only package")
232
233     if Options["Check"] and newpriority != oldpriority:
234         check_override_compliance(package, p, suite.archive.path, suite_name, cnf, session)
235
236     # If we're in no-action mode
237     if Options["No-Action"]:
238         if newpriority != oldpriority:
239             print "I: Would change priority from %s to %s" % (oldpriority,newpriority)
240         if newsection != oldsection:
241             print "I: Would change section from %s to %s" % (oldsection,newsection)
242         if Options.has_key("Done"):
243             print "I: Would also close bug(s): %s" % (Options["Done"])
244
245         sys.exit(0)
246
247     if newpriority != oldpriority:
248         print "I: Will change priority from %s to %s" % (oldpriority,newpriority)
249
250     if newsection != oldsection:
251         print "I: Will change section from %s to %s" % (oldsection,newsection)
252
253     if not Options.has_key("Done"):
254         pass
255         #utils.warn("No bugs to close have been specified. Noone will know you have done this.")
256     else:
257         print "I: Will close bug(s): %s" % (Options["Done"])
258
259     game_over()
260
261     Logger = daklog.Logger("override")
262
263     dsc_otype_id = get_override_type('dsc').overridetype_id
264
265     # We're already in a transaction
266     # We're in "do it" mode, we have something to do... do it
267     if newpriority != oldpriority:
268         session.execute("""
269         UPDATE override
270            SET priority = :newprioid
271          WHERE package = :package
272            AND override.type != :otypedsc
273            AND suite = (SELECT id FROM suite WHERE suite_name = :suite_name)""",
274            {'newprioid': newprioid, 'package': package,
275             'otypedsc':  dsc_otype_id, 'suite_name': suite_name})
276
277         Logger.log(["changed priority", package, oldpriority, newpriority])
278
279     if newsection != oldsection:
280         q = session.execute("""
281         UPDATE override
282            SET section = :newsecid
283          WHERE package = :package
284            AND suite = (SELECT id FROM suite WHERE suite_name = :suite_name)""",
285            {'newsecid': newsecid, 'package': package,
286             'suite_name': suite_name})
287
288         Logger.log(["changed section", package, oldsection, newsection])
289
290     session.commit()
291
292     if Options.has_key("Done"):
293         if not cnf.has_key("Dinstall::BugServer"):
294             utils.warn("Asked to send Done message but Dinstall::BugServer is not configured")
295             Logger.close()
296             return
297
298         Subst = {}
299         Subst["__OVERRIDE_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
300         Subst["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
301         bcc = []
302         if cnf.find("Dinstall::Bcc") != "":
303             bcc.append(cnf["Dinstall::Bcc"])
304         if bcc:
305             Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
306         else:
307             Subst["__BCC__"] = "X-Filler: 42"
308         if cnf.has_key("Dinstall::PackagesServer"):
309             Subst["__CC__"] = "Cc: " + package + "@" + cnf["Dinstall::PackagesServer"] + "\nX-DAK: dak override"
310         else:
311             Subst["__CC__"] = "X-DAK: dak override"
312         Subst["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
313         Subst["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
314         Subst["__WHOAMI__"] = utils.whoami()
315         Subst["__SOURCE__"] = package
316
317         summary = "Concerning package %s...\n" % (package)
318         summary += "Operating on the %s suite\n" % (suite_name)
319         if newpriority != oldpriority:
320             summary += "Changed priority from %s to %s\n" % (oldpriority,newpriority)
321         if newsection != oldsection:
322             summary += "Changed section from %s to %s\n" % (oldsection,newsection)
323         Subst["__SUMMARY__"] = summary
324
325         template = os.path.join(cnf["Dir::Templates"], "override.bug-close")
326         for bug in utils.split_args(Options["Done"]):
327             Subst["__BUG_NUMBER__"] = bug
328             mail_message = utils.TemplateSubst(Subst, template)
329             utils.send_mail(mail_message)
330             Logger.log(["closed bug", bug])
331
332     Logger.close()
333
334 #################################################################################
335
336 if __name__ == '__main__':
337     main()