869
|
1 # -*- coding: utf-8 -*-
|
|
2 #
|
|
3 # Maintained by Simon Cross <hodegstar+bittencontrib@gmail.com>
|
|
4 #
|
|
5 # Copyright (C) 2010 Edgewall Software
|
|
6 # All rights reserved.
|
|
7 #
|
|
8 # This software is licensed as described in the file COPYING, which
|
|
9 # you should have received as part of this distribution. The terms
|
|
10 # are also available at http://bitten.edgewall.org/wiki/License.
|
|
11
|
|
12 """Utility for deleting duplicate builds encounted while upgrading
|
|
13 to schema version 10."""
|
|
14
|
|
15 import os
|
|
16
|
|
17 from trac.env import Environment
|
|
18 from trac.attachment import Attachment
|
|
19 from trac.resource import Resource
|
|
20
|
|
21 __version__ = "1.0a1"
|
|
22
|
|
23 class BuildDeleter(object):
|
|
24 """Class for deleting a build."""
|
|
25
|
|
26 def __init__(self, env_path):
|
|
27 self.env = Environment(env_path)
|
|
28 self.logs_dir = self.env.config.get('bitten', 'logs_dir', 'log/bitten')
|
|
29
|
|
30 def _log_files(self, cursor, build):
|
|
31 """Return a list of log files."""
|
|
32 cursor.execute("SELECT filename FROM bitten_log WHERE build=%s",
|
|
33 (build,))
|
|
34 rows = cursor.fetchall()
|
|
35
|
|
36 all_files = []
|
|
37 for row in rows:
|
|
38 filename = row[0]
|
|
39 file_prefix = os.path.join(self.logs_dir, filename)
|
|
40 for suffix in ['', '.level', '.levels']:
|
|
41 log_file = file_prefix + suffix
|
|
42 if os.path.isfile(log_file):
|
|
43 all_files.append(log_file)
|
|
44
|
|
45 return all_files
|
|
46
|
|
47 def discover(self, build):
|
|
48 """Print a summary of what is linked to the build."""
|
|
49 print "Items to delete for build %r" % (build,)
|
|
50 print "-------------------------------"
|
|
51
|
|
52 db = self.env.get_db_cnx()
|
|
53 cursor = db.cursor()
|
|
54
|
|
55 print "Attachments for build resource:"
|
|
56 cursor.execute("SELECT config FROM bitten_build WHERE id=%s", (build,))
|
|
57 config = cursor.fetchone()[0]
|
|
58 print " %s/%s" % (config, build)
|
|
59
|
|
60 print "Log files:"
|
|
61 print " ", "\n ".join(self._log_files(cursor, build))
|
|
62
|
|
63 print "Rows from bitten_log with ids:"
|
|
64 cursor.execute("SELECT id FROM bitten_log WHERE build=%s", (build,))
|
|
65 print " ", "\n ".join(str(row[0]) for row in cursor.fetchall())
|
|
66
|
|
67 print "Rows from bitten_report with ids:"
|
|
68 cursor.execute("SELECT id FROM bitten_report WHERE build=%s", (build,))
|
|
69 print " ", "\n ".join(str(row[0]) for row in cursor.fetchall())
|
|
70 print "Rows from bitten_report_item with report set to these ids will"
|
|
71 print "also be deleted."
|
|
72
|
|
73 print "Rows from bitten_step for this build with names:"
|
|
74 cursor.execute("SELECT name FROM bitten_step WHERE build=%s", (build,))
|
|
75 print " ", "\n ".join(str(row[0]) for row in cursor.fetchall())
|
|
76
|
|
77 print "Row from bitten_build with id:"
|
|
78 cursor.execute("SELECT id FROM bitten_build WHERE id=%s", (build,))
|
|
79 print " ", "\n ".join(str(row[0]) for row in cursor.fetchall())
|
|
80
|
|
81 def remove(self, build):
|
|
82 """Delete what is linked to the build."""
|
|
83 print "Deleting items for build %r" % (build,)
|
|
84
|
|
85 db = self.env.get_db_cnx()
|
|
86 cursor = db.cursor()
|
|
87
|
|
88 print "Determining associated config."
|
|
89 cursor.execute("SELECT config FROM bitten_build WHERE id=%s", (build,))
|
|
90 config = cursor.fetchone()[0]
|
|
91
|
|
92 print "Collecting log files."
|
|
93 filenames = self._log_files(cursor, build)
|
|
94
|
|
95 try:
|
|
96 print "Deleting bitten_log entries."
|
|
97 cursor.execute("DELETE FROM bitten_log WHERE build=%s", (build,))
|
|
98
|
|
99 print "Deleting bitten_report_item_entries."
|
|
100 cursor.execute("DELETE FROM bitten_report_item WHERE report IN ("
|
|
101 "SELECT bitten_report.id FROM bitten_report "
|
|
102 "WHERE bitten_report.build=%s"
|
|
103 ")", (build,))
|
|
104
|
|
105 print "Deleting bitten_report entires."
|
|
106 cursor.execute("DELETE FROM bitten_report WHERE build=%s",
|
|
107 (build,))
|
|
108
|
|
109 print "Deleting bitten_step entries."
|
|
110 cursor.execute("DELETE FROM bitten_step WHERE build=%s", (build,))
|
|
111
|
|
112 print "Delete bitten_build entry."
|
|
113 cursor.execute("DELETE FROM bitten_build WHERE id=%s", (build,))
|
|
114 except:
|
|
115 db.rollback()
|
|
116 print "Build deletion failed. Database rolled back."
|
|
117 raise
|
|
118
|
|
119 print "Bitten database changes committed."
|
|
120 db.commit()
|
|
121
|
|
122 print "Removing log files."
|
|
123 for filename in filenames:
|
|
124 os.remove(filename)
|
|
125
|
|
126 print "Removing attachments."
|
|
127 resource = Resource('build', '%s/%s' % (config, build))
|
|
128 Attachment.delete_all(self.env, 'build', resource.id, db)
|
|
129
|
|
130
|
|
131 def main():
|
|
132 from optparse import OptionParser
|
|
133
|
|
134 parser = OptionParser(usage='usage: %prog env_path build_id',
|
|
135 version='%%prog %s' % __version__)
|
|
136
|
|
137 options, args = parser.parse_args()
|
|
138
|
|
139 if len(args) != 2:
|
|
140 parser.error('incorrect number of arguments')
|
|
141
|
|
142 env_path, build_id = args
|
|
143
|
|
144 deleter = BuildDeleter(env_path)
|
|
145 deleter.discover(build_id)
|
|
146 proceed = raw_input('Continue [y/n]? ')
|
|
147 if proceed == 'y':
|
|
148 deleter.remove(build_id)
|
|
149
|
|
150
|
|
151 if __name__ == "__main__":
|
|
152 import sys
|
|
153 sys.exit(main())
|