changeset 869:467e993df52d 0.6.x

0.6dev: Merged [940:942] from trunk.
author hodgestar
date Mon, 18 Oct 2010 11:55:44 +0000
parents 242f2e225c4e
children 0a59d3e71870
files bitten/upgrades.py contrib/deletebuild.py
diffstat 2 files changed, 157 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/upgrades.py
+++ b/bitten/upgrades.py
@@ -588,11 +588,10 @@
 
     if duplicates_exist:
         print "--------------------------------------------------------\n"
-        print "Duplicate builds found. You can remove the builds you don't want to"
-        print "keep by using this one-line command:\n"
-        print "$  python -c \"from bitten.model import Build; from trac.env import Environment; " \
-                        "Build.fetch(Environment('%s'), <buildid>).delete()\"" % env.path
-        print "\n...where <buildid> is the id of the build to remove.\n"
+        print "Duplicate builds found. You can obtain help on removing the"
+        print "builds you don't want by reading the Bitten upgrade"
+        print "documentation at:"
+        print "http://bitten.edgewall.org/wiki/Documentation/upgrade.html"
         print "Upgrades cannot be performed until conflicts are resolved."
         print "The upgrade script will now exit with an error:\n"
 
new file mode 100644
--- /dev/null
+++ b/contrib/deletebuild.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+#
+# Maintained by Simon Cross <hodegstar+bittencontrib@gmail.com>
+#
+# Copyright (C) 2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://bitten.edgewall.org/wiki/License.
+
+"""Utility for deleting duplicate builds encounted while upgrading
+to schema version 10."""
+
+import os
+
+from trac.env import Environment
+from trac.attachment import Attachment
+from trac.resource import Resource
+
+__version__ = "1.0a1"
+
+class BuildDeleter(object):
+    """Class for deleting a build."""
+
+    def __init__(self, env_path):
+        self.env = Environment(env_path)
+        self.logs_dir = self.env.config.get('bitten', 'logs_dir', 'log/bitten')
+
+    def _log_files(self, cursor, build):
+        """Return a list of log files."""
+        cursor.execute("SELECT filename FROM bitten_log WHERE build=%s",
+            (build,))
+        rows = cursor.fetchall()
+
+        all_files = []
+        for row in rows:
+            filename = row[0]
+            file_prefix = os.path.join(self.logs_dir, filename)
+            for suffix in ['', '.level', '.levels']:
+                log_file = file_prefix + suffix
+                if os.path.isfile(log_file):
+                    all_files.append(log_file)
+
+        return all_files
+
+    def discover(self, build):
+        """Print a summary of what is linked to the build."""
+        print "Items to delete for build %r" % (build,)
+        print "-------------------------------"
+
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+
+        print "Attachments for build resource:"
+        cursor.execute("SELECT config FROM bitten_build WHERE id=%s", (build,))
+        config = cursor.fetchone()[0]
+        print "  %s/%s" % (config, build)
+
+        print "Log files:"
+        print " ", "\n  ".join(self._log_files(cursor, build))
+
+        print "Rows from bitten_log with ids:"
+        cursor.execute("SELECT id FROM bitten_log WHERE build=%s", (build,))
+        print " ", "\n  ".join(str(row[0]) for row in cursor.fetchall())
+
+        print "Rows from bitten_report with ids:"
+        cursor.execute("SELECT id FROM bitten_report WHERE build=%s", (build,))
+        print " ", "\n  ".join(str(row[0]) for row in cursor.fetchall())
+        print "Rows from bitten_report_item with report set to these ids will"
+        print "also be deleted."
+
+        print "Rows from bitten_step for this build with names:"
+        cursor.execute("SELECT name FROM bitten_step WHERE build=%s", (build,))
+        print " ", "\n  ".join(str(row[0]) for row in cursor.fetchall())
+
+        print "Row from bitten_build with id:"
+        cursor.execute("SELECT id FROM bitten_build WHERE id=%s", (build,))
+        print " ", "\n  ".join(str(row[0]) for row in cursor.fetchall())
+
+    def remove(self, build):
+        """Delete what is linked to the build."""
+        print "Deleting items for build %r" % (build,)
+
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+
+        print "Determining associated config."
+        cursor.execute("SELECT config FROM bitten_build WHERE id=%s", (build,))
+        config = cursor.fetchone()[0]
+
+        print "Collecting log files."
+        filenames = self._log_files(cursor, build)
+
+        try:
+            print "Deleting bitten_log entries."
+            cursor.execute("DELETE FROM bitten_log WHERE build=%s", (build,))
+
+            print "Deleting bitten_report_item_entries."
+            cursor.execute("DELETE FROM bitten_report_item WHERE report IN ("
+                "SELECT bitten_report.id FROM bitten_report "
+                "WHERE bitten_report.build=%s"
+                ")", (build,))
+
+            print "Deleting bitten_report entires."
+            cursor.execute("DELETE FROM bitten_report WHERE build=%s",
+                (build,))
+
+            print "Deleting bitten_step entries."
+            cursor.execute("DELETE FROM bitten_step WHERE build=%s", (build,))
+
+            print "Delete bitten_build entry."
+            cursor.execute("DELETE FROM bitten_build WHERE id=%s", (build,))
+        except:
+            db.rollback()
+            print "Build deletion failed. Database rolled back."
+            raise
+
+        print "Bitten database changes committed."
+        db.commit()
+
+        print "Removing log files."
+        for filename in filenames:
+            os.remove(filename)
+
+        print "Removing attachments."
+        resource = Resource('build', '%s/%s' % (config, build))
+        Attachment.delete_all(self.env, 'build', resource.id, db)
+
+
+def main():
+    from optparse import OptionParser
+
+    parser = OptionParser(usage='usage: %prog env_path build_id',
+                          version='%%prog %s' % __version__)
+
+    options, args = parser.parse_args()
+
+    if len(args) != 2:
+        parser.error('incorrect number of arguments')
+
+    env_path, build_id = args
+
+    deleter = BuildDeleter(env_path)
+    deleter.discover(build_id)
+    proceed = raw_input('Continue [y/n]? ')
+    if proceed == 'y':
+        deleter.remove(build_id)
+
+
+if __name__ == "__main__":
+    import sys
+    sys.exit(main())
Copyright (C) 2012-2017 Edgewall Software