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

1#! /usr/bin/env python3 

2 

3""" 

4bts -- manage bugs filed against ftp.debian.org 

5 

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""" 

11 

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. 

26 

27################################################################################ 

28################################################################################ 

29 

30import logging 

31import re 

32import sys 

33 

34log = logging.getLogger() 

35 

36import apt_pkg 

37import debianbts as bts 

38 

39from daklib import utils 

40 

41Cnf: apt_pkg.Configuration 

42 

43 

44def usage(): 

45 print( 

46 """ 

47SYNOPSIS 

48 dak bts-categorize [options] 

49 

50OPTIONS 

51 -s 

52 --simulate 

53 Don't send email, instead output the lines that would be sent to 

54 control@b.d.o. 

55 

56 -v 

57 --verbose 

58 Print more informational log messages 

59 

60 -q 

61 --quiet 

62 Suppress informational messages 

63 

64 -h 

65 --help 

66 Print this documentation. 

67""" 

68 ) 

69 

70 

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] 

78 

79 

80class BugClassifier: 

81 """ 

82 classify bugs using usertags based on the bug subject lines 

83 

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 """ 

91 

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") 

96 

97 classifiers = { 

98 rm_re: "remove", 

99 dak_re: "dak", 

100 arch_re: "archs", 

101 override_re: "override", 

102 } 

103 

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 """ 

109 

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] 

114 

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 ] 

120 

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 = "" 

128 

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 

133 

134 if retval: 

135 log.info(retval) 

136 else: 

137 log.debug("Unmatched: [%s] %s" % (bug.bug_num, bug.subject)) 

138 

139 return retval 

140 

141 def email_text(self): 

142 controls = "" 

143 

144 bc = BugClassifier() 

145 try: 

146 for bug in bc.unclassified_bugs(): 

147 controls += bc.classify_bug(bug) 

148 

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 

155 

156 

157def send_email(commands, simulate=False): 

158 global Cnf 

159 

160 Subst = { 

161 "__COMMANDS__": commands, 

162 "__DAK_ADDRESS__": Cnf["Dinstall::MyAdminAddress"], 

163 } 

164 

165 bts_mail_message = utils.TemplateSubst( 

166 Subst, Cnf["Dir::Templates"] + "/bts-categorize" 

167 ) 

168 

169 if simulate: 

170 print(bts_mail_message) 

171 else: 

172 utils.send_mail(bts_mail_message) 

173 

174 

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() 

182 

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] 

187 

188 apt_pkg.parse_commandline(Cnf, arguments, sys.argv) # type: ignore[attr-defined] 

189 Options = Cnf.subtree("BtsCategorize::Options") # type: ignore[attr-defined] 

190 

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) 

194 

195 if Options["Quiet"]: 

196 level = logging.ERROR 

197 

198 elif Options["Verbose"]: 

199 level = logging.DEBUG 

200 

201 else: 

202 level = logging.INFO 

203 

204 logging.basicConfig( 

205 level=level, format="%(asctime)s %(levelname)s %(message)s", stream=sys.stderr 

206 ) 

207 

208 body = BugClassifier().email_text() 

209 

210 if body: 

211 send_email(body, Options["Simulate"]) 

212 

213 else: 

214 log.info("nothing to do") 

215 

216 

217if __name__ == "__main__": 

218 # import doctest 

219 # doctest.testmod() 

220 main()