Coverage for dak/bts_categorize.py: 38%
70 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"""
4bts -- manage bugs filed against ftp.debian.org
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2009 Mike O'Connor <stew@vireo.org>
8@copyright: 2010 Alexander Reichle-Schmehl <tolimar@debian.org>
9@license: GNU General Public License version 2 or later
10"""
12# This program is free software; you can redistribute it and/or modify it
13# under the terms of the GNU General Public License as published by the
14# Free Software Foundation; either version 2, or (at your option) any
15# later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program; if not, write to the Free Software
24# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25# USA.
27################################################################################
28################################################################################
30import logging
31import re
32import sys
34log = logging.getLogger()
36import apt_pkg
37import debianbts as bts
39from daklib import utils
41Cnf: apt_pkg.Configuration
44def usage():
45 print(
46 """
47SYNOPSIS
48 dak bts-categorize [options]
50OPTIONS
51 -s
52 --simulate
53 Don't send email, instead output the lines that would be sent to
54 control@b.d.o.
56 -v
57 --verbose
58 Print more informational log messages
60 -q
61 --quiet
62 Suppress informational messages
64 -h
65 --help
66 Print this documentation.
67"""
68 )
71arguments = [
72 ("s", "simulate", "BtsCategorize::Options::Simulate"),
73 ("v", "verbose", "BtsCategorize::Options::Verbose"),
74 ("q", "quiet", "BtsCategorize::Options::Quiet"),
75 ("h", "help", "BtsCategorize::Options::Help"),
76 ("o", "option", "", "ArbItem"),
77]
80class BugClassifier:
81 """
82 classify bugs using usertags based on the bug subject lines
84 >>> BugClassifier.rm_re.match( "RM: asdf" ) != None
85 True
86 >>> BugClassifier.rm_re.match( "[dak] Packages.diff/Index broken" ) != None
87 False
88 >>> BugClassifier.dak_re.match( "[dak] Packages.diff/Index broken" ) != None
89 True
90 """
92 rm_re = re.compile("^RM")
93 dak_re = re.compile(r"^\[dak\]")
94 arch_re = re.compile(r"^\[Architectures\]")
95 override_re = re.compile("^override")
97 classifiers = {
98 rm_re: "remove",
99 dak_re: "dak",
100 arch_re: "archs",
101 override_re: "override",
102 }
104 def unclassified_bugs(self):
105 """
106 Returns a list of open bugs which have not yet been classified
107 by one of our usertags.
108 """
110 tagged_bugs = bts.get_usertag("ftp.debian.org@packages.debian.org")
111 tagged_bugs_ftp = []
112 for tags in tagged_bugs.keys():
113 tagged_bugs_ftp += tagged_bugs[tags]
115 return [
116 bug
117 for bug in bts.get_status(bts.get_bugs(package="ftp.debian.org"))
118 if bug.pending == "pending" and bug.bug_num not in tagged_bugs_ftp
119 ]
121 def classify_bug(self, bug):
122 """
123 if any of our classifiers match, return a newline terminated
124 command to set an appropriate usertag, otherwise return an
125 empty string
126 """
127 retval = ""
129 for classifier in self.classifiers.keys():
130 if classifier.match(bug.subject):
131 retval = "usertag %s %s\n" % (bug.bug_num, self.classifiers[classifier])
132 break
134 if retval:
135 log.info(retval)
136 else:
137 log.debug("Unmatched: [%s] %s" % (bug.bug_num, bug.subject))
139 return retval
141 def email_text(self):
142 controls = ""
144 bc = BugClassifier()
145 try:
146 for bug in bc.unclassified_bugs():
147 controls += bc.classify_bug(bug)
149 return controls
150 except:
151 log.error(
152 "couldn't retrieve bugs from soap interface: %s" % sys.exc_info()[0]
153 )
154 return None
157def send_email(commands, simulate=False):
158 global Cnf
160 Subst = {
161 "__COMMANDS__": commands,
162 "__DAK_ADDRESS__": Cnf["Dinstall::MyAdminAddress"],
163 }
165 bts_mail_message = utils.TemplateSubst(
166 Subst, Cnf["Dir::Templates"] + "/bts-categorize"
167 )
169 if simulate:
170 print(bts_mail_message)
171 else:
172 utils.send_mail(bts_mail_message)
175def main():
176 """
177 for now, we just dump a list of commands that could be sent for
178 control@b.d.o
179 """
180 global Cnf
181 Cnf = utils.get_conf()
183 for arg in arguments:
184 opt = "BtsCategorize::Options::%s" % arg[1]
185 if opt not in Cnf: 185 ↛ 183line 185 didn't jump to line 183 because the condition on line 185 was always true
186 Cnf[opt] = "" # type: ignore[index]
188 apt_pkg.parse_commandline(Cnf, arguments, sys.argv) # type: ignore[attr-defined]
189 Options = Cnf.subtree("BtsCategorize::Options") # type: ignore[attr-defined]
191 if Options["Help"]: 191 ↛ 195line 191 didn't jump to line 195 because the condition on line 191 was always true
192 usage()
193 sys.exit(0)
195 if Options["Quiet"]:
196 level = logging.ERROR
198 elif Options["Verbose"]:
199 level = logging.DEBUG
201 else:
202 level = logging.INFO
204 logging.basicConfig(
205 level=level, format="%(asctime)s %(levelname)s %(message)s", stream=sys.stderr
206 )
208 body = BugClassifier().email_text()
210 if body:
211 send_email(body, Options["Simulate"])
213 else:
214 log.info("nothing to do")
217if __name__ == "__main__":
218 # import doctest
219 # doctest.testmod()
220 main()