diff --git a/.gitignore b/.gitignore
index 0076e91..65bac97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 /__pycache__
+/venv
 *.rej
 *.orig
 .DS_Store
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..e89d85f
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,2 @@
+[MESSAGES CONTROL]
+disable = invalid-name,missing-function-docstring
diff --git a/make_schema_doc.py b/make_schema_doc.py
old mode 100755
new mode 100644
index 9095120..575bdc5
--- a/make_schema_doc.py
+++ b/make_schema_doc.py
@@ -16,14 +16,8 @@
 #
 # This document is not confidential.
 
-import copy
 import re
-import types
 import time
-import sys
-import argparse
-from pprint import pprint, pformat
-from black import FileMode, format_str
 
 import schema_remarks
 import get_schema
@@ -880,393 +874,6 @@ def make_body(first, last):
     return body
 
 
-def write_file(first, last, file):
-    # file = open(filename, 'w')
-    # file is an already-open filehandle
-    (header, body, footer) = make_tables(first, last)
-    file.write(header)
-    file.write(body)
-    file.write(footer)
-    file.close()
-
-
-def test_schema_remarks(args):
-    first = args.first
-    last = args.last
-    file = args.file
-    if last == None:
-        last = first
-    if file:
-        write_file(first, last, file)
-    else:
-        try:
-            (header, body, footer) = make_tables(first, last)
-        except BzSchemaProcessingException as e:
-            print('\n'.join(e.errors))
-            sys.exit()
-        print("Succeeded!")
-
-
-class regex_in:
-    string: str
-    match: re.Match = None
-
-    def __init__(self, thestring):
-        self.string = thestring
-
-    def __eq__(self, other: str | re.Pattern):
-        if isinstance(other, str):
-            other = re.compile(other)
-        assert isinstance(other, re.Pattern)
-        # TODO extend for search and match variants
-        self.match = other.fullmatch(self.string)
-        return self.match is not None
-
-    def __getitem__(self, group):
-        return self.match[group]
-
-
-# Note the `as m` in in the case specification
-# match regex_in(validated_string):
-#    case r'\d(\d)' as m:
-#        print(f'The second digit is {m[1]}')
-#        print(f'The whole match is {m.match}')
-
-
-def generate_schema_remarks(args):
-    first = args.first
-    last = args.last
-    if last == None:
-        last = first
-    print(f"generating missing remarks for {first} .. {last}")
-    try:
-        (header, body, footer) = make_tables(first, last)
-    except BzSchemaProcessingException as e:
-        for error in e.errors:
-            match regex_in(error):
-                case r"No column remarks for table '(\S+)'\." as m:
-                    if not m[1] in schema_remarks.table_remark:
-                        schema_remarks.table_remark[m[1]] = 'TODO'
-                    schema_remarks.column_remark[m[1]] = {}
-                case r"Table '(\S+)' has no remark for column '(\S+)'\." as m:
-                    schema_remarks.column_remark[m[1]][m[2]] = 'TODO'
-                case r"No index remarks for table '(\S+)'\." as m:
-                    schema_remarks.index_remark[m[1]] = {}
-                case r"Table '(\S+)' has no remark for index '(\S+)'\." as m:
-                    schema_remarks.index_remark[m[1]][m[2]] = 'TODO'
-                case _:
-                    print(f"Unhandled error: {error}")
-        # pprint(schema_remarks.column_remark)
-        if first != last:
-            # we're comparing two versions, run it a second time to catch added/removed
-            try:
-                (header, body, footer) = make_tables(first, last)
-            except BzSchemaProcessingException as e:
-                for error in e.errors:
-                    match regex_in(error):
-                        case r"No remark to add table (\S+)" as m:
-                            schema_remarks.table_added_remark[m[1]] = 'TODO'
-                        case r"No remark to remove table (\S+)" as m:
-                            schema_remarks.table_removed_remark[m[1]] = 'TODO'
-                        case r"No remark to add column (\S+)\.(\S+)\." as m:
-                            if not m[1] in schema_remarks.column_added_remark:
-                                schema_remarks.column_added_remark[m[1]] = {}
-                            schema_remarks.column_added_remark[m[1]][m[2]] = 'TODO'
-                        case r"No remark to remove column (\S+)\.(\S+)\." as m:
-                            if not m[1] in schema_remarks.column_removed_remark:
-                                schema_remarks.column_removed_remark[m[1]] = {}
-                            schema_remarks.column_removed_remark[m[1]][m[2]] = 'TODO'
-                        case r"No remark to add index (\S+):(\S+)\." as m:
-                            if not m[1] in schema_remarks.index_added_remark:
-                                schema_remarks.index_added_remark[m[1]] = {}
-                            schema_remarks.index_added_remark[m[1]][m[2]] = 'TODO'
-                        case r"No remark to remove index (\S+):(\S+)\." as m:
-                            if not m[1] in schema_remarks.index_removed_remark:
-                                schema_remarks.index_removed_remark[m[1]] = {}
-                            schema_remarks.index_removed_remark[m[1]][m[2]] = 'TODO'
-                        case _:
-                            print(f"Unhandled error: {error}")
-        var_dict = {
-            'version_order': format_str(
-                'version_order = %s' % pformat(schema_remarks.version_order),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'default_first_version': format_str(
-                'default_first_version = %s'
-                % pformat(schema_remarks.default_first_version),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'default_last_version': format_str(
-                'default_last_version = %s'
-                % pformat(schema_remarks.default_last_version),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'version_schema_map': format_str(
-                'version_schema_map = %s' % pformat(schema_remarks.version_schema_map),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'version_remark': format_str(
-                'version_remark = %s' % pformat(schema_remarks.version_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'table_remark': format_str(
-                'table_remark = %s' % pformat(schema_remarks.table_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'table_added_remark': format_str(
-                'table_added_remark = %s' % pformat(schema_remarks.table_added_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'table_removed_remark': format_str(
-                'table_removed_remark = %s'
-                % pformat(schema_remarks.table_removed_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'column_remark': format_str(
-                'column_remark = %s' % pformat(schema_remarks.column_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'column_renamed': format_str(
-                'column_renamed = %s' % pformat(schema_remarks.column_renamed),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'column_added_remark': format_str(
-                'column_added_remark = %s'
-                % pformat(schema_remarks.column_added_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'column_removed_remark': format_str(
-                'column_removed_remark = %s'
-                % pformat(schema_remarks.column_removed_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'index_remark': format_str(
-                'index_remark = %s' % pformat(schema_remarks.index_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'index_renamed': format_str(
-                'index_renamed = %s' % pformat(schema_remarks.index_renamed),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'index_removed_remark': format_str(
-                'index_removed_remark = %s'
-                % pformat(schema_remarks.index_removed_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'index_added_remark': format_str(
-                'index_added_remark = %s' % pformat(schema_remarks.index_added_remark),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'notation_guide': format_str(
-                'notation_guide = %s' % pformat(schema_remarks.notation_guide),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'header': format_str(
-                'header = %s' % pformat(schema_remarks.header),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'footer': format_str(
-                'footer = %s' % pformat(schema_remarks.footer),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'prelude': format_str(
-                'prelude = %s' % pformat(schema_remarks.prelude),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-            'afterword': format_str(
-                'afterword = %s' % pformat(schema_remarks.afterword),
-                mode=FileMode(
-                    string_normalization=False, experimental_string_processing=True
-                ),
-            ),
-        }
-
-        with open('schema_remarks_template.txt', 'r') as infile:
-            template = infile.read()
-        with open('schema_remarks_new.py', 'w') as outfile:
-            output = template.format(**var_dict)
-            outfile.write(output)
-        print("Wrote changes to schema_remarks_new.py.")
-        print(
-            "diff the changes from schema_remarks.py and if you like them, move it"
-            " overtop of it."
-        )
-        sys.exit()
-    print("No changes detected.")
-
-
-def validate_schema_remarks(args):
-    for v in schema_remarks.version_order:
-        if not v in schema_remarks.version_schema_map:
-            errors.append(
-                f"Version {v} found in version_order is not listed in"
-                " version_schema_map"
-            )
-        if len([item for item in schema_remarks.version_remark if item[0] == v]) < 1:
-            errors.append(
-                f"Version {v} found in version_order is not listed in version_remark"
-            )
-    for v in schema_remarks.version_schema_map.keys():
-        if not v in schema_remarks.version_order:
-            errors.append(
-                f"Version {v} found in version_schema_map is not listed in"
-                " version_order"
-            )
-        if len([item for item in schema_remarks.version_remark if item[0] == v]) < 1:
-            errors.append(
-                f"Version {v} found in version_schema_map is not listed in"
-                " version_remark"
-            )
-    for item in schema_remarks.version_remark:
-        v = item[0]
-        if not v in schema_remarks.version_order:
-            errors.append(
-                f"Version {v} found in version_remark is not listed in version_order"
-            )
-        if not v in schema_remarks.version_schema_map:
-            errors.append(
-                f"Version {v} found in version_remark is not listed in"
-                " version_schema_map"
-            )
-    if errors:
-        print(str.join('\n', errors))
-        sys.exit()
-    print("Versions validated.")
-
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(
-        description="A utility for generating schema comparison documents."
-    )
-    subparsers = parser.add_subparsers(
-        required=True,
-        metavar='subcommand',
-        help='Type `%(prog)s {subcommand} -h` for additional help',
-        title='Available subcommands',
-    )
-    parser_validate = subparsers.add_parser(
-        'validate',
-        help='Validate that the version-related lists are in sync with each other',
-        description=(
-            'Validate that the version-related lists are in sync with each other'
-        ),
-    )
-    parser_validate.set_defaults(func=validate_schema_remarks)
-    parser_test = subparsers.add_parser(
-        'test',
-        help=(
-            'Test schema document generation. By default it only prints errors or a'
-            ' success message.'
-        ),
-        description=(
-            'Test schema document generation. By default it only prints errors or a'
-            ' success message.'
-        ),
-    )
-    parser_test.add_argument(
-        'first',
-        metavar="first",
-        choices=schema_remarks.version_order,
-        help=(
-            "The starting version of the schemas to compare, or the single version to"
-            " display if 'last' is not provided."
-        ),
-    )
-    parser_test.add_argument(
-        'last',
-        metavar="last",
-        choices=schema_remarks.version_order,
-        nargs="?",
-        default=None,
-        help="The destination version of the schema to compare",
-    )
-    parser_test.add_argument(
-        '-f',
-        dest="file",
-        metavar='FILENAME',
-        type=argparse.FileType('w'),
-        help=(
-            "A file to write the generated schema doc to. Passing - will write it to"
-            " standard out."
-        ),
-    )
-    parser_test.set_defaults(func=test_schema_remarks)
-    parser_generate = subparsers.add_parser(
-        'generate',
-        help=(
-            'Add all of the missing remarks from a new schema to schema_remarks.py for'
-            ' you as TODO items.'
-        ),
-        description=(
-            'Add all of the missing remarks from a new schema to schema_remarks.py for'
-            ' you to as TODO items. Saves you the trouble of searching through the'
-            ' massive file looking for the right spot in alphabetical order to put'
-            ' them. Two benefits: gets a schema live faster (just without things'
-            ' documented), and you can search for TODO in the file to find the things'
-            ' that need updating.'
-        ),
-    )
-    parser_generate.add_argument(
-        'first',
-        metavar="first",
-        choices=schema_remarks.version_order,
-        help=(
-            "The starting version of the schemas to compare, or the single version to"
-            " display if 'last' is not provided."
-        ),
-    )
-    parser_generate.add_argument(
-        'last',
-        metavar="last",
-        choices=schema_remarks.version_order,
-        nargs="?",
-        default=None,
-        help="The destination version of the schema to compare",
-    )
-    parser_generate.set_defaults(func=generate_schema_remarks)
-    args = parser.parse_args()
-    args.func(args)
-
 # A. REFERENCES
 #
 #
diff --git a/pyproject.toml b/pyproject.toml
index 08c2feb..693c920 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
 [tool.black]
 target-version = ["py310"]
 skip-string-normalization = true
-experimental-string-processing = true
+unstable = true
 color = true
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..9fa7856
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+mysqlclient
+black>=21.0 # need 21 for py3.10 compat
+argparse
+click<8.1 # 8.1 breaks black 21
diff --git a/schema-tool b/schema-tool
new file mode 100755
index 0000000..7e6b1a3
--- /dev/null
+++ b/schema-tool
@@ -0,0 +1,445 @@
+#!./venvwrapper.sh
+# vim:syntax=python
+#
+#             Perforce Defect Tracking Integration Project
+#              <http://www.ravenbrook.com/project/p4dti/>
+#
+#  MAKE_SCHEMA_DOC.PY -- GENERATE BUGZILLA SCHEMA DOCUMENTATION
+#
+#             Nick Barnes, Ravenbrook Limited, 2003-07-07
+#
+#
+# 1. INTRODUCTION
+#
+
+# This module generates Bugzilla schema documentation.
+#
+# The intended readership is project developers.
+#
+# This document is not confidential.
+
+"""
+A utility for generating schema comparison documents and performing maintenance on the schema data.
+"""
+
+import re
+import sys
+import argparse
+import subprocess
+from pprint import pformat
+from black import Mode, format_str
+from collections import OrderedDict
+
+import schema_remarks
+from make_schema_doc import BzSchemaProcessingException, make_tables, version_compare
+
+
+def write_file(first, last, file):
+    # file = open(filename, 'w')
+    # file is an already-open filehandle
+    (header, body, footer) = make_tables(first, last)
+    file.write(header)
+    file.write(body)
+    file.write(footer)
+    file.close()
+
+
+def test_schema_remarks(args):
+    first = args.first
+    last = args.last
+    file = args.file
+    if last is None:
+        last = first
+    if file:
+        write_file(first, last, file)
+    else:
+        try:
+            make_tables(first, last)
+        except BzSchemaProcessingException as e:
+            print('\n'.join(e.errors))
+            sys.exit()
+        print("Succeeded!")
+
+
+class regex_in:
+    r"""
+    An object that can be compared with a regex for use in match..case
+
+    Note the `as m` in in the case specification:
+
+    match regex_in(validated_string):
+        case r'\d(\d)' as m:
+            print(f'The second digit is {m[1]}')
+            print(f'The whole match is {m.match}')
+    """
+
+    string: str
+    match: re.Match = None
+
+    def __init__(self, thestring):
+        self.string = thestring
+
+    def __eq__(self, other: str | re.Pattern):
+        if isinstance(other, str):
+            other = re.compile(other)
+        assert isinstance(other, re.Pattern)
+        self.match = other.fullmatch(self.string)
+        return self.match is not None
+
+    def __getitem__(self, group):
+        return self.match[group]
+
+
+def fix_error(error):
+    match regex_in(error):
+        case r"No column remarks for table '(\S+)'\." as m:
+            if not m[1] in schema_remarks.table_remark:
+                schema_remarks.table_remark[m[1]] = 'TODO'
+            schema_remarks.column_remark[m[1]] = {}
+        case r"Table '(\S+)' has no remark for column '(\S+)'\." as m:
+            schema_remarks.column_remark[m[1]][m[2]] = 'TODO'
+        case r"No index remarks for table '(\S+)'\." as m:
+            if not m[1] in schema_remarks.table_remark:
+                schema_remarks.table_remark[m[1]] = 'TODO'
+            schema_remarks.index_remark[m[1]] = {}
+        case r"Table '(\S+)' has no remark for index '(\S+)'\." as m:
+            schema_remarks.index_remark[m[1]][m[2]] = 'TODO'
+        case r"No remark to add table (\S+)" as m:
+            schema_remarks.table_added_remark[m[1]] = 'TODO'
+        case r"No remark to remove table (\S+)" as m:
+            schema_remarks.table_removed_remark[m[1]] = 'TODO'
+        case r"No remark to add column (\S+)\.(\S+)\." as m:
+            if not m[1] in schema_remarks.column_added_remark:
+                schema_remarks.column_added_remark[m[1]] = {}
+            schema_remarks.column_added_remark[m[1]][m[2]] = 'TODO'
+        case r"No remark to remove column (\S+)\.(\S+)\." as m:
+            if not m[1] in schema_remarks.column_removed_remark:
+                schema_remarks.column_removed_remark[m[1]] = {}
+            schema_remarks.column_removed_remark[m[1]][m[2]] = 'TODO'
+        case r"No remark to add index (\S+):(\S+)\." as m:
+            if not m[1] in schema_remarks.index_added_remark:
+                schema_remarks.index_added_remark[m[1]] = {}
+            schema_remarks.index_added_remark[m[1]][m[2]] = 'TODO'
+        case r"No remark to remove index (\S+):(\S+)\." as m:
+            if not m[1] in schema_remarks.index_removed_remark:
+                schema_remarks.index_removed_remark[m[1]] = {}
+            schema_remarks.index_removed_remark[m[1]][m[2]] = 'TODO'
+        case _:
+            print(f"Unhandled error: {error}")
+
+
+def version_map_formatter(version_map):
+    output = '{\n'
+    for version in schema_remarks.version_order:
+        output += f"    '{version}': '{version_map[version]}',\n"
+    output += '}\n'
+    return output
+
+
+def generate_schema_remarks(args):
+    first = args.first
+    last = args.last
+    if last is None:
+        last = first
+    print(f"generating missing remarks for {first} .. {last}")
+    try:
+        make_tables(first, last)
+    except BzSchemaProcessingException as e:
+        for error in e.errors:
+            fix_error(error)
+        # pprint(schema_remarks.column_remark)
+        if first != last:
+            # we're comparing two versions, run it a second time to catch some
+            # added/removed errors that get masked by the main remarks being
+            # missing on the first pass
+            try:
+                make_tables(first, last)
+            except BzSchemaProcessingException as e:
+                for error in e.errors:
+                    fix_error(error)
+
+        mode = Mode(  # pylint: disable=unexpected-keyword-arg
+            string_normalization=False,
+            unstable=True,
+        )
+        var_dict = {
+            'version_order': format_str(
+                f'version_order = {pformat(schema_remarks.version_order)}',
+                mode=mode,
+            ),
+            'default_first_version': format_str(
+                'default_first_version ='
+                f' {pformat(schema_remarks.default_first_version)}',
+                mode=mode,
+            ),
+            'default_last_version': format_str(
+                'default_last_version ='
+                f' {pformat(schema_remarks.default_last_version)}',
+                mode=mode,
+            ),
+            'version_schema_map': (
+                f'version_schema_map = {version_map_formatter(schema_remarks.version_schema_map)}'
+            ),
+            'version_remark': format_str(
+                f'version_remark = {pformat(schema_remarks.version_remark)}',
+                mode=mode,
+            ),
+            'table_remark': format_str(
+                f'table_remark = {pformat(schema_remarks.table_remark)}',
+                mode=mode,
+            ),
+            'table_added_remark': format_str(
+                f'table_added_remark = {pformat(schema_remarks.table_added_remark)}',
+                mode=mode,
+            ),
+            'table_removed_remark': format_str(
+                'table_removed_remark ='
+                f' {pformat(schema_remarks.table_removed_remark)}',
+                mode=mode,
+            ),
+            'column_remark': format_str(
+                f'column_remark = {pformat(schema_remarks.column_remark)}',
+                mode=mode,
+            ),
+            'column_renamed': format_str(
+                f'column_renamed = {pformat(schema_remarks.column_renamed)}',
+                mode=mode,
+            ),
+            'column_added_remark': format_str(
+                f'column_added_remark = {pformat(schema_remarks.column_added_remark)}',
+                mode=mode,
+            ),
+            'column_removed_remark': format_str(
+                'column_removed_remark ='
+                f' {pformat(schema_remarks.column_removed_remark)}',
+                mode=mode,
+            ),
+            'index_remark': format_str(
+                f'index_remark = {pformat(schema_remarks.index_remark)}',
+                mode=mode,
+            ),
+            'index_renamed': format_str(
+                f'index_renamed = {pformat(schema_remarks.index_renamed)}',
+                mode=mode,
+            ),
+            'index_removed_remark': format_str(
+                'index_removed_remark ='
+                f' {pformat(schema_remarks.index_removed_remark)}',
+                mode=mode,
+            ),
+            'index_added_remark': format_str(
+                f'index_added_remark = {pformat(schema_remarks.index_added_remark)}',
+                mode=mode,
+            ),
+            'notation_guide': format_str(
+                f'notation_guide = {pformat(schema_remarks.notation_guide)}',
+                mode=mode,
+            ),
+            'header': format_str(
+                f'header = {pformat(schema_remarks.header)}',
+                mode=mode,
+            ),
+            'footer': format_str(
+                f'footer = {pformat(schema_remarks.footer)}',
+                mode=mode,
+            ),
+            'prelude': format_str(
+                f'prelude = {pformat(schema_remarks.prelude)}',
+                mode=mode,
+            ),
+            'afterword': format_str(
+                f'afterword = {pformat(schema_remarks.afterword)}',
+                mode=mode,
+            ),
+        }
+
+        with open('template_schema_remarks.txt', 'r', encoding='utf-8') as infile:
+            template = infile.read()
+        with open('schema_remarks_new.py', 'w', encoding='utf-8') as outfile:
+            output = template.format(**var_dict)
+            outfile.write(output)
+        subprocess.run(
+            ["diff", "-u", "--color", "schema_remarks.py", "schema_remarks_new.py"]
+        )
+        print("Wrote changes to schema_remarks_new.py.")
+        print("If these changes are okay, move it overtop of schema_remarks.py")
+        sys.exit()
+    print("No changes detected.")
+
+
+def validate_schema_remarks(_args):
+    errors = []
+    for v in schema_remarks.version_order:
+        if not v in schema_remarks.version_schema_map:
+            errors.append(
+                f"Version {v} found in version_order is not listed in"
+                " version_schema_map"
+            )
+        if len([item for item in schema_remarks.version_remark if item[0] == v]) < 1:
+            errors.append(
+                f"Version {v} found in version_order is not listed in version_remark"
+            )
+    for v in list(schema_remarks.version_schema_map.keys()):
+        if not v in schema_remarks.version_order:
+            errors.append(
+                f"Version {v} found in version_schema_map is not listed in"
+                " version_order"
+            )
+        if len([item for item in schema_remarks.version_remark if item[0] == v]) < 1:
+            errors.append(
+                f"Version {v} found in version_schema_map is not listed in"
+                " version_remark"
+            )
+    for item in schema_remarks.version_remark:
+        v = item[0]
+        if not v in schema_remarks.version_order:
+            errors.append(
+                f"Version {v} found in version_remark is not listed in version_order"
+            )
+        if not v in schema_remarks.version_schema_map:
+            errors.append(
+                f"Version {v} found in version_remark is not listed in"
+                " version_schema_map"
+            )
+    if errors:
+        print(str.join('\n', errors))
+        sys.exit()
+    print("Versions validated.")
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description=(
+            "A utility for generating schema comparison documents and performing"
+            " maintenance on the schema data."
+        )
+    )
+    subparsers = parser.add_subparsers(
+        required=True,
+        metavar='subcommand',
+        help='Type `%(prog)s {subcommand} -h` for additional help',
+        title='Available subcommands',
+    )
+    parser_validate = subparsers.add_parser(
+        'validate',
+        help='Validate that the version-related lists are in sync with each other',
+        description=(
+            'Validate that the version-related lists are in sync with each other'
+        ),
+    )
+    parser_validate.set_defaults(func=validate_schema_remarks)
+    parser_test = subparsers.add_parser(
+        'test',
+        help=(
+            'Test schema document generation. By default it only prints errors or a'
+            ' success message.'
+        ),
+        description=(
+            'Test schema document generation. By default it only prints errors or a'
+            ' success message.'
+        ),
+    )
+    parser_test.add_argument(
+        'first',
+        metavar="first",
+        choices=schema_remarks.version_order,
+        help=(
+            "The starting version of the schemas to compare, or the single version to"
+            " display if 'last' is not provided."
+        ),
+    )
+    parser_test.add_argument(
+        'last',
+        metavar="last",
+        choices=schema_remarks.version_order,
+        nargs="?",
+        default=None,
+        help="The destination version of the schema to compare",
+    )
+    parser_test.add_argument(
+        '-f',
+        dest="file",
+        metavar='FILENAME',
+        type=argparse.FileType('w'),
+        help=(
+            "A file to write the generated schema doc to. Passing - will write it to"
+            " standard out."
+        ),
+    )
+    parser_test.set_defaults(func=test_schema_remarks)
+    parser_generate = subparsers.add_parser(
+        'generate',
+        help=(
+            'Add all of the missing remarks from a new schema to schema_remarks.py for'
+            ' you as TODO items.'
+        ),
+        description=(
+            'Add all of the missing remarks from a new schema to schema_remarks.py for'
+            ' you to as TODO items. Saves you the trouble of searching through the'
+            ' massive file looking for the right spot in alphabetical order to put'
+            ' them. Two benefits: gets a schema live faster (just without things'
+            ' documented), and you can search for TODO in the file to find the things'
+            ' that need updating.'
+        ),
+    )
+    parser_generate.add_argument(
+        'first',
+        metavar="first",
+        choices=schema_remarks.version_order,
+        help=(
+            "The starting version of the schemas to compare, or the single version to"
+            " display if 'last' is not provided."
+        ),
+    )
+    parser_generate.add_argument(
+        'last',
+        metavar="last",
+        choices=schema_remarks.version_order,
+        nargs="?",
+        default=None,
+        help="The destination version of the schema to compare",
+    )
+    parser_generate.set_defaults(func=generate_schema_remarks)
+    main_args = parser.parse_args()
+    main_args.func(main_args)
+
+# A. REFERENCES
+#
+#
+# B. DOCUMENT HISTORY
+#
+# 2001-03-08 NB Created.
+#
+#
+# C. COPYRIGHT AND LICENSE
+#
+# This file is copyright (c) 2001 Perforce Software, Inc.  All rights
+# reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+#
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in
+#     the documentation and/or other materials provided with the
+#     distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+#
+#
+# $Id$
diff --git a/schema_remarks.py b/schema_remarks.py
index 34b6d6f..ece48c9 100644
--- a/schema_remarks.py
+++ b/schema_remarks.py
@@ -273,6 +273,10 @@
 
 version_schema_map = {
     '2.0': '2.0',
+    '2.2': '2.2',
+    '2.4': '2.4',
+    '2.6': '2.6',
+    '2.8': '2.8',
     '2.10': '2.10',
     '2.12': '2.12',
     '2.14': '2.14',
@@ -281,10 +285,10 @@
     '2.14.3': '2.14.2',
     '2.14.4': '2.14.2',
     '2.14.5': '2.14.2',
+    '2.16rc1': '2.16',
+    '2.16rc2': '2.16',
     '2.16': '2.16',
     '2.16.1': '2.16',
-    '2.16.10': '2.16',
-    '2.16.11': '2.16',
     '2.16.2': '2.16',
     '2.16.3': '2.16',
     '2.16.4': '2.16',
@@ -293,14 +297,17 @@
     '2.16.7': '2.16',
     '2.16.8': '2.16',
     '2.16.9': '2.16',
-    '2.16rc1': '2.16',
-    '2.16rc2': '2.16',
+    '2.16.10': '2.16',
+    '2.16.11': '2.16',
     '2.17.1': '2.17.1',
     '2.17.3': '2.17.3',
     '2.17.4': '2.17.4',
     '2.17.5': '2.17.5',
     '2.17.6': '2.17.5',
     '2.17.7': '2.17.7',
+    '2.18rc1': '2.18rc1',
+    '2.18rc2': '2.18rc1',
+    '2.18rc3': '2.18rc3',
     '2.18': '2.18rc3',
     '2.18.1': '2.18.1',
     '2.18.2': '2.18.2',
@@ -308,13 +315,11 @@
     '2.18.4': '2.18.2',
     '2.18.5': '2.18.2',
     '2.18.6': '2.18.2',
-    '2.18rc1': '2.18rc1',
-    '2.18rc2': '2.18rc1',
-    '2.18rc3': '2.18rc3',
     '2.19.1': '2.19.1',
     '2.19.2': '2.19.2',
     '2.19.3': '2.19.3',
-    '2.2': '2.2',
+    '2.20rc1': '2.20rc1',
+    '2.20rc2': '2.20rc2',
     '2.20': '2.20rc2',
     '2.20.1': '2.20rc2',
     '2.20.2': '2.20rc2',
@@ -323,9 +328,8 @@
     '2.20.5': '2.20rc2',
     '2.20.6': '2.20rc2',
     '2.20.7': '2.20rc2',
-    '2.20rc1': '2.20rc1',
-    '2.20rc2': '2.20rc2',
     '2.21.1': '2.21.1',
+    '2.22rc1': '2.22rc1',
     '2.22': '2.22rc1',
     '2.22.1': '2.22rc1',
     '2.22.2': '2.22rc1',
@@ -334,18 +338,13 @@
     '2.22.5': '2.22rc1',
     '2.22.6': '2.22rc1',
     '2.22.7': '2.22rc1',
-    '2.22rc1': '2.22rc1',
     '2.23.1': '2.23.1',
     '2.23.2': '2.23.2',
     '2.23.3': '2.23.3',
     '2.23.4': '2.23.4',
-    '2.4': '2.4',
-    '2.6': '2.6',
-    '2.8': '2.8',
+    '3.0rc1': '2.23.4',
     '3.0': '2.23.4',
     '3.0.1': '2.23.4',
-    '3.0.10': '2.23.4',
-    '3.0.11': '2.23.4',
     '3.0.2': '2.23.4',
     '3.0.3': '2.23.4',
     '3.0.4': '2.23.4',
@@ -354,14 +353,16 @@
     '3.0.7': '2.23.4',
     '3.0.8': '2.23.4',
     '3.0.9': '2.23.4',
-    '3.0rc1': '2.23.4',
+    '3.0.10': '2.23.4',
+    '3.0.11': '2.23.4',
     '3.1.1': '3.1.1',
     '3.1.2': '3.1.2',
     '3.1.3': '3.1.3',
     '3.1.4': '3.1.4',
+    '3.2rc1': '3.1.4',
+    '3.2rc2': '3.1.4',
     '3.2': '3.1.4',
     '3.2.1': '3.1.4',
-    '3.2.10': '3.1.4',
     '3.2.2': '3.1.4',
     '3.2.3': '3.1.4',
     '3.2.4': '3.1.4',
@@ -370,19 +371,14 @@
     '3.2.7': '3.1.4',
     '3.2.8': '3.1.4',
     '3.2.9': '3.1.4',
-    '3.2rc1': '3.1.4',
-    '3.2rc2': '3.1.4',
+    '3.2.10': '3.1.4',
     '3.3.1': '3.3.1',
     '3.3.2': '3.3.2',
     '3.3.3': '3.3.2',
     '3.3.4': '3.3.4',
+    '3.4rc1': '3.3.4',
     '3.4': '3.3.4',
     '3.4.1': '3.3.4',
-    '3.4.10': '3.3.4',
-    '3.4.11': '3.3.4',
-    '3.4.12': '3.3.4',
-    '3.4.13': '3.3.4',
-    '3.4.14': '3.3.4',
     '3.4.2': '3.3.4',
     '3.4.3': '3.3.4',
     '3.4.4': '3.3.4',
@@ -391,16 +387,17 @@
     '3.4.7': '3.3.4',
     '3.4.8': '3.3.4',
     '3.4.9': '3.3.4',
-    '3.4rc1': '3.3.4',
+    '3.4.10': '3.3.4',
+    '3.4.11': '3.3.4',
+    '3.4.12': '3.3.4',
+    '3.4.13': '3.3.4',
+    '3.4.14': '3.3.4',
     '3.5.1': '3.5.1',
     '3.5.2': '3.5.1',
     '3.5.3': '3.5.3',
+    '3.6rc1': '3.5.3',
     '3.6': '3.5.3',
     '3.6.1': '3.5.3',
-    '3.6.10': '3.5.3',
-    '3.6.11': '3.5.3',
-    '3.6.12': '3.5.3',
-    '3.6.13': '3.5.3',
     '3.6.2': '3.5.3',
     '3.6.3': '3.5.3',
     '3.6.4': '3.5.3',
@@ -409,12 +406,25 @@
     '3.6.7': '3.5.3',
     '3.6.8': '3.5.3',
     '3.6.9': '3.5.3',
-    '3.6rc1': '3.5.3',
+    '3.6.10': '3.5.3',
+    '3.6.11': '3.5.3',
+    '3.6.12': '3.5.3',
+    '3.6.13': '3.5.3',
     '3.7.1': '3.7.1',
     '3.7.2': '3.7.2',
     '3.7.3': '3.7.2',
+    '4.0rc1': '4.0rc1',
+    '4.0rc2': '4.0rc1',
     '4.0': '4.0rc1',
     '4.0.1': '4.0rc1',
+    '4.0.2': '4.0rc1',
+    '4.0.3': '4.0rc1',
+    '4.0.4': '4.0rc1',
+    '4.0.5': '4.0rc1',
+    '4.0.6': '4.0rc1',
+    '4.0.7': '4.0rc1',
+    '4.0.8': '4.0rc1',
+    '4.0.9': '4.0rc1',
     '4.0.10': '4.0rc1',
     '4.0.11': '4.0rc1',
     '4.0.12': '4.0rc1',
@@ -424,28 +434,13 @@
     '4.0.16': '4.0rc1',
     '4.0.17': '4.0rc1',
     '4.0.18': '4.0rc1',
-    '4.0.2': '4.0rc1',
-    '4.0.3': '4.0rc1',
-    '4.0.4': '4.0rc1',
-    '4.0.5': '4.0rc1',
-    '4.0.6': '4.0rc1',
-    '4.0.7': '4.0rc1',
-    '4.0.8': '4.0rc1',
-    '4.0.9': '4.0rc1',
-    '4.0rc1': '4.0rc1',
-    '4.0rc2': '4.0rc1',
     '4.1.1': '4.1.1',
     '4.1.2': '4.1.1',
     '4.1.3': '4.1.3',
+    '4.2rc1': '4.2rc1',
+    '4.2rc2': '4.2rc1',
     '4.2': '4.2',
     '4.2.1': '4.2.1',
-    '4.2.10': '4.2.1',
-    '4.2.11': '4.2.1',
-    '4.2.12': '4.2.1',
-    '4.2.13': '4.2.1',
-    '4.2.14': '4.2.1',
-    '4.2.15': '4.2.1',
-    '4.2.16': '4.2.1',
     '4.2.2': '4.2.1',
     '4.2.3': '4.2.1',
     '4.2.4': '4.2.1',
@@ -454,18 +449,20 @@
     '4.2.7': '4.2.1',
     '4.2.8': '4.2.1',
     '4.2.9': '4.2.1',
-    '4.2rc1': '4.2rc1',
-    '4.2rc2': '4.2rc1',
+    '4.2.10': '4.2.1',
+    '4.2.11': '4.2.1',
+    '4.2.12': '4.2.1',
+    '4.2.13': '4.2.1',
+    '4.2.14': '4.2.1',
+    '4.2.15': '4.2.1',
+    '4.2.16': '4.2.1',
     '4.3.1': '4.3.1',
     '4.3.2': '4.3.2',
     '4.3.3': '4.3.3',
+    '4.4rc1': '4.3.3',
+    '4.4rc2': '4.4rc2',
     '4.4': '4.4rc2',
     '4.4.1': '4.4rc2',
-    '4.4.10': '4.4rc2',
-    '4.4.11': '4.4rc2',
-    '4.4.12': '4.4rc2',
-    '4.4.13': '4.4rc2',
-    '4.4.14': '4.4rc2',
     '4.4.2': '4.4rc2',
     '4.4.3': '4.4rc2',
     '4.4.4': '4.4rc2',
@@ -474,14 +471,20 @@
     '4.4.7': '4.4rc2',
     '4.4.8': '4.4rc2',
     '4.4.9': '4.4rc2',
-    '4.4rc1': '4.3.3',
-    '4.4rc2': '4.4rc2',
+    '4.4.10': '4.4rc2',
+    '4.4.11': '4.4rc2',
+    '4.4.12': '4.4rc2',
+    '4.4.13': '4.4rc2',
+    '4.4.14': '4.4rc2',
     '4.5.1': '4.5.1',
     '4.5.2': '4.5.2',
     '4.5.3': '4.5.2',
     '4.5.4': '4.5.2',
     '4.5.5': '4.5.5',
     '4.5.6': '4.5.6',
+    '5.0rc1': '5.0rc1',
+    '5.0rc2': '5.0rc1',
+    '5.0rc3': '5.0rc1',
     '5.0': '5.0rc1',
     '5.0.1': '5.0rc1',
     '5.0.2': '5.0rc1',
@@ -490,12 +493,9 @@
     '5.0.4.1': '5.0rc1',
     '5.0.5': '5.0rc1',
     '5.0.6': '5.0.6',
-    '5.0rc1': '5.0rc1',
-    '5.0rc2': '5.0rc1',
-    '5.0rc3': '5.0rc1',
+    '5.2': '5.0.6',
     '5.1.1': '5.1.1',
     '5.1.2': '5.1.1',
-    '5.2': '5.0.6',
     '5.3.3': '5.1.1',
     '5.9.1': '5.9.1',
 }
@@ -648,7 +648,7 @@
     ('3.4.14', '2012-01-31', 'A security patch release'),
     ('3.6.8', '2012-01-31', 'A security patch release'),
     ('4.0.4', '2012-01-31', 'A security patch release'),
-    ('4.2rc1', '2012-01-31', 'A release candidate'),
+    ('4.2rc2', '2012-01-31', 'A release candidate'),
     ('4.0.5', '2012-02-22', 'A patch release'),
     ('4.2', '2012-02-22', ''),
     ('3.6.9', '2012-04-18', 'A security patch release'),
@@ -728,8 +728,10 @@
     (
         '5.3.3',
         '2024-05-??',
-        'A development release following 5.1.2 (the branch was renamed). '
-        'Not-yet-released.',
+        (
+            'A development release following 5.1.2 (the branch was renamed). '
+            'Not-yet-released.'
+        ),
     ),
     ('5.9.1', '2024-05-??', 'A not-yet-released development release'),
 ]
@@ -1579,9 +1581,11 @@
         'description': 'A description of the component.',
         'id': 'The component id.',
         'initialowner': [
-            'The default initial owner of bugs in this '
-            'component.  On component creation, this is '
-            'set to the user who creates the component.',
+            (
+                'The default initial owner of bugs in this '
+                'component.  On component creation, this is '
+                'set to the user who creates the component.'
+            ),
             (
                 None,
                 '2.10',
@@ -1590,10 +1594,12 @@
             ('2.12', None, '%(VERSION_STRING)sforeign key %(column-profiles-userid)s.'),
         ],
         'initialqacontact': [
-            'The initial "qa_contact" field for bugs '
-            'of this component. Note that the use of '
-            'the qa_contact field is optional, '
-            'parameterized by Param("useqacontact").',
+            (
+                'The initial "qa_contact" field for bugs '
+                'of this component. Note that the use of '
+                'the qa_contact field is optional, '
+                'parameterized by Param("useqacontact").'
+            ),
             (
                 None,
                 '2.10',
@@ -1708,26 +1714,34 @@
             (
                 '2.23.1',
                 None,
-                '%(VERSION_STRING)s1 (FIELD_TYPE_FREETEXT) for a '
-                'single-line text field. ',
+                (
+                    '%(VERSION_STRING)s1 (FIELD_TYPE_FREETEXT) for a '
+                    'single-line text field. '
+                ),
             ),
             (
                 '2.23.3',
                 None,
-                '%(VERSION_STRING)s2 (FIELD_TYPE_SINGLE_SELECT) for a '
-                'single-select field. ',
+                (
+                    '%(VERSION_STRING)s2 (FIELD_TYPE_SINGLE_SELECT) for a '
+                    'single-select field. '
+                ),
             ),
             (
                 '3.1.2',
                 None,
-                '%(VERSION_STRING)s3 (FIELD_TYPE_MULTI_SELECT) for a '
-                'multi-select field. ',
+                (
+                    '%(VERSION_STRING)s3 (FIELD_TYPE_MULTI_SELECT) for a '
+                    'multi-select field. '
+                ),
             ),
             (
                 '3.1.2',
                 None,
-                '%(VERSION_STRING)s4 (FIELD_TYPE_TEXTAREA) for a '
-                'large text box field. ',
+                (
+                    '%(VERSION_STRING)s4 (FIELD_TYPE_TEXTAREA) for a '
+                    'large text box field. '
+                ),
             ),
             (
                 '3.1.3',
@@ -2211,9 +2225,11 @@
             (
                 None,
                 '2.12',
-                '%(VERSION_STRING)sThe MySQL function '
-                '<code>encrypt</code> is used to encrypt '
-                'passwords.',
+                (
+                    '%(VERSION_STRING)sThe MySQL function '
+                    '<code>encrypt</code> is used to encrypt '
+                    'passwords.'
+                ),
             ),
             (
                 '2.14',
@@ -2260,14 +2276,18 @@
             (
                 None,
                 '5.2',
-                "The user's email address.  Used when logging in "
-                "or providing mailto: links.",
+                (
+                    "The user's email address.  Used when logging in "
+                    "or providing mailto: links."
+                ),
             ),
             (
                 '5.1.1',
                 None,
-                "The user's username. Used when logging in and "
-                "displayed to other users.",
+                (
+                    "The user's username. Used when logging in and "
+                    "displayed to other users."
+                ),
             ),
         ],
         'mfa': 'TODO',
@@ -2421,19 +2441,25 @@
             'The series category. (foreign key %(column-series_categories-id)s)'
         ),
         'creator': [
-            'The user who created this series (foreign key '
-            '%(column-profiles-userid)s).',
+            (
+                'The user who created this series (foreign key '
+                '%(column-profiles-userid)s).'
+            ),
             (
                 None,
                 '2.23.2',
-                '%(VERSION_STRING)s 0 if this series is created by '
-                'checksetup when first installing Bugzilla.',
+                (
+                    '%(VERSION_STRING)s 0 if this series is created by '
+                    'checksetup when first installing Bugzilla.'
+                ),
             ),
             (
                 '2.23.3',
                 None,
-                '%(VERSION_STRING)s NULL if this series is created by '
-                'checksetup when first installing Bugzilla.',
+                (
+                    '%(VERSION_STRING)s NULL if this series is created by '
+                    'checksetup when first installing Bugzilla.'
+                ),
             ),
         ],
         'frequency': 'The period between data samples for this series, in days.',
@@ -3735,216 +3761,237 @@
 # schema itself.
 
 prelude = [
-    '\n\n<center>\n<p>Quick links to <a href="#notes-tables">table'
-    ' definitions</a>:</p>\n\n%(QUICK_TABLES_TABLE)s</center>\n\n<h2><a id="section-1"'
-    ' name="section-1">1. Introduction</a></h2>\n\n<p>This document describes the'
-    ' Bugzilla database schema for Bugzilla\n%(BUGZILLA_VERSIONS)s.</p>\n\n<p>This'
-    ' document is generated automatically by a Python script which\nconstructs and'
-    ' colors the schema tables from the stored results of\nMySQL queries.  For more'
-    ' information about the scripts, see'
-    ' <a\nhref="https://github.com/bugzilla/bugzilla-schema">GitHub</a>.</p>\n\n<p>The'
-    ' purpose of this document is to act as a reference for\ndevelopers of Bugzilla and'
-    ' of code which interacts with Bugzilla\n(e.g. P4DTI).</p>\n\n<p>The intended'
-    ' readership is P4DTI developers and Bugzilla developers\nand'
-    ' administrators.</p>\n\n<p>This document is not confidential.</p>\n\n<p>Please <a'
-    ' href="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&amp;component=bugzilla.org&amp;short_desc=[schematool]%%20change%%20this%%20to%%20your%%20description">file'
-    ' a bug report</a> if you find any issues or what a new feature.</p>\n\n<h2><a'
-    ' id="section-2" name="section-2">2. Bugzilla overview</a></h2>\n\n<p>Bugzilla is a'
-    ' defect tracking system, written in Perl with a CGI\nweb GUI.  By default it uses'
-    ' MySQL to store its tables.',
+    (
+        '\n\n<center>\n<p>Quick links to <a href="#notes-tables">table'
+        ' definitions</a>:</p>\n\n%(QUICK_TABLES_TABLE)s</center>\n\n<h2><a'
+        ' id="section-1" name="section-1">1. Introduction</a></h2>\n\n<p>This document'
+        ' describes the Bugzilla database schema for'
+        ' Bugzilla\n%(BUGZILLA_VERSIONS)s.</p>\n\n<p>This document is generated'
+        ' automatically by a Python script which\nconstructs and colors the schema'
+        ' tables from the stored results of\nMySQL queries.  For more information about'
+        ' the scripts, see'
+        ' <a\nhref="https://github.com/bugzilla/bugzilla-schema">GitHub</a>.</p>\n\n<p>The'
+        ' purpose of this document is to act as a reference for\ndevelopers of Bugzilla'
+        ' and of code which interacts with Bugzilla\n(e.g. P4DTI).</p>\n\n<p>The'
+        ' intended readership is P4DTI developers and Bugzilla developers\nand'
+        ' administrators.</p>\n\n<p>This document is not confidential.</p>\n\n<p>Please'
+        ' <a href="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&amp;component=bugzilla.org&amp;short_desc=[schematool]%%20change%%20this%%20to%%20your%%20description">file'
+        ' a bug report</a> if you find any issues or what a new feature.</p>\n\n<h2><a'
+        ' id="section-2" name="section-2">2. Bugzilla overview</a></h2>\n\n<p>Bugzilla'
+        ' is a defect tracking system, written in Perl with a CGI\nweb GUI.  By default'
+        ' it uses MySQL to store its tables.'
+    ),
     ('2.22', None, '%(VERSION_STRING)s PostgreSQL is also supported.'),
-    '</p>\n'
-    '\n'
-    '%(NOTATION_GUIDE)s\n'
-    '\n'
-    '<h3><a id="notes-bugs" name="notes-bugs">Bugs</a></h3>\n'
-    '\n'
-    '<p>Each defect is called a <b>bug</b> and corresponds to one row in\n'
-    '%(the-table-bugs)s.  It is identified by its number,\n'
-    '%(column-bugs-bug_id)s.</p>\n'
-    '\n'
-    '<h3><a id="notes-products" name="notes-products">Products and '
-    'components</a></h3>\n'
-    '\n'
-    '<p>The work managed by Bugzilla is divided into products.  The work\n'
-    'for each product is in turn divided into the components of that\n'
-    'product.  Several properties of a new bug (e.g. ownership) are\n'
-    'determined by the product and component to which it belongs.  Each\n'
-    'component is represented by a row in %(the-table-components)s.',
+    (
+        '</p>\n'
+        '\n'
+        '%(NOTATION_GUIDE)s\n'
+        '\n'
+        '<h3><a id="notes-bugs" name="notes-bugs">Bugs</a></h3>\n'
+        '\n'
+        '<p>Each defect is called a <b>bug</b> and corresponds to one row in\n'
+        '%(the-table-bugs)s.  It is identified by its number,\n'
+        '%(column-bugs-bug_id)s.</p>\n'
+        '\n'
+        '<h3><a id="notes-products" name="notes-products">Products and '
+        'components</a></h3>\n'
+        '\n'
+        '<p>The work managed by Bugzilla is divided into products.  The work\n'
+        'for each product is in turn divided into the components of that\n'
+        'product.  Several properties of a new bug (e.g. ownership) are\n'
+        'determined by the product and component to which it belongs.  Each\n'
+        'component is represented by a row in %(the-table-components)s.'
+    ),
     (
         '2.2',
         None,
-        ' %(VERSION_STRING)sEach product is represented by a\n'
-        'row in %(the-table-products)s.',
+        (
+            ' %(VERSION_STRING)sEach product is represented by a\n'
+            'row in %(the-table-products)s.'
+        ),
     ),
     '</p>',
     (
         '2.19.1',
         None,
+        (
+            '\n'
+            '\n'
+            '<p>%(VERSION_STRING)sProducts are grouped by "classification".  This\n'
+            'is optional and controlled by the parameter \'useclassification\'.  The\n'
+            'classifications are used to help in finding bugs and in constructing\n'
+            'meaningful time series, but have no other semantics in Bugzilla.\n'
+            'There is a default classification, with ID 1, meaning\n'
+            '"Unclassified".</p> '
+        ),
+    ),
+    (
+        '<h3><a id="notes-workflow" name="notes-workflow">Workflow</a></h3>\n'
         '\n'
-        '\n'
-        '<p>%(VERSION_STRING)sProducts are grouped by "classification".  This\n'
-        'is optional and controlled by the parameter \'useclassification\'.  The\n'
-        'classifications are used to help in finding bugs and in constructing\n'
-        'meaningful time series, but have no other semantics in Bugzilla.\n'
-        'There is a default classification, with ID 1, meaning\n'
-        '"Unclassified".</p> ',
+        '<p>Each bug has a status (%(column-bugs-bug_status)s).  If a bug has a\n'
+        'status which shows it has been resolved, it also has a resolution\n'
+        '(%(column-bugs-resolution)s), otherwise the resolution field is empty.</p>'
     ),
-    '<h3><a id="notes-workflow" name="notes-workflow">Workflow</a></h3>\n'
-    '\n'
-    '<p>Each bug has a status (%(column-bugs-bug_status)s).  If a bug has a\n'
-    'status which shows it has been resolved, it also has a resolution\n'
-    '(%(column-bugs-resolution)s), otherwise the resolution field is empty.</p>',
     (
         '3.1.1',
         None,
-        '<p>%(VERSION_STRING)sWorkflow is configurable.  The\n'
-        'possible status values are stored in %(the-table-bug_status)s; the\n'
-        'transitions in %(the-table-status_workflow)s.</p>',
+        (
+            '<p>%(VERSION_STRING)sWorkflow is configurable.  The\n'
+            'possible status values are stored in %(the-table-bug_status)s; the\n'
+            'transitions in %(the-table-status_workflow)s.</p>'
+        ),
     ),
-    '<p>This table shows the possible values and valid transitions of\n'
-    'the status field in the default workflow.</p>\n'
-    '\n'
-    '<table border="1" cellspacing="0" cellpadding="5">\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <th>Status</th>\n'
-    '\n'
-    '    <th>Resolved?</th>\n'
-    '\n'
-    '    <th>Description</th>\n'
-    '\n'
-    '    <th>Transitions</th>\n'
-    '\n'
-    '  </tr>\n'
-    '\n',
     (
-        '2.10',
-        '3.6.13',
-        '<tr%(VERSION_COLOUR)s valign="top" align="left">\n'
+        '<p>This table shows the possible values and valid transitions of\n'
+        'the status field in the default workflow.</p>\n'
         '\n'
-        '    <td>UNCONFIRMED</td>\n'
+        '<table border="1" cellspacing="0" cellpadding="5">\n'
+        '  <tr valign="top" align="left">\n'
         '\n'
-        '    <td>No</td>\n'
+        '    <th>Status</th>\n'
         '\n'
-        '    <td>%(VERSION_STRING)sA new bug, when a product has voting</td>\n'
+        '    <th>Resolved?</th>\n'
         '\n'
-        '    <td>to NEW by voting or confirmation<br />\n'
-        '        to ASSIGNED by acceptance<br />\n'
-        '        to RESOLVED by resolution<br />\n'
-        '    </td>\n'
+        '    <th>Description</th>\n'
+        '\n'
+        '    <th>Transitions</th>\n'
+        '\n'
+        '  </tr>\n'
         '\n'
-        '  </tr>',
+    ),
+    (
+        '2.10',
+        '3.6.13',
+        (
+            '<tr%(VERSION_COLOUR)s valign="top" align="left">\n'
+            '\n'
+            '    <td>UNCONFIRMED</td>\n'
+            '\n'
+            '    <td>No</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sA new bug, when a product has voting</td>\n'
+            '\n'
+            '    <td>to NEW by voting or confirmation<br />\n'
+            '        to ASSIGNED by acceptance<br />\n'
+            '        to RESOLVED by resolution<br />\n'
+            '    </td>\n'
+            '\n'
+            '  </tr>'
+        ),
     ),
     (
         '3.7.1',
         None,
-        '<tr%(VERSION_COLOUR)s valign="top" align="left">\n'
+        (
+            '<tr%(VERSION_COLOUR)s valign="top" align="left">\n'
+            '\n'
+            '    <td>UNCONFIRMED</td>\n'
+            '\n'
+            '    <td>No</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sA new bug, when a product allows '
+            'UNCONFIRMED</td>\n'
+            '\n'
+            '    <td>to NEW by confirmation<br />\n'
+            '        to ASSIGNED by acceptance<br />\n'
+            '        to RESOLVED by resolution<br />\n'
+            '    </td>\n'
+            '\n'
+            '  </tr>'
+        ),
+    ),
+    (
+        '<tr valign="top" align="left">\n'
+        '\n'
+        '    <td>NEW</td>\n'
+        '\n'
+        '    <td>No</td>\n'
+        '\n'
+        '    <td>Recently added or confirmed</td>\n'
+        '\n'
+        '    <td>to ASSIGNED by acceptance<br />\n'
+        '        to RESOLVED by analysis and maybe fixing<br />\n'
+        '        to NEW by reassignment<br />\n'
+        '    </td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top" align="left">\n'
+        '\n'
+        '    <td>ASSIGNED</td>\n'
+        '\n'
+        '    <td>No</td>\n'
+        '\n'
+        '    <td>Has been assigned</td>\n'
+        '\n'
+        '    <td>to NEW by reassignment<br />\n'
+        '        to RESOLVED by analysis and maybe fixing<br />\n'
+        '    </td>\n'
         '\n'
-        '    <td>UNCONFIRMED</td>\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top" align="left">\n'
+        '\n'
+        '    <td>REOPENED</td>\n'
         '\n'
         '    <td>No</td>\n'
         '\n'
-        '    <td>%(VERSION_STRING)sA new bug, when a product allows '
-        'UNCONFIRMED</td>\n'
+        '    <td>Was once resolved but has been reopened</td>\n'
         '\n'
-        '    <td>to NEW by confirmation<br />\n'
+        '    <td>to NEW by reassignment<br />\n'
         '        to ASSIGNED by acceptance<br />\n'
-        '        to RESOLVED by resolution<br />\n'
+        '        to RESOLVED by analysis and maybe fixing<br />\n'
+        '    </td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top" align="left">\n'
+        '\n'
+        '    <td>RESOLVED</td>\n'
+        '\n'
+        '    <td>Yes</td>\n'
+        '\n'
+        '    <td>Has been resolved (e.g. fixed, deemed unfixable, etc.  See '
+        '"resolution" column)</td>\n'
+        '\n'
+        '    <td>to REOPENED by reopening<br />\n'
+        '        to VERIFIED by verification<br />\n'
+        '        to CLOSED by closing<br />\n'
+        '    </td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top" align="left">\n'
+        '\n'
+        '    <td>VERIFIED</td>\n'
+        '\n'
+        '    <td>Yes</td>\n'
+        '\n'
+        '    <td>The resolution has been approved by QA</td>\n'
+        '\n'
+        '    <td>to CLOSED when the product ships<br />\n'
+        '        to REOPENED by reopening<br />\n'
         '    </td>\n'
         '\n'
-        '  </tr>',
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top" align="left">\n'
+        '\n'
+        '    <td>CLOSED</td>\n'
+        '\n'
+        '    <td>Yes</td>\n'
+        '\n'
+        '    <td>Over and done with</td>\n'
+        '\n'
+        '    <td>to REOPENED by reopening</td>\n'
+        '\n'
+        '  </tr>\n'
+        '</table>\n'
+        '\n'
+        '<p>This table shows the allowable values of the resolution field.  The\n'
+        'values "FIXED", "MOVED", and "DUPLICATE" have special meaning for\n'
+        'Bugzilla.  The other values may be changed, '
     ),
-    '<tr valign="top" align="left">\n'
-    '\n'
-    '    <td>NEW</td>\n'
-    '\n'
-    '    <td>No</td>\n'
-    '\n'
-    '    <td>Recently added or confirmed</td>\n'
-    '\n'
-    '    <td>to ASSIGNED by acceptance<br />\n'
-    '        to RESOLVED by analysis and maybe fixing<br />\n'
-    '        to NEW by reassignment<br />\n'
-    '    </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <td>ASSIGNED</td>\n'
-    '\n'
-    '    <td>No</td>\n'
-    '\n'
-    '    <td>Has been assigned</td>\n'
-    '\n'
-    '    <td>to NEW by reassignment<br />\n'
-    '        to RESOLVED by analysis and maybe fixing<br />\n'
-    '    </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <td>REOPENED</td>\n'
-    '\n'
-    '    <td>No</td>\n'
-    '\n'
-    '    <td>Was once resolved but has been reopened</td>\n'
-    '\n'
-    '    <td>to NEW by reassignment<br />\n'
-    '        to ASSIGNED by acceptance<br />\n'
-    '        to RESOLVED by analysis and maybe fixing<br />\n'
-    '    </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <td>RESOLVED</td>\n'
-    '\n'
-    '    <td>Yes</td>\n'
-    '\n'
-    '    <td>Has been resolved (e.g. fixed, deemed unfixable, etc.  See '
-    '"resolution" column)</td>\n'
-    '\n'
-    '    <td>to REOPENED by reopening<br />\n'
-    '        to VERIFIED by verification<br />\n'
-    '        to CLOSED by closing<br />\n'
-    '    </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <td>VERIFIED</td>\n'
-    '\n'
-    '    <td>Yes</td>\n'
-    '\n'
-    '    <td>The resolution has been approved by QA</td>\n'
-    '\n'
-    '    <td>to CLOSED when the product ships<br />\n'
-    '        to REOPENED by reopening<br />\n'
-    '    </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <td>CLOSED</td>\n'
-    '\n'
-    '    <td>Yes</td>\n'
-    '\n'
-    '    <td>Over and done with</td>\n'
-    '\n'
-    '    <td>to REOPENED by reopening</td>\n'
-    '\n'
-    '  </tr>\n'
-    '</table>\n'
-    '\n'
-    '<p>This table shows the allowable values of the resolution field.  The\n'
-    'values "FIXED", "MOVED", and "DUPLICATE" have special meaning for\n'
-    'Bugzilla.  The other values may be changed, ',
     (None, '2.19.2', '%(VERSION_STRING)sby editing the schema of %(the-table-bugs)s, '),
     (
         '2.19.3',
@@ -3952,152 +3999,176 @@
         '%(VERSION_STRING)sby manually updating %(the-table-resolution)s, ',
     ),
     ('2.23.3', None, '%(VERSION_STRING)sby using editvalues.cgi, '),
-    'to add, remove, or rename values as necessary.</p>\n'
-    '\n'
-    '<table border="1" cellspacing="0" cellpadding="5">\n'
-    '  <tr valign="top" align="left">\n'
-    '\n'
-    '    <th>Resolution</th>\n'
-    '\n'
-    '    <th>Meaning</th>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>FIXED</td>\n'
-    '\n'
-    '    <td>The bug has been fixed.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>INVALID</td>\n'
-    '\n'
-    '    <td>The problem described is not a bug.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>WONTFIX</td>\n'
-    '\n'
-    '    <td>This bug will never be fixed.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>LATER</td>\n'
-    '\n'
-    '    <td>This bug will not be fixed in this version.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>REMIND</td>\n'
-    '\n'
-    '    <td>This bug probably won\'t be fixed in this version.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>DUPLICATE</td>\n'
-    '\n'
-    '    <td>This is a duplicate of an existing bug A description comment\n'
-    '        is added to this effect',
+    (
+        'to add, remove, or rename values as necessary.</p>\n'
+        '\n'
+        '<table border="1" cellspacing="0" cellpadding="5">\n'
+        '  <tr valign="top" align="left">\n'
+        '\n'
+        '    <th>Resolution</th>\n'
+        '\n'
+        '    <th>Meaning</th>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>FIXED</td>\n'
+        '\n'
+        '    <td>The bug has been fixed.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>INVALID</td>\n'
+        '\n'
+        '    <td>The problem described is not a bug.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>WONTFIX</td>\n'
+        '\n'
+        '    <td>This bug will never be fixed.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>LATER</td>\n'
+        '\n'
+        '    <td>This bug will not be fixed in this version.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>REMIND</td>\n'
+        '\n'
+        '    <td>This bug probably won\'t be fixed in this version.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>DUPLICATE</td>\n'
+        '\n'
+        '    <td>This is a duplicate of an existing bug A description comment\n'
+        '        is added to this effect'
+    ),
     (
         '2.12',
         None,
-        ', and %(VERSION_STRING)sa record is added to\n'
-        '        %(the-table-duplicates)s',
+        (
+            ', and %(VERSION_STRING)sa record is added to\n'
+            '        %(the-table-duplicates)s'
+        ),
+    ),
+    (
+        '.</td> </tr>\n'
+        '\n'
+        '  <tr>\n'
+        '\n'
+        '    <td>WORKSFORME</td>\n'
+        '\n'
+        '    <td>This bug could not be reproduced.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
     ),
-    '.</td> </tr>\n'
-    '\n'
-    '  <tr>\n'
-    '\n'
-    '    <td>WORKSFORME</td>\n'
-    '\n'
-    '    <td>This bug could not be reproduced.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n',
     (
         '2.12',
         None,
-        '  <tr%(VERSION_COLOUR)s>\n'
+        (
+            '  <tr%(VERSION_COLOUR)s>\n'
+            '\n'
+            '    <td>MOVED</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sThis bug has been moved to another '
+            'database.</td>\n'
+            '\n'
+            '</tr>\n'
+        ),
+    ),
+    (
+        '</table>\n'
         '\n'
-        '    <td>MOVED</td>\n'
+        '<h3><a id="notes-users" name="notes-users">Users</a></h3>\n'
         '\n'
-        '    <td>%(VERSION_STRING)sThis bug has been moved to another '
-        'database.</td>\n'
+        '<p>Bugzilla has users.  Each user is represented by one row in\n'
+        '%(the-table-profiles)s.  Each user is referred by a number\n'
+        '(%(column-profiles-userid)s) and an email address\n'
+        '(%(column-profiles-login_name)s).</p>\n'
+        '\n'
+        '<h3><a id="notes-authentication" '
+        'name="notes-authentication">Authentication</a></h3>\n'
         '\n'
-        '</tr>\n',
     ),
-    '</table>\n'
-    '\n'
-    '<h3><a id="notes-users" name="notes-users">Users</a></h3>\n'
-    '\n'
-    '<p>Bugzilla has users.  Each user is represented by one row in\n'
-    '%(the-table-profiles)s.  Each user is referred by a number\n'
-    '(%(column-profiles-userid)s) and an email address\n'
-    '(%(column-profiles-login_name)s).</p>\n'
-    '\n'
-    '<h3><a id="notes-authentication" '
-    'name="notes-authentication">Authentication</a></h3>\n'
-    '\n',
     (
         '2.19.1',
         None,
-        '<p>%(VERSION_STRING)sThere are various\n'
-        'authentication mechanisms, including "environment variable\n'
-        'authentication" (Bugzilla/Auth/Login/WWW/Env.pm) which uses\n'
-        'environment variables to pass an external user ID\n'
-        '(%(column-profiles-extern_id)s) to the Bugzilla CGI.  The rest of this\n'
-        'section describes the password-based authentication which has always\n'
-        'been in Bugzilla and which is still widely used.</p>',
-    ),
-    '<p>Each user has a password, used to authenticate that user to\n'
-    'Bugzilla.  The password is stored in %(column-profiles-cryptpassword)s in\n'
-    'encrypted form.',
+        (
+            '<p>%(VERSION_STRING)sThere are various\n'
+            'authentication mechanisms, including "environment variable\n'
+            'authentication" (Bugzilla/Auth/Login/WWW/Env.pm) which uses\n'
+            'environment variables to pass an external user ID\n'
+            '(%(column-profiles-extern_id)s) to the Bugzilla CGI.  The rest of this\n'
+            'section describes the password-based authentication which has always\n'
+            'been in Bugzilla and which is still widely used.</p>'
+        ),
+    ),
+    (
+        '<p>Each user has a password, used to authenticate that user to\n'
+        'Bugzilla.  The password is stored in %(column-profiles-cryptpassword)s in\n'
+        'encrypted form.'
+    ),
     (
         None,
         '2.12',
-        '  %(VERSION_STRING)s it is also stored in\n'
-        '%(column-profiles-password)s as plaintext.',
+        (
+            '  %(VERSION_STRING)s it is also stored in\n'
+            '%(column-profiles-password)s as plaintext.'
+        ),
+    ),
+    (
+        '</p>\n'
+        '\n'
+        '<p>On a successful login, Bugzilla generates a pair of cookies for the\n'
+        'user\'s browser.  On subsequent accesses, a user gets access if these\n'
+        'cookie checks pass:</p>\n'
+        '\n'
+        '<ul>\n'
+        '\n'
+        '  <li>they have both Bugzilla_login and Bugzilla_logincookie cookies;</li>'
     ),
-    '</p>\n'
-    '\n'
-    '<p>On a successful login, Bugzilla generates a pair of cookies for the\n'
-    'user\'s browser.  On subsequent accesses, a user gets access if these\n'
-    'cookie checks pass:</p>\n'
-    '\n'
-    '<ul>\n'
-    '\n'
-    '  <li>they have both Bugzilla_login and Bugzilla_logincookie cookies;</li>',
     (
         None,
         '2.17.3',
-        '<li>%(VERSION_STRING)sTheir Bugzilla_login is the\n'
-        '  %(column-profiles-login_name)s of a row in\n'
-        '  %(the-table-profiles)s;</li>',
+        (
+            '<li>%(VERSION_STRING)sTheir Bugzilla_login is the\n'
+            '  %(column-profiles-login_name)s of a row in\n'
+            '  %(the-table-profiles)s;</li>'
+        ),
     ),
     (
         '2.17.4',
         None,
-        '<li>%(VERSION_STRING)sTheir Bugzilla_login is the\n'
-        '  %(column-profiles-userid)s of a row in\n'
-        '  %(the-table-profiles)s;</li>',
+        (
+            '<li>%(VERSION_STRING)sTheir Bugzilla_login is the\n'
+            '  %(column-profiles-userid)s of a row in\n'
+            '  %(the-table-profiles)s;</li>'
+        ),
+    ),
+    (
+        '\n'
+        '  <li>their Bugzilla_logincookie matches a row in '
+        '%(the-table-logincookies)s;</li>\n'
+        '\n'
+        '  <li>the userids of these two rows match;</li>\n'
+        '\n'
     ),
-    '\n'
-    '  <li>their Bugzilla_logincookie matches a row in '
-    '%(the-table-logincookies)s;</li>\n'
-    '\n'
-    '  <li>the userids of these two rows match;</li>\n'
-    '\n',
     (
         None,
         '2.14.5',
@@ -4106,18 +4177,22 @@
     (
         None,
         '2.14.1',
-        '<li>%(VERSION_STRING)s%(column-logincookies-hostname)s matches the CGI '
-        'REMOTE_HOST;</li>\n'
-        '\n'
-        '  ',
+        (
+            '<li>%(VERSION_STRING)s%(column-logincookies-hostname)s matches the CGI '
+            'REMOTE_HOST;</li>\n'
+            '\n'
+            '  '
+        ),
     ),
     (
         '2.14.2',
         None,
-        '<li>%(VERSION_STRING)s%(column-logincookies-ipaddr)s matches the CGI '
-        'REMOTE_ADDR;</li>\n'
-        '\n'
-        '  ',
+        (
+            '<li>%(VERSION_STRING)s%(column-logincookies-ipaddr)s matches the CGI '
+            'REMOTE_ADDR;</li>\n'
+            '\n'
+            '  '
+        ),
     ),
     (
         '2.10',
@@ -4129,71 +4204,83 @@
         None,
         '<li>%(VERSION_STRING)s%(column-profiles-is_enabled)s is 1.</li>\n  ',
     ),
-    '</ul>\n'
-    '\n'
-    '<p>If the cookie checks fail, the user has to login (with their\n'
-    'password), in which case a new row is added to\n'
-    '%(the-table-logincookies)s and the user gets a new pair of\n'
-    'cookies.</p>\n'
-    '\n'
-    '<p>Rows in %(the-table-logincookies)s are deleted after 30 days (at\n'
-    'user login time).</p>',
+    (
+        '</ul>\n'
+        '\n'
+        '<p>If the cookie checks fail, the user has to login (with their\n'
+        'password), in which case a new row is added to\n'
+        '%(the-table-logincookies)s and the user gets a new pair of\n'
+        'cookies.</p>\n'
+        '\n'
+        '<p>Rows in %(the-table-logincookies)s are deleted after 30 days (at\n'
+        'user login time).</p>'
+    ),
     (
         '2.8',
         '3.6.13',
-        '<h3><a id="notes-voting" name="notes-voting">Voting</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sUsers may vote for bugs which they think are\n'
-        'important.  A user can vote for a bug more than once.  Votes are\n'
-        'recorded in %(the-table-votes)s.</p>',
+        (
+            '<h3><a id="notes-voting" name="notes-voting">Voting</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sUsers may vote for bugs which they think are\n'
+            'important.  A user can vote for a bug more than once.  Votes are\n'
+            'recorded in %(the-table-votes)s.</p>'
+        ),
     ),
     (
         '2.10',
         '3.6.13',
-        '%(VERSION_STRING)sThe maximum number of votes per\n'
-        'bug per user is product-dependent.  Whether or not project managers\n'
-        'pay any attention to votes is up to them, apart from the "confirmation\n'
-        'by acclamation" process, which is as follows:</p>\n'
-        '\n'
-        '<p>New bugs have the status UNCONFIRMED.  To enter the main workflow,\n'
-        'they need the status NEW.  To get the status NEW, they need a\n'
-        'particular number of votes which is product-dependent.</p>',
+        (
+            '%(VERSION_STRING)sThe maximum number of votes per\n'
+            'bug per user is product-dependent.  Whether or not project managers\n'
+            'pay any attention to votes is up to them, apart from the "confirmation\n'
+            'by acclamation" process, which is as follows:</p>\n'
+            '\n'
+            '<p>New bugs have the status UNCONFIRMED.  To enter the main workflow,\n'
+            'they need the status NEW.  To get the status NEW, they need a\n'
+            'particular number of votes which is product-dependent.</p>'
+        ),
     ),
     (
         '2.10',
         None,
-        '<h3><a id="notes-milestones" name="notes-milestones">Milestones</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sProducts may have "milestones" defined.  The\n'
-        'intention is that a milestone should be a point in a project at which\n'
-        'a set of bugs has been resolved.  An example might be a product\n'
-        'release or a QA target.  Milestones may be turned on and off with the\n'
-        'parameter "usetargetmilestone".</p>\n'
-        '\n'
-        '<p>If milestones are on, each bug has a "target milestone" (by which\n'
-        'it should be fixed).  A product may have a URL associated with it\n'
-        'which locates a document describing the milestones for that product.\n'
-        'This document itself is entirely outside Bugzilla.  A product may also\n'
-        'have a default target milestone, which is given to new bugs.</p>\n'
+        (
+            '<h3><a id="notes-milestones" name="notes-milestones">Milestones</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sProducts may have "milestones" defined.  The\n'
+            'intention is that a milestone should be a point in a project at which\n'
+            'a set of bugs has been resolved.  An example might be a product\n'
+            'release or a QA target.  Milestones may be turned on and off with the\n'
+            'parameter "usetargetmilestone".</p>\n'
+            '\n'
+            '<p>If milestones are on, each bug has a "target milestone" (by which\n'
+            'it should be fixed).  A product may have a URL associated with it\n'
+            'which locates a document describing the milestones for that product.\n'
+            'This document itself is entirely outside Bugzilla.  A product may also\n'
+            'have a default target milestone, which is given to new bugs.</p>\n'
+            '\n'
+            '<p>Milestones for a product have a "sort key", which allows them to be\n'
+            'presented in a specific order in the user interface.</p>\n'
+            '\n'
+            '<p>Milestones are kept in %(the-table-milestones)s.</p>'
+        ),
+    ),
+    (
+        '<h3><a id="notes-versions" name="notes-versions">Versions</a></h3>\n'
+        '\n'
+        '<p>Products may have versions.  This allows more accurate bug\n'
+        'reporting: "we saw it in 1.3.7b3".'
+    ),
+    ('2.10', None, 'Versions are\ntotally independent of milestones.'),
+    (
+        '</p>\n'
         '\n'
-        '<p>Milestones for a product have a "sort key", which allows them to be\n'
-        'presented in a specific order in the user interface.</p>\n'
+        '<h3><a id="notes-parameters" name="notes-parameters">Parameters</a></h3>\n'
         '\n'
-        '<p>Milestones are kept in %(the-table-milestones)s.</p>',
+        '<p>The operation of Bugzilla is controlled by parameters.  These are\n'
+        'set in editparams.cgi.  The current values are stored in data/params.\n'
+        'They are <b>not</b> stored in the database.</p>\n'
+        '<p>'
     ),
-    '<h3><a id="notes-versions" name="notes-versions">Versions</a></h3>\n'
-    '\n'
-    '<p>Products may have versions.  This allows more accurate bug\n'
-    'reporting: "we saw it in 1.3.7b3".',
-    ('2.10', None, 'Versions are\ntotally independent of milestones.'),
-    '</p>\n'
-    '\n'
-    '<h3><a id="notes-parameters" name="notes-parameters">Parameters</a></h3>\n'
-    '\n'
-    '<p>The operation of Bugzilla is controlled by parameters.  These are\n'
-    'set in editparams.cgi.  The current values are stored in data/params.\n'
-    'They are <b>not</b> stored in the database.</p>\n'
-    '<p>',
     (
         None,
         '2.21.1',
@@ -4202,383 +4289,356 @@
     (
         '2.22rc1',
         None,
-        '%(VERSION_STRING)sThe set of parameters is defined in the modules in '
-        'Bugzilla/Config/.',
+        (
+            '%(VERSION_STRING)sThe set of parameters is defined in the modules in '
+            'Bugzilla/Config/.'
+        ),
     ),
     '</p>\n\n',
     (
         '2.4',
         None,
-        '<h3><a id="notes-groups" name="notes-groups">Groups</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugzilla has "groups" of users.  Membership of a\n'
-        'group allows a user to perform certain tasks.  Each group is\n'
-        'represented by a row of %(the-table-groups)s.</p>\n'
-        '\n'
-        '<p>There are a number of built-in groups, as follows:</p>\n'
-        '\n'
-        '<table border="1" cellspacing="0" cellpadding="5">\n'
-        '  <tr align="left" valign="top">\n'
-        '\n'
-        '    <th>Name</th>\n'
-        '\n'
-        '    <th>Description</th>\n'
-        '\n'
-        '  </tr>\n'
-        '\n',
+        (
+            '<h3><a id="notes-groups" name="notes-groups">Groups</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugzilla has "groups" of users.  Membership of a\n'
+            'group allows a user to perform certain tasks.  Each group is\n'
+            'represented by a row of %(the-table-groups)s.</p>\n'
+            '\n'
+            '<p>There are a number of built-in groups, as follows:</p>\n'
+            '\n'
+            '<table border="1" cellspacing="0" cellpadding="5">\n'
+            '  <tr align="left" valign="top">\n'
+            '\n'
+            '    <th>Name</th>\n'
+            '\n'
+            '    <th>Description</th>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+        ),
     ),
     (
         '2.17.1',
         None,
-        '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>admin</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)s Can administer all aspects of Bugzilla</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n',
+        (
+            '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>admin</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)s Can administer all aspects of Bugzilla</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+        ),
     ),
     (
         '2.4',
         None,
-        ' <tr align="left" valign="top">\n'
-        '\n'
-        '    <td>tweakparams</td>\n'
-        '\n'
-        '    <td>Can tweak operating parameters</td>\n'
-        '\n'
-        '  </tr>',
+        (
+            ' <tr align="left" valign="top">\n'
+            '\n'
+            '    <td>tweakparams</td>\n'
+            '\n'
+            '    <td>Can tweak operating parameters</td>\n'
+            '\n'
+            '  </tr>'
+        ),
     ),
     (
         '2.4',
         '2.8',
-        '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>editgroupmembers</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)sCan put people in and out of groups</td>\n'
-        '\n'
-        '  </tr>',
+        (
+            '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>editgroupmembers</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sCan put people in and out of groups</td>\n'
+            '\n'
+            '  </tr>'
+        ),
     ),
     (
         '2.10',
         None,
-        '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>editusers</td>\n'
-        '\n'
-        '    <td>Can edit or disable users</td>\n'
-        '\n'
-        '  </tr>',
+        (
+            '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>editusers</td>\n'
+            '\n'
+            '    <td>Can edit or disable users</td>\n'
+            '\n'
+            '  </tr>'
+        ),
     ),
     (
         '2.4',
         None,
-        '<tr align="left" valign="top">\n'
-        '\n'
-        '    <td>creategroups</td>\n'
-        '\n'
-        '    <td>Can create and destroy groups</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n'
-        '  <tr align="left" valign="top">\n'
-        '\n'
-        '    <td>editcomponents</td>\n'
-        '\n'
-        '    <td>Can create, destroy, and edit components and other controls (e.g. '
-        'flagtypes).</td>\n'
-        '\n'
-        '  </tr>',
+        (
+            '<tr align="left" valign="top">\n'
+            '\n'
+            '    <td>creategroups</td>\n'
+            '\n'
+            '    <td>Can create and destroy groups</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+            '  <tr align="left" valign="top">\n'
+            '\n'
+            '    <td>editcomponents</td>\n'
+            '\n'
+            '    <td>Can create, destroy, and edit components and other controls (e.g. '
+            'flagtypes).</td>\n'
+            '\n'
+            '  </tr>'
+        ),
     ),
     (
         '2.10',
         None,
-        '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>editkeywords</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)sCan create, destroy, and edit keywords</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n'
-        '  <tr %(VERSION_COLOUR)salign="left" valign="top">\n'
-        '\n'
-        '    <td>editbugs</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)sCan edit all aspects of any bug</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n'
-        '  <tr %(VERSION_COLOUR)salign="left" valign="top">\n'
-        '\n'
-        '    <td>canconfirm</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)sCan confirm a bug</td>\n'
-        '\n'
-        '  </tr>',
+        (
+            '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>editkeywords</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sCan create, destroy, and edit keywords</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+            '  <tr %(VERSION_COLOUR)salign="left" valign="top">\n'
+            '\n'
+            '    <td>editbugs</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sCan edit all aspects of any bug</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+            '  <tr %(VERSION_COLOUR)salign="left" valign="top">\n'
+            '\n'
+            '    <td>canconfirm</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)sCan confirm a bug</td>\n'
+            '\n'
+            '  </tr>'
+        ),
     ),
     (
         '2.19.1',
         None,
-        '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>editclassifications</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)s Can edit classifications</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n'
-        '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>bz_canusewhines</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)s Can configure whine reports for self</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n'
-        '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>bz_canusewhineatothers</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)s Can configure whine reports for other '
-        'users</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n',
+        (
+            '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>editclassifications</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)s Can edit classifications</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+            '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>bz_canusewhines</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)s Can configure whine reports for self</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+            '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>bz_canusewhineatothers</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)s Can configure whine reports for other '
+            'users</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+        ),
     ),
     (
         '2.22rc1',
         None,
-        '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>bz_sudoers</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)s Can impersonate another user</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n'
-        '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '\n'
-        '    <td>bz_sudo_protect</td>\n'
-        '\n'
-        '    <td>%(VERSION_STRING)s Cannot be impersonated</td>\n'
-        '\n'
-        '  </tr>\n'
-        '\n',
+        (
+            '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>bz_sudoers</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)s Can impersonate another user</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+            '  <tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '\n'
+            '    <td>bz_sudo_protect</td>\n'
+            '\n'
+            '    <td>%(VERSION_STRING)s Cannot be impersonated</td>\n'
+            '\n'
+            '  </tr>\n'
+            '\n'
+        ),
     ),
     (
         '2.4',
         None,
-        '</table>\n'
-        '\n'
-        '<p>New groups may be added and used to control access to sets of bugs.\n'
-        'These "bug groups" have %(column-groups-isbuggroup)s set to 1.  A bug\n'
-        'may be in any number of bug groups.  To see a bug, a user must be a\n'
-        'member of all the bug groups which the bug is in.</p>',
+        (
+            '</table>\n'
+            '\n'
+            '<p>New groups may be added and used to control access to sets of bugs.\n'
+            'These "bug groups" have %(column-groups-isbuggroup)s set to 1.  A bug\n'
+            'may be in any number of bug groups.  To see a bug, a user must be a\n'
+            'member of all the bug groups which the bug is in.</p>'
+        ),
     ),
     (
         '2.10',
         None,
-        '<p>%(VERSION_STRING)sIf the parameter "usebuggroups"\n'
-        'is on, each product automatically has a bug group associated with it.\n'
-        'If the parameter "usebuggroupsentry" is also on, a user must be in the\n'
-        'product\'s bug group in order to create new bugs for the\n'
-        'product.</p>',
+        (
+            '<p>%(VERSION_STRING)sIf the parameter "usebuggroups"\n'
+            'is on, each product automatically has a bug group associated with it.\n'
+            'If the parameter "usebuggroupsentry" is also on, a user must be in the\n'
+            'product\'s bug group in order to create new bugs for the\n'
+            'product.</p>'
+        ),
     ),
     (
         '2.10',
         None,
-        '<p>%(VERSION_STRING)sUsers may be added to a group\n'
-        'by any user who has the "bless" property for that group.  The "bless"\n'
-        'property itself may only be conferred by an administrator.</p>',
+        (
+            '<p>%(VERSION_STRING)sUsers may be added to a group\n'
+            'by any user who has the "bless" property for that group.  The "bless"\n'
+            'property itself may only be conferred by an administrator.</p>'
+        ),
     ),
     (
         '2.4',
         None,
-        '<p>Group membership for new users and new groups is\n'
-        'determined by matching %(column-groups-userregexp)s against the user\'s\n'
-        'email address.',
+        (
+            '<p>Group membership for new users and new groups is\n'
+            'determined by matching %(column-groups-userregexp)s against the user\'s\n'
+            'email address.'
+        ),
     ),
     (
         '2.10',
         None,
-        '%(VERSION_STRING)sThe default configuration has\n'
-        'universal regexps for the "editbugs" and "canconfirm" groups.',
+        (
+            '%(VERSION_STRING)sThe default configuration has\n'
+            'universal regexps for the "editbugs" and "canconfirm" groups.'
+        ),
     ),
     ('2.4', None, '</p>\n\n<p>'),
     (
         '2.4',
         '2.16.7',
-        '%(VERSION_STRING)sEach group corresponds to a bit\n'
-        'in a 64-bit bitset, %(column-groups-bit)s.  User\n'
-        'membership in a group is conferred by the bit being set in '
-        '%(column-profiles-groupset)s.  Bug\n'
-        'membership in a bug group is conferred by the bit being set in '
-        '%(column-bugs-groupset)s.',
+        (
+            '%(VERSION_STRING)sEach group corresponds to a bit\n'
+            'in a 64-bit bitset, %(column-groups-bit)s.  User\n'
+            'membership in a group is conferred by the bit being set in '
+            '%(column-profiles-groupset)s.  Bug\n'
+            'membership in a bug group is conferred by the bit being set in '
+            '%(column-bugs-groupset)s.'
+        ),
     ),
     (
         '2.10',
         '2.16.7',
-        '%(VERSION_STRING)sThe bless\n'
-        'privilege for a group is conferred by the bit being set in '
-        '%(column-profiles-blessgroupset)s.',
+        (
+            '%(VERSION_STRING)sThe bless\n'
+            'privilege for a group is conferred by the bit being set in '
+            '%(column-profiles-blessgroupset)s.'
+        ),
     ),
     (
         '2.17.1',
         None,
-        '%(VERSION_STRING)sUser membership in a group is\n'
-        'conferred by a row in %(the-table-user_group_map)s, with\n'
-        '%(column-user_group_map-isbless)s set to 0.  The bless privilege for a\n'
-        'group is conferred by a row with %(column-user_group_map-isbless)s set\n'
-        'to 1.  Bug membership in a bug group is conferred by a row in\n'
-        '%(the-table-bug_group_map)s.',
+        (
+            '%(VERSION_STRING)sUser membership in a group is\n'
+            'conferred by a row in %(the-table-user_group_map)s, with\n'
+            '%(column-user_group_map-isbless)s set to 0.  The bless privilege for a\n'
+            'group is conferred by a row with %(column-user_group_map-isbless)s set\n'
+            'to 1.  Bug membership in a bug group is conferred by a row in\n'
+            '%(the-table-bug_group_map)s.'
+        ),
     ),
     ('2.4', None, '</p>'),
     (
         '2.17.1',
         None,
-        '<p>%(VERSION_STRING)sGroups may be configured so\n'
-        'that membership in one group automatically confers membership or the\n'
-        '"bless" privilege for another group.  This is controlled by\n'
-        '%(the-table-group_group_map)s.</p>',
+        (
+            '<p>%(VERSION_STRING)sGroups may be configured so\n'
+            'that membership in one group automatically confers membership or the\n'
+            '"bless" privilege for another group.  This is controlled by\n'
+            '%(the-table-group_group_map)s.</p>'
+        ),
     ),
     (
         '2.19.1',
         None,
-        '<p>%(VERSION_STRING)sGroups may be configured so\n'
-        'that the existence of a group is not visible to members of another\n'
-        'group. This is controlled by %(the-table-group_group_map)s.</p>',
+        (
+            '<p>%(VERSION_STRING)sGroups may be configured so\n'
+            'that the existence of a group is not visible to members of another\n'
+            'group. This is controlled by %(the-table-group_group_map)s.</p>'
+        ),
     ),
     (
         '2.17.3',
         None,
-        '<p>%(VERSION_STRING)sA product may be configured\n'
-        'so that membership in one or more groups is required to perform\n'
-        'certain actions on bugs in the product.  Whether or not a new bug for\n'
-        'the product is placed in a group is also configurable (note that user\n'
-        'membership in a group is required to place an existing bug in that\n'
-        'group).  All this is controlled by %(the-table-group_control_map)s.</p>\n'
-        '\n'
-        '<p>The %(column-group_control_map-membercontrol)s and\n'
-        '%(column-group_control_map-othercontrol)s\n'
-        'columns of that table determine the treatment of a given group for a\n'
-        'new bug in a given product, depending on whether the bug is being\n'
-        'created by a member or non-member of that group respectively.  The\n'
-        'possible values of these columns are as follows:</p>\n'
-        '\n'
-        '<table border="1" cellspacing="0" cellpadding="5">\n'
-        '<tr>\n'
-        '  <th>value</th>\n'
-        '  <th>name</th>\n'
-        '  <th>meaning</th>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>0</td>\n'
-        '  <td>NA</td>\n'
-        '  <td>A bug for this product cannot be placed in this group.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>1</td>\n'
-        '  <td>Shown</td>\n'
-        '  <td>A bug for this product may be placed in this group, but will not be '
-        'by default.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>2</td>\n'
-        '  <td>Default</td>\n'
-        '  <td>A bug for this product may be placed in this group, and is by '
-        'default.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>3</td>\n'
-        '  <td>Mandatory</td>\n'
-        '  <td>A bug for this product is always placed in this group.</td>\n'
-        '</tr>\n'
-        '</table>\n'
-        '\n'
-        '<p>Only certain combinations of membercontrol/othercontrol are\n'
-        'permitted, as follows:</p>\n'
-        '\n'
-        '<table border="1" cellspacing="0" cellpadding="5">\n'
-        ' <tr>\n'
-        '  <th>membercontrol</th>\n'
-        '  <th>othercontrol</th>\n'
-        '  <th>Notes</th>\n'
-        '</tr>\n'
-        '<tr>\n'
-        '  <td>0(NA)</td>\n'
-        '  <td>0(NA)</td>\n'
-        '  <td>A bug for this product can never be placed in this group (so the\n'
-        '  option isn\'t presented).</td>\n'
-        '\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td rowspan="4">1 (Shown)</td>\n'
-        '  <td>0(NA)</td>\n'
-        '  <td>Only members can place a bug in this group.<b>This is the default '
-        'setting.</b></td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>1 (Shown)</td>\n'
-        '  <td>Anyone can place a new bug in this group.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>2 (Default)</td>\n'
-        '  <td>Anyone can place a bug in this group, and\n'
-        '  non-members will do so by default.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>3 (Mandatory)</td>\n'
-        '  <td>Anyone can place a bug in this group, and non-members will always do '
-        'so.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td rowspan="3">2 (Default)</td>\n'
-        '  <td>0(NA)</td>\n'
-        '  <td>Only members can place a bug in this group, and do so by '
-        'default.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>2 (Default)</td>\n'
-        '  <td>Anyone can place a bug in this group, and does so by default.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>3 (Mandatory)</td>\n'
-        '  <td>Members can place a bug in this group, and do so by default.\n'
-        '  Non-members always place a bug in this group.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr>\n'
-        '  <td>3(Mandatory)</td>\n'
-        '  <td>3(Mandatory)</td>\n'
-        '  <td>A bug for this product can never be removed from this group (so\n'
-        '  the option isn\'t presented).</td>\n'
-        '</tr>\n'
-        '</table>\n'
-        '\n',
+        (
+            '<p>%(VERSION_STRING)sA product may be configured\nso that membership in'
+            ' one or more groups is required to perform\ncertain actions on bugs in the'
+            ' product.  Whether or not a new bug for\nthe product is placed in a group'
+            ' is also configurable (note that user\nmembership in a group is required'
+            ' to place an existing bug in that\ngroup).  All this is controlled by'
+            ' %(the-table-group_control_map)s.</p>\n\n<p>The'
+            ' %(column-group_control_map-membercontrol)s'
+            ' and\n%(column-group_control_map-othercontrol)s\ncolumns of that table'
+            ' determine the treatment of a given group for a\nnew bug in a given'
+            ' product, depending on whether the bug is being\ncreated by a member or'
+            ' non-member of that group respectively.  The\npossible values of these'
+            ' columns are as follows:</p>\n\n<table border="1" cellspacing="0"'
+            ' cellpadding="5">\n<tr>\n  <th>value</th>\n  <th>name</th>\n '
+            ' <th>meaning</th>\n</tr>\n\n<tr>\n  <td>0</td>\n  <td>NA</td>\n  <td>A bug'
+            ' for this product cannot be placed in this group.</td>\n</tr>\n\n<tr>\n '
+            ' <td>1</td>\n  <td>Shown</td>\n  <td>A bug for this product may be placed'
+            ' in this group, but will not be by default.</td>\n</tr>\n\n<tr>\n '
+            ' <td>2</td>\n  <td>Default</td>\n  <td>A bug for this product may be'
+            ' placed in this group, and is by default.</td>\n</tr>\n\n<tr>\n '
+            ' <td>3</td>\n  <td>Mandatory</td>\n  <td>A bug for this product is always'
+            ' placed in this group.</td>\n</tr>\n</table>\n\n<p>Only certain'
+            ' combinations of membercontrol/othercontrol are\npermitted, as'
+            ' follows:</p>\n\n<table border="1" cellspacing="0" cellpadding="5">\n'
+            ' <tr>\n  <th>membercontrol</th>\n  <th>othercontrol</th>\n '
+            ' <th>Notes</th>\n</tr>\n<tr>\n  <td>0(NA)</td>\n  <td>0(NA)</td>\n  <td>A'
+            ' bug for this product can never be placed in this group (so the\n  option'
+            ' isn\'t presented).</td>\n\n</tr>\n\n<tr>\n  <td rowspan="4">1'
+            ' (Shown)</td>\n  <td>0(NA)</td>\n  <td>Only members can place a bug in'
+            ' this group.<b>This is the default setting.</b></td>\n</tr>\n\n<tr>\n '
+            ' <td>1 (Shown)</td>\n  <td>Anyone can place a new bug in this'
+            ' group.</td>\n</tr>\n\n<tr>\n  <td>2 (Default)</td>\n  <td>Anyone can'
+            ' place a bug in this group, and\n  non-members will do so by'
+            ' default.</td>\n</tr>\n\n<tr>\n  <td>3 (Mandatory)</td>\n  <td>Anyone can'
+            ' place a bug in this group, and non-members will always do'
+            ' so.</td>\n</tr>\n\n<tr>\n  <td rowspan="3">2 (Default)</td>\n '
+            ' <td>0(NA)</td>\n  <td>Only members can place a bug in this group, and do'
+            ' so by default.</td>\n</tr>\n\n<tr>\n  <td>2 (Default)</td>\n  <td>Anyone'
+            ' can place a bug in this group, and does so by'
+            ' default.</td>\n</tr>\n\n<tr>\n  <td>3 (Mandatory)</td>\n  <td>Members can'
+            ' place a bug in this group, and do so by default.\n  Non-members always'
+            ' place a bug in this group.</td>\n</tr>\n\n<tr>\n  <td>3(Mandatory)</td>\n'
+            '  <td>3(Mandatory)</td>\n  <td>A bug for this product can never be removed'
+            ' from this group (so\n  the option isn\'t'
+            ' presented).</td>\n</tr>\n</table>\n\n'
+        ),
     ),
     (
         '2.6',
         None,
-        '<h3><a id="notes-attachments" '
-        'name="notes-attachments">Attachments</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sUsers can upload attachments to bugs.  An\n'
-        'attachments can be marked as a patch.  Attachments are stored in\n'
-        '%(the-table-attachments)s.',
+        (
+            '<h3><a id="notes-attachments" '
+            'name="notes-attachments">Attachments</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sUsers can upload attachments to bugs.  An\n'
+            'attachments can be marked as a patch.  Attachments are stored in\n'
+            '%(the-table-attachments)s.'
+        ),
     ),
     (
         '2.16rc1',
@@ -4588,8 +4648,10 @@
     (
         '2.6',
         '2.20.7',
-        '%(VERSION_STRING)sAttachment data is stored in\n'
-        '%(column-attachments-thedata)s.',
+        (
+            '%(VERSION_STRING)sAttachment data is stored in\n'
+            '%(column-attachments-thedata)s.'
+        ),
     ),
     (
         '2.21.1',
@@ -4599,222 +4661,250 @@
     (
         '2.22rc1',
         None,
-        '%(VERSION_STRING)sAttachments can be URLs, marked\n'
-        'by the flag %(column-attachments-isurl)s.  The URL itself is stored in\n'
-        '%(column-attach_data-thedata)s.',
+        (
+            '%(VERSION_STRING)sAttachments can be URLs, marked\n'
+            'by the flag %(column-attachments-isurl)s.  The URL itself is stored in\n'
+            '%(column-attach_data-thedata)s.'
+        ),
     ),
     '</p>',
     (
         '2.16rc1',
         '2.16.7',
-        '<p>%(VERSION_STRING)sEach attachment may have\n'
-        'one of a number of "status" keywords associated with it.  The status\n'
-        'keywords are user-defined on a per-product basis.  The set of status\n'
-        'keywords is defined in %(the-table-attachstatusdefs)s.  Whether a\n'
-        'given attachment has a given status keyword is defined by\n'
-        '%(the-table-attachstatuses)s.</p>',
+        (
+            '<p>%(VERSION_STRING)sEach attachment may have\n'
+            'one of a number of "status" keywords associated with it.  The status\n'
+            'keywords are user-defined on a per-product basis.  The set of status\n'
+            'keywords is defined in %(the-table-attachstatusdefs)s.  Whether a\n'
+            'given attachment has a given status keyword is defined by\n'
+            '%(the-table-attachstatuses)s.</p>'
+        ),
     ),
     (
         '2.17.1',
         None,
-        '<p>%(VERSION_STRING)sAttachment statuses are\n'
-        'implemented with the <a href="#notes-flags">flags</a> system.</p>',
+        (
+            '<p>%(VERSION_STRING)sAttachment statuses are\n'
+            'implemented with the <a href="#notes-flags">flags</a> system.</p>'
+        ),
     ),
     (
         '2.17.1',
         None,
-        '\n'
-        '\n'
-        '<h3><a id="notes-flags" name="notes-flags">Flags</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugs and attachments may be marked with "flags".\n'
-        'The set of flag types is user-defined (using editflagtypes.cgi).  For\n'
-        'instance, a flag type might be "candidate for version 7.3 triage", or\n'
-        '"7.3" for short.  Flag types are recorded in %(the-table-flagtypes)s.\n'
-        'Each flag type is either for bugs or for attachments, not both.</p>\n'
-        '\n'
-        '<p>Actual flags are recorded in %(the-table-flags)s.  Each flag has a\n'
-        'status of "+" ("granted"), "-" ("denied") or "?" ("requested").  For\n'
-        'instance, one bug might have flag "7.3+", and another might have flag\n'
-        '"7.3-".</p>\n'
-        '\n'
-        '<p>A status of "?" indicates that a user has requested that this item\n'
-        'be given this flag.  There is an special interface for viewing request\n'
-        'flags (request.cgi).  A request flag may be marked for the attention\n'
-        'of a particular user, the "requestee".</p>\n'
-        '\n'
-        '<p>A flag type may have a "CC list" of email addresses, of people to\n'
-        'notify when a flag is requested.</p>\n'
-        '\n'
-        '<p>By default, a single bug or attachment may receive several flags of\n'
-        'the same type, with the same or different statuses and the same or\n'
-        'different requestees.  This may be disabled for any given flag\n'
-        'type.</p>\n'
-        '\n'
-        '<p>Particular flag types may only be available to bugs in certain\n'
-        'products and components (or their attachments).  This is recorded in\n'
-        '%(the-table-flaginclusions)s.  Particular flag types may <em>not</em>\n'
-        'be available to bugs in certain products and components (or their\n'
-        'attachments).  This is recorded in %(the-table-flagexclusions)s.</p>\n'
-        '\n'
-        '<p>Various features of flag types may be disabled: they can be made\n'
-        'inactive, not requestable, not "requesteeable", not "multiplicable".</p>\n'
-        '\n',
+        (
+            '\n'
+            '\n'
+            '<h3><a id="notes-flags" name="notes-flags">Flags</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugs and attachments may be marked with "flags".\n'
+            'The set of flag types is user-defined (using editflagtypes.cgi).  For\n'
+            'instance, a flag type might be "candidate for version 7.3 triage", or\n'
+            '"7.3" for short.  Flag types are recorded in %(the-table-flagtypes)s.\n'
+            'Each flag type is either for bugs or for attachments, not both.</p>\n'
+            '\n'
+            '<p>Actual flags are recorded in %(the-table-flags)s.  Each flag has a\n'
+            'status of "+" ("granted"), "-" ("denied") or "?" ("requested").  For\n'
+            'instance, one bug might have flag "7.3+", and another might have flag\n'
+            '"7.3-".</p>\n'
+            '\n'
+            '<p>A status of "?" indicates that a user has requested that this item\n'
+            'be given this flag.  There is an special interface for viewing request\n'
+            'flags (request.cgi).  A request flag may be marked for the attention\n'
+            'of a particular user, the "requestee".</p>\n'
+            '\n'
+            '<p>A flag type may have a "CC list" of email addresses, of people to\n'
+            'notify when a flag is requested.</p>\n'
+            '\n'
+            '<p>By default, a single bug or attachment may receive several flags of\n'
+            'the same type, with the same or different statuses and the same or\n'
+            'different requestees.  This may be disabled for any given flag\n'
+            'type.</p>\n'
+            '\n'
+            '<p>Particular flag types may only be available to bugs in certain\n'
+            'products and components (or their attachments).  This is recorded in\n'
+            '%(the-table-flaginclusions)s.  Particular flag types may <em>not</em>\n'
+            'be available to bugs in certain products and components (or their\n'
+            'attachments).  This is recorded in %(the-table-flagexclusions)s.</p>\n'
+            '\n'
+            '<p>Various features of flag types may be disabled: they can be made\n'
+            'inactive, not requestable, not "requesteeable", not "multiplicable".</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.10',
         None,
-        '\n'
-        '\n'
-        '<h3><a id="notes-keywords" name="notes-keywords">Keywords</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugzilla users can define a number of keywords,\n'
-        'and then give each bug a set of keywords.  This is mainly for use in\n'
-        'finding related bugs.  The keywords are stored in\n'
-        '%(the-table-keyworddefs)s, and the one-to-many mapping from bugs to\n'
-        'keywords is stored in %(the-table-keywords)s, and also in\n'
-        '%(column-bugs-keywords)s.</p>\n'
-        '\n',
+        (
+            '\n'
+            '\n'
+            '<h3><a id="notes-keywords" name="notes-keywords">Keywords</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugzilla users can define a number of keywords,\n'
+            'and then give each bug a set of keywords.  This is mainly for use in\n'
+            'finding related bugs.  The keywords are stored in\n'
+            '%(the-table-keyworddefs)s, and the one-to-many mapping from bugs to\n'
+            'keywords is stored in %(the-table-keywords)s, and also in\n'
+            '%(column-bugs-keywords)s.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.6',
         None,
-        '<h3><a id="notes-dependencies" '
-        'name="notes-dependencies">Dependencies</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugs may depend on other bugs being fixed.  That\n'
-        'is, it may be impossible to fix one bug until another one is fixed.\n'
-        'Bugzilla records and displays such information and uses it to notify\n'
-        'users when a bug changes (all contacts for all dependent bugs are\n'
-        'notified when a bug changes).</p>\n'
+        (
+            '<h3><a id="notes-dependencies" '
+            'name="notes-dependencies">Dependencies</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugs may depend on other bugs being fixed.  That\n'
+            'is, it may be impossible to fix one bug until another one is fixed.\n'
+            'Bugzilla records and displays such information and uses it to notify\n'
+            'users when a bug changes (all contacts for all dependent bugs are\n'
+            'notified when a bug changes).</p>\n'
+            '\n'
+            '<p>Dependencies are recorded in %(the-table-dependencies)s.</p>'
+        ),
+    ),
+    (
+        '<h3><a id="notes-activity" name="notes-activity">Activity</a></h3>\n'
         '\n'
-        '<p>Dependencies are recorded in %(the-table-dependencies)s.</p>',
+        '<p>Bugzilla keeps a record of changes made to bugs.  This record is in\n'
+        '%(the-table-bugs_activity)s.  Each row in this table records a change\n'
+        'to a field in %(the-table-bugs)s.'
     ),
-    '<h3><a id="notes-activity" name="notes-activity">Activity</a></h3>\n'
-    '\n'
-    '<p>Bugzilla keeps a record of changes made to bugs.  This record is in\n'
-    '%(the-table-bugs_activity)s.  Each row in this table records a change\n'
-    'to a field in %(the-table-bugs)s.',
     (
         '2.10',
         None,
-        '%(VERSION_STRING)sThe fields are referred to by a\n'
-        'number which is looked up in %(the-table-fielddefs)s.  This table\n'
-        'records the name of the field and also a longer description used to\n'
-        'display activity tables.',
+        (
+            '%(VERSION_STRING)sThe fields are referred to by a\n'
+            'number which is looked up in %(the-table-fielddefs)s.  This table\n'
+            'records the name of the field and also a longer description used to\n'
+            'display activity tables.'
+        ),
+    ),
+    (
+        '</p>\n'
+        '\n'
+        '<h3><a id="notes-severity" name="notes-severity">Severity</a></h3>\n'
+        '\n'
+        '<p>Each bug has a "severity" field, %(column-bugs-bug_severity)s,\n'
+        'indicating the severity of the impact of the bug.  There is no code in\n'
+        'Bugzilla which distinguishes the values of this field, although it may\n'
+        'naturally be used in queries.'
     ),
-    '</p>\n'
-    '\n'
-    '<h3><a id="notes-severity" name="notes-severity">Severity</a></h3>\n'
-    '\n'
-    '<p>Each bug has a "severity" field, %(column-bugs-bug_severity)s,\n'
-    'indicating the severity of the impact of the bug.  There is no code in\n'
-    'Bugzilla which distinguishes the values of this field, although it may\n'
-    'naturally be used in queries.',
     (
         '2.19.3',
         None,
-        '%(VERSION_STRING)sThe set of values available for\n'
-        'this field is stored in %(table-bug_severity)s and can be controlled\n'
-        'by the administrator. ',
+        (
+            '%(VERSION_STRING)sThe set of values available for\n'
+            'this field is stored in %(table-bug_severity)s and can be controlled\n'
+            'by the administrator. '
+        ),
+    ),
+    (
+        'The intended meanings of the built-in values of this field are as\n'
+        'follows:</p>\n'
+        '\n'
+        '<table border="1" cellspacing="0" cellpadding="5">\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <th>Value</th>\n'
+        '\n'
+        '    <th>Intended meaning</th>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <td>Blocker</td>\n'
+        '\n'
+        '    <td>Blocks development and/or testing work</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <td>Critical</td>\n'
+        '\n'
+        '    <td>Crashes, loss of data, severe memory leak</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <td>Major</td>\n'
+        '\n'
+        '    <td>Major loss of function</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <td>Minor</td>\n'
+        '\n'
+        '    <td>Minor loss of function, or other problem where easy workaround is '
+        'present</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <td>Trivial</td>\n'
+        '\n'
+        '    <td>Cosmetic problem</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr align="left" valign="top">\n'
+        '\n'
+        '    <td>Enhancement</td>\n'
+        '\n'
+        '    <td>Request for enhancement</td>\n'
+        '\n'
+        '  </tr>\n'
+        '</table>\n'
+        '\n'
+        '<h3><a id="notes-email" name="notes-email">Email notification</a></h3>\n'
+        '\n'
+        '<p>When a bug changes, email notification is sent out to a number of\n'
+        'users:</p>\n'
+        '\n'
+        '<ul>\n'
+        '  <li>The bug\'s owner (%(column-bugs-assigned_to)s)</li>\n'
+        '  <li>The bug\'s reporter (%(column-bugs-reporter)s)</li>\n'
+        '  <li>The bug\'s QA contact, if the "useqacontact" parameter is set '
+        '(%(column-bugs-qa_contact)s)</li>\n'
+        '  <li>All the users who have explicitly asked to be notified when the bug '
+        'changes (these users are stored in %(the-table-cc)s).</li>\n'
+        '  <li>All the users who have voted for this bug (recorded in '
+        '%(the-table-votes)s).</li>\n'
+        '</ul>\n'
+        '\n'
+        '<p>'
     ),
-    'The intended meanings of the built-in values of this field are as\n'
-    'follows:</p>\n'
-    '\n'
-    '<table border="1" cellspacing="0" cellpadding="5">\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <th>Value</th>\n'
-    '\n'
-    '    <th>Intended meaning</th>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <td>Blocker</td>\n'
-    '\n'
-    '    <td>Blocks development and/or testing work</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <td>Critical</td>\n'
-    '\n'
-    '    <td>Crashes, loss of data, severe memory leak</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <td>Major</td>\n'
-    '\n'
-    '    <td>Major loss of function</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <td>Minor</td>\n'
-    '\n'
-    '    <td>Minor loss of function, or other problem where easy workaround is '
-    'present</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <td>Trivial</td>\n'
-    '\n'
-    '    <td>Cosmetic problem</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr align="left" valign="top">\n'
-    '\n'
-    '    <td>Enhancement</td>\n'
-    '\n'
-    '    <td>Request for enhancement</td>\n'
-    '\n'
-    '  </tr>\n'
-    '</table>\n'
-    '\n'
-    '<h3><a id="notes-email" name="notes-email">Email notification</a></h3>\n'
-    '\n'
-    '<p>When a bug changes, email notification is sent out to a number of\n'
-    'users:</p>\n'
-    '\n'
-    '<ul>\n'
-    '  <li>The bug\'s owner (%(column-bugs-assigned_to)s)</li>\n'
-    '  <li>The bug\'s reporter (%(column-bugs-reporter)s)</li>\n'
-    '  <li>The bug\'s QA contact, if the "useqacontact" parameter is set '
-    '(%(column-bugs-qa_contact)s)</li>\n'
-    '  <li>All the users who have explicitly asked to be notified when the bug '
-    'changes (these users are stored in %(the-table-cc)s).</li>\n'
-    '  <li>All the users who have voted for this bug (recorded in '
-    '%(the-table-votes)s).</li>\n'
-    '</ul>\n'
-    '\n'
-    '<p>',
     (
         '2.12',
         None,
-        '%(VERSION_STRING)sIndividual users may filter\n'
-        'these messages according to the way in which the bug changes and their\n'
-        'relationship to the bug.\n',
+        (
+            '%(VERSION_STRING)sIndividual users may filter\n'
+            'these messages according to the way in which the bug changes and their\n'
+            'relationship to the bug.\n'
+        ),
     ),
     (
         '2.12',
         '2.19.2',
-        '%(VERSION_STRING)sThese filtering preferences are\n'
-        'recorded in %(column-profiles-emailflags)s.\n',
+        (
+            '%(VERSION_STRING)sThese filtering preferences are\n'
+            'recorded in %(column-profiles-emailflags)s.\n'
+        ),
     ),
     (
         '2.19.3',
         None,
-        '%(VERSION_STRING)sThese filtering preferences are\n'
-        'recorded in the %(table-email_setting)s table.\n',
+        (
+            '%(VERSION_STRING)sThese filtering preferences are\n'
+            'recorded in the %(table-email_setting)s table.\n'
+        ),
     ),
     ('2.12', None, '</p>\n\n<p>'),
     (
@@ -4825,27 +4915,33 @@
     (
         '2.17.4',
         None,
-        '%(VERSION_STRING)sThis is handled by the\n'
-        'Bugzilla::Bugmail module, which is invoked by the template system\n'
-        '(from Bugzilla::Template) when it encounters a call to SendBugMail()\n'
-        'in a template.  ',
+        (
+            '%(VERSION_STRING)sThis is handled by the\n'
+            'Bugzilla::Bugmail module, which is invoked by the template system\n'
+            '(from Bugzilla::Template) when it encounters a call to SendBugMail()\n'
+            'in a template.  '
+        ),
     ),
     (
         '3.3.1',
         None,
-        '</p>%(VERSION_STRING)sIf the parameter\n'
-        '"use_mailer_queue" is set, all email is queued to be sent\n'
-        'asynchronously.  This is managed by a third-party general-purpose Perl\n'
-        'job queueing system called TheSchwartz, using several database tables\n'
-        'of its own (%(table-ts_error)s, %(table-ts_exitstatus)s,\n'
-        '%(table-ts_funcmap)s, %(table-ts_job)s, and %(table-ts_note)s).',
+        (
+            '</p>%(VERSION_STRING)sIf the parameter\n'
+            '"use_mailer_queue" is set, all email is queued to be sent\n'
+            'asynchronously.  This is managed by a third-party general-purpose Perl\n'
+            'job queueing system called TheSchwartz, using several database tables\n'
+            'of its own (%(table-ts_error)s, %(table-ts_exitstatus)s,\n'
+            '%(table-ts_funcmap)s, %(table-ts_job)s, and %(table-ts_note)s).'
+        ),
+    ),
+    (
+        '</p>\n'
+        '\n'
+        '<h3><a id="notes-descriptions" name="notes-descriptions">Long '
+        'descriptions</a></h3>\n'
+        '\n'
+        '<p>Each bug has a number of comments associated with it. '
     ),
-    '</p>\n'
-    '\n'
-    '<h3><a id="notes-descriptions" name="notes-descriptions">Long '
-    'descriptions</a></h3>\n'
-    '\n'
-    '<p>Each bug has a number of comments associated with it. ',
     (
         None,
         '2.8',
@@ -4856,75 +4952,87 @@
         None,
         '%(VERSION_STRING)sThese are stored individually in\n%(the-table-longdescs)s.',
     ),
-    '</p>\n'
-    '\n'
-    '<p>They are displayed as the "Description" on the bug form, ordered by\n'
-    'date and annotated with the user and date.  Users can add new comments\n'
-    'with the "Additional comment" field on the bug form.</p>',
+    (
+        '</p>\n'
+        '\n'
+        '<p>They are displayed as the "Description" on the bug form, ordered by\n'
+        'date and annotated with the user and date.  Users can add new comments\n'
+        'with the "Additional comment" field on the bug form.</p>'
+    ),
     (
         '2.10',
         None,
-        '<h3><a id="notes-namedqueries" name="notes-namedqueries">Named '
-        'queries</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sUsers can name queries.  Links to named query\n'
-        'pages appear in a navigation footer bar on most Bugzilla pages.  A\n'
-        'query named "(Default query)" is a user\'s default query.  Named\n'
-        'queries are stored in %(the-table-namedqueries)s.</p>\n'
-        '\n',
+        (
+            '<h3><a id="notes-namedqueries" name="notes-namedqueries">Named '
+            'queries</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sUsers can name queries.  Links to named query\n'
+            'pages appear in a navigation footer bar on most Bugzilla pages.  A\n'
+            'query named "(Default query)" is a user\'s default query.  Named\n'
+            'queries are stored in %(the-table-namedqueries)s.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.23.3',
         None,
-        '<p>%(VERSION_STRING)sIf the parameter\n'
-        '"querysharegroup" is set, it names a group of users who are empowered\n'
-        'to share named queries.  An empowered user can share a given named\n'
-        'query they create with all the members of a group, as long as he or\n'
-        'she has the "bless" property for that group.  A query can only be\n'
-        'shared with a single group.  Sharing is recorded in\n'
-        '%(the-table-namedquery_group_map)s.</p>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sAny user able to use a given named query can\n'
-        'control whether or not that query appears in his or her navigation\n'
-        'footer bar.  This is recorded in\n'
-        '%(the-table-namedqueries_link_in_footer)s.</p>\n'
-        '\n',
+        (
+            '<p>%(VERSION_STRING)sIf the parameter\n'
+            '"querysharegroup" is set, it names a group of users who are empowered\n'
+            'to share named queries.  An empowered user can share a given named\n'
+            'query they create with all the members of a group, as long as he or\n'
+            'she has the "bless" property for that group.  A query can only be\n'
+            'shared with a single group.  Sharing is recorded in\n'
+            '%(the-table-namedquery_group_map)s.</p>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sAny user able to use a given named query can\n'
+            'control whether or not that query appears in his or her navigation\n'
+            'footer bar.  This is recorded in\n'
+            '%(the-table-namedqueries_link_in_footer)s.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.17.5',
         None,
-        '<h3><a id="notes-charts" name="notes-charts">Charts</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugzilla can draw general time-series charts.\n'
-        'There are a number of default time series.  Each product has a default\n'
-        'series for each bug status or resolution (for instance "how many bugs\n'
-        'are INVALID in product Foo") and each component has a default series\n'
-        'for all open bugs (UNCONFIRMED/NEW/ASSIGNED/REOPENED) and one for all\n'
-        'closed bugs (RESOLVED/VERIFIED/CLOSED).  A user can also define a new\n'
-        'time series based on any query, and give it a "frequency" (actually a\n'
-        'period, measured in days).  The set of series is stored in\n'
-        '%(the-table-series)s.</p>\n'
-        '\n'
-        '<p>To collect the data for the time series, the Bugzilla administrator\n'
-        'needs to arrange for the collectstats.pl script to be run every day.\n'
-        'This script stores the data in %(the-table-series_data)s.</p>\n'
-        '\n'
-        '<p>Series have categories and subcategories, which are provided in\n'
-        'order to make it easier to manage large numbers of series.  They are\n'
-        'normalized in %(the-table-series_categories)s.</p>\n',
+        (
+            '<h3><a id="notes-charts" name="notes-charts">Charts</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugzilla can draw general time-series charts.\n'
+            'There are a number of default time series.  Each product has a default\n'
+            'series for each bug status or resolution (for instance "how many bugs\n'
+            'are INVALID in product Foo") and each component has a default series\n'
+            'for all open bugs (UNCONFIRMED/NEW/ASSIGNED/REOPENED) and one for all\n'
+            'closed bugs (RESOLVED/VERIFIED/CLOSED).  A user can also define a new\n'
+            'time series based on any query, and give it a "frequency" (actually a\n'
+            'period, measured in days).  The set of series is stored in\n'
+            '%(the-table-series)s.</p>\n'
+            '\n'
+            '<p>To collect the data for the time series, the Bugzilla administrator\n'
+            'needs to arrange for the collectstats.pl script to be run every day.\n'
+            'This script stores the data in %(the-table-series_data)s.</p>\n'
+            '\n'
+            '<p>Series have categories and subcategories, which are provided in\n'
+            'order to make it easier to manage large numbers of series.  They are\n'
+            'normalized in %(the-table-series_categories)s.</p>\n'
+        ),
     ),
     (
         '2.17.5',
         None,
-        '<p>By default, a time series is "private": only\n'
-        'visible to the user who created it. An administrator may make a time\n'
-        'series "public", or visible to other users.',
+        (
+            '<p>By default, a time series is "private": only\n'
+            'visible to the user who created it. An administrator may make a time\n'
+            'series "public", or visible to other users.'
+        ),
     ),
     (
         '2.17.5',
         '2.18rc2',
-        '%(VERSION_STRING)s this is determined by the\n'
-        '"subscription" system (see below).',
+        (
+            '%(VERSION_STRING)s this is determined by the\n'
+            '"subscription" system (see below).'
+        ),
     ),
     (
         '2.18rc3',
@@ -4935,184 +5043,208 @@
     (
         '2.17.5',
         '2.18rc2',
-        '<p>%(VERSION_STRING)sIf a series is "private"\n'
-        '(not "public") then users may "subscribe" to it.  Each user is\n'
-        'automatically subscribed to any series created by that user.  The\n'
-        'subscription is recorded in %(the-table-user_series_map)s.  If all\n'
-        'users unsubscribe from a time series, data will stop being collected\n'
-        'on it (by setting the period to 0 days).  A series is "public" if\n'
-        '%(column-user_series_map-user_id)s is zero.</p> ',
+        (
+            '<p>%(VERSION_STRING)sIf a series is "private"\n'
+            '(not "public") then users may "subscribe" to it.  Each user is\n'
+            'automatically subscribed to any series created by that user.  The\n'
+            'subscription is recorded in %(the-table-user_series_map)s.  If all\n'
+            'users unsubscribe from a time series, data will stop being collected\n'
+            'on it (by setting the period to 0 days).  A series is "public" if\n'
+            '%(column-user_series_map-user_id)s is zero.</p> '
+        ),
     ),
     (
         '2.18rc3',
         None,
-        '<p>%(VERSION_STRING)sVisibility of a time series\n'
-        'to a user is determined on a per-category basis using the groups\n'
-        'system.  The group memberships required to see a time series in a\n'
-        'given category are recorded in %(the-table-category_group_map)s.  A\n'
-        'user may see a time series if they are in all the groups for the\n'
-        'category <em>and</em> either ths user created the series or it is\n'
-        'public.</p>\n'
-        '\n',
+        (
+            '<p>%(VERSION_STRING)sVisibility of a time series\n'
+            'to a user is determined on a per-category basis using the groups\n'
+            'system.  The group memberships required to see a time series in a\n'
+            'given category are recorded in %(the-table-category_group_map)s.  A\n'
+            'user may see a time series if they are in all the groups for the\n'
+            'category <em>and</em> either ths user created the series or it is\n'
+            'public.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.10',
         None,
-        '<h3><a id="notes-watchers"\n'
-        'name="notes-watchers">Watchers</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugzilla lets users "watch" each other; receiving\n'
-        'each other\'s Bugzilla email.  For instance, if Sam goes on holiday,\n'
-        'Phil can "watch" her, receiving all her Bugzilla email.  This is set\n'
-        'up by the user preferences (userprefs.cgi), recorded in\n'
-        '%(the-table-watch)s and handled by the <a href="#notes-email">email\n'
-        'subsystem</a>.</p>\n'
-        '\n',
+        (
+            '<h3><a id="notes-watchers"\n'
+            'name="notes-watchers">Watchers</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugzilla lets users "watch" each other; receiving\n'
+            'each other\'s Bugzilla email.  For instance, if Sam goes on holiday,\n'
+            'Phil can "watch" her, receiving all her Bugzilla email.  This is set\n'
+            'up by the user preferences (userprefs.cgi), recorded in\n'
+            '%(the-table-watch)s and handled by the <a href="#notes-email">email\n'
+            'subsystem</a>.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.10',
         '2.17.1',
-        '\n'
-        '<h3><a id="notes-shadow" name="notes-shadow">Shadow database</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)s: Bugzilla can maintain a shadow, read-only copy\n'
-        'of everything in another database (with the parameter "shadowdb").  If\n'
-        'the parameter "queryagainstshadowdb" is on, queries were run against\n'
-        'the shadow.  A record of SQL activity since the last reflection is\n'
-        'kept in %(the-table-shadowlog)s.</p>',
+        (
+            '\n'
+            '<h3><a id="notes-shadow" name="notes-shadow">Shadow database</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)s: Bugzilla can maintain a shadow, read-only copy\n'
+            'of everything in another database (with the parameter "shadowdb").  If\n'
+            'the parameter "queryagainstshadowdb" is on, queries were run against\n'
+            'the shadow.  A record of SQL activity since the last reflection is\n'
+            'kept in %(the-table-shadowlog)s.</p>'
+        ),
     ),
     (
         '2.17.1',
         None,
-        '\n'
-        '<h3><a id="notes-time-tracking" name="notes-time-tracking">Time '
-        'tracking</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)s Bugzilla can track time for each bug, if the\n'
-        '"timetrackinggroup" parameter is set.  Members of that group get the\n'
-        'ability to estimate the amount of effort (measured in hours) a bug\n'
-        'will take to fix, either when creating or when editing the bug.\n'
-        'Members of that group are also permitted to record hours of effort\n'
-        'spent on the bug</p>\n'
-        '\n'
-        '<p>%(column-longdescs-work_time)s\n'
-        'records each increment of work.  The sum of this column for a bug is\n'
-        'computed to display as "Hours Worked" for the bug.</p>\n'
-        '\n'
-        '<p>%(column-bugs-estimated_time)s is\n'
-        'the estimate for how much time the bug will take in total, displayed\n'
-        'as "Orig. Est.".  This can be changed by members of the\n'
-        'timetrackinggroup.</p>\n'
-        '\n'
-        '<p>%(column-bugs-remaining_time)s is\n'
-        'the current estimate for how much more time the bug will take to fix,\n'
-        'displayed as "Hours Left".  This can be changed by members of the\n'
-        'timetrackinggroup.</p>\n'
-        '\n'
-        '<p>The total of "Hours Left" and "Hours Worked" is shown as "Current\n'
-        'Est.": the current estimate of the total effort required to fix the\n'
-        'bug. "Hours Worked" as a percentage of "Current Est" is shown as "%%\n'
-        'Complete". "Current Est" deducted from "Orig. Est" is shown as\n'
-        '"Gain"</p>',
+        (
+            '\n'
+            '<h3><a id="notes-time-tracking" name="notes-time-tracking">Time '
+            'tracking</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)s Bugzilla can track time for each bug, if the\n'
+            '"timetrackinggroup" parameter is set.  Members of that group get the\n'
+            'ability to estimate the amount of effort (measured in hours) a bug\n'
+            'will take to fix, either when creating or when editing the bug.\n'
+            'Members of that group are also permitted to record hours of effort\n'
+            'spent on the bug</p>\n'
+            '\n'
+            '<p>%(column-longdescs-work_time)s\n'
+            'records each increment of work.  The sum of this column for a bug is\n'
+            'computed to display as "Hours Worked" for the bug.</p>\n'
+            '\n'
+            '<p>%(column-bugs-estimated_time)s is\n'
+            'the estimate for how much time the bug will take in total, displayed\n'
+            'as "Orig. Est.".  This can be changed by members of the\n'
+            'timetrackinggroup.</p>\n'
+            '\n'
+            '<p>%(column-bugs-remaining_time)s is\n'
+            'the current estimate for how much more time the bug will take to fix,\n'
+            'displayed as "Hours Left".  This can be changed by members of the\n'
+            'timetrackinggroup.</p>\n'
+            '\n'
+            '<p>The total of "Hours Left" and "Hours Worked" is shown as "Current\n'
+            'Est.": the current estimate of the total effort required to fix the\n'
+            'bug. "Hours Worked" as a percentage of "Current Est" is shown as "%%\n'
+            'Complete". "Current Est" deducted from "Orig. Est" is shown as\n'
+            '"Gain"</p>'
+        ),
     ),
     (
         '2.19.3',
         None,
+        (
+            '\n'
+            '<p>%(VERSION_STRING)s%(column-bugs-deadline)s records a calendar deadline '
+            'for the bug.</p>'
+        ),
+    ),
+    (
+        '<h3><a id="notes-whine" name="notes-whine">The Whine System</a></h3>\n'
+        '\n'
+        '<p>Bugzilla has a system for sending "whine" email messages to\n'
+        'specified users on a regular basis.  This system relies on the\n'
+        'administrator configuring the Bugzilla server to run a script at\n'
+        'regular intervals (e.g. by using crontab).</p>\n'
         '\n'
-        '<p>%(VERSION_STRING)s%(column-bugs-deadline)s records a calendar deadline '
-        'for the bug.</p>',
+        '<p>The <code>whineatnews.pl</code> script should be run once a day.\n'
+        'For each bug which has status NEW or REOPENED, and which has not\n'
+        'changed for a certain number of days, it sends a message to the bug\'s\n'
+        'owner.  The number of days is controlled by a Bugzilla parameter\n'
+        'called "whinedays".  The content of the email message is controlled by\n'
+        'a Bugzilla parameter called "whinemail".</p>'
     ),
-    '<h3><a id="notes-whine" name="notes-whine">The Whine System</a></h3>\n'
-    '\n'
-    '<p>Bugzilla has a system for sending "whine" email messages to\n'
-    'specified users on a regular basis.  This system relies on the\n'
-    'administrator configuring the Bugzilla server to run a script at\n'
-    'regular intervals (e.g. by using crontab).</p>\n'
-    '\n'
-    '<p>The <code>whineatnews.pl</code> script should be run once a day.\n'
-    'For each bug which has status NEW or REOPENED, and which has not\n'
-    'changed for a certain number of days, it sends a message to the bug\'s\n'
-    'owner.  The number of days is controlled by a Bugzilla parameter\n'
-    'called "whinedays".  The content of the email message is controlled by\n'
-    'a Bugzilla parameter called "whinemail".</p>',
     (
         '2.19.1',
         None,
-        '<p>%(VERSION_STRING)sThe <code>whine.pl</code>\n'
-        'script runs a separate whine system, which allows a number of whine\n'
-        'schedules to be established with varying frequency (up to every 15\n'
-        'minutes), criteria, and content of whine messages.  It is configured\n'
-        'with <code>editwhines.cgi</code>.  Obviously, <code>whine.pl</code>\n'
-        'needs to be run every 15 minutes in order to send the most frequent\n'
-        'messages.</p>\n'
-        '\n'
-        '<p>Users must be in the bz_canusewhines group to configure whine\n'
-        'messages.  Users must be in the bz_canusewhineatothers group to\n'
-        'configure whine messages <em>to be sent to other users</em>.  These\n'
-        'restrictions are checked when configuring whine messages and also\n'
-        'before messages are sent, so removing a user from one of these groups\n'
-        'will disable any whines which that user has configured.</p>\n'
-        '\n'
-        '<p>A whine schedule, stored in %(the-table-whine_schedules)s,\n'
-        'specifies the frequency with which an email should be sent to a\n'
-        'particular user.  The email is specified with a whine event (see\n'
-        'below).  There is a variety of ways of specifying the frequency: both\n'
-        'days (every day, a particular day of the week, weekdays only, a\n'
-        'particular day of the month, the last day of the month) and times (a\n'
-        'particular hour, or every 15, 30, or 60 minutes).</p>\n'
-        '\n',
+        (
+            '<p>%(VERSION_STRING)sThe <code>whine.pl</code>\n'
+            'script runs a separate whine system, which allows a number of whine\n'
+            'schedules to be established with varying frequency (up to every 15\n'
+            'minutes), criteria, and content of whine messages.  It is configured\n'
+            'with <code>editwhines.cgi</code>.  Obviously, <code>whine.pl</code>\n'
+            'needs to be run every 15 minutes in order to send the most frequent\n'
+            'messages.</p>\n'
+            '\n'
+            '<p>Users must be in the bz_canusewhines group to configure whine\n'
+            'messages.  Users must be in the bz_canusewhineatothers group to\n'
+            'configure whine messages <em>to be sent to other users</em>.  These\n'
+            'restrictions are checked when configuring whine messages and also\n'
+            'before messages are sent, so removing a user from one of these groups\n'
+            'will disable any whines which that user has configured.</p>\n'
+            '\n'
+            '<p>A whine schedule, stored in %(the-table-whine_schedules)s,\n'
+            'specifies the frequency with which an email should be sent to a\n'
+            'particular user.  The email is specified with a whine event (see\n'
+            'below).  There is a variety of ways of specifying the frequency: both\n'
+            'days (every day, a particular day of the week, weekdays only, a\n'
+            'particular day of the month, the last day of the month) and times (a\n'
+            'particular hour, or every 15, 30, or 60 minutes).</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.19.3',
         None,
-        '\n'
-        '<p>%(VERSION_STRING)sWhines may be scheduled for groups as well as '
-        'users.</p>',
+        (
+            '\n'
+            '<p>%(VERSION_STRING)sWhines may be scheduled for groups as well as '
+            'users.</p>'
+        ),
     ),
     (
         '2.19.1',
         None,
-        '\n'
-        '<p>A whine schedule, stored in %(the-table-whine_schedules)s,\n'
-        'specifies the frequency with which an email should be sent to a\n'
-        'particular user.  The email is specified with a whine event (see\n'
-        'below).  There is a variety of ways of specifying the frequency: both\n'
-        'days (every day, a particular day of the week, weekdays only, a\n'
-        'particular day of the month, the last day of the month) and times (a\n'
-        'particular hour, or every 15, 30, or 60 minutes).</p>\n'
-        '\n'
-        '<p>A whine event, stored in %(the-table-whine_events)s, describes an\n'
-        'email message: subject line and some body text to precede query\n'
-        'results.  A message may consist of more than one whine query.</p>\n'
-        '\n'
-        '<p>A whine query, stored in %(the-table-whine_queries)s is a <a\n'
-        'href="#notes-namedqueries">named query</a>, to which a title is given\n'
-        'for use in email messages.  Whine queries are stored in\n'
-        '%(the-table-whine_queries)s.  A whine query may specify that a\n'
-        'separate message is to be sent for each bug found.</p>\n',
+        (
+            '\n'
+            '<p>A whine schedule, stored in %(the-table-whine_schedules)s,\n'
+            'specifies the frequency with which an email should be sent to a\n'
+            'particular user.  The email is specified with a whine event (see\n'
+            'below).  There is a variety of ways of specifying the frequency: both\n'
+            'days (every day, a particular day of the week, weekdays only, a\n'
+            'particular day of the month, the last day of the month) and times (a\n'
+            'particular hour, or every 15, 30, or 60 minutes).</p>\n'
+            '\n'
+            '<p>A whine event, stored in %(the-table-whine_events)s, describes an\n'
+            'email message: subject line and some body text to precede query\n'
+            'results.  A message may consist of more than one whine query.</p>\n'
+            '\n'
+            '<p>A whine query, stored in %(the-table-whine_queries)s is a <a\n'
+            'href="#notes-namedqueries">named query</a>, to which a title is given\n'
+            'for use in email messages.  Whine queries are stored in\n'
+            '%(the-table-whine_queries)s.  A whine query may specify that a\n'
+            'separate message is to be sent for each bug found.</p>\n'
+        ),
     ),
     (
         '2.19.3',
         None,
+        (
+            '\n'
+            '<h3><a id="notes-settings" name="notes-settings">Settings</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sThere are several user-interface preferences,\n'
+            'each of which can take a number of values.  Each preference has a row\n'
+            'in the %(table-setting)s table, and possible values in the\n'
+            '%(table-setting_value)s table.  The administrator may set a default\n'
+            'value for each preference (%(column-setting-default_value)s) and\n'
+            'determine whether users are able to override the default\n'
+            '(%(column-setting-is_enabled)s).  The user\'s individual preferences\n'
+            'are recorded in the %(table-profile_setting)s table.</p>'
+        ),
+    ),
+    (
         '\n'
-        '<h3><a id="notes-settings" name="notes-settings">Settings</a></h3>\n'
         '\n'
-        '<p>%(VERSION_STRING)sThere are several user-interface preferences,\n'
-        'each of which can take a number of values.  Each preference has a row\n'
-        'in the %(table-setting)s table, and possible values in the\n'
-        '%(table-setting_value)s table.  The administrator may set a default\n'
-        'value for each preference (%(column-setting-default_value)s) and\n'
-        'determine whether users are able to override the default\n'
-        '(%(column-setting-is_enabled)s).  The user\'s individual preferences\n'
-        'are recorded in the %(table-profile_setting)s table.</p>',
+        '<h3><a id="notes-quips" name="notes-quips">Quips</a></h3>\n'
+        '\n'
+        '<p>Bugzilla supports "quips": small text messages, often humorous,\n'
+        'which appear along with search results.  The quips are selected at\n'
+        'random from a set.</p>'
     ),
-    '\n'
-    '\n'
-    '<h3><a id="notes-quips" name="notes-quips">Quips</a></h3>\n'
-    '\n'
-    '<p>Bugzilla supports "quips": small text messages, often humorous,\n'
-    'which appear along with search results.  The quips are selected at\n'
-    'random from a set.</p>',
     (
         None,
         '2.16.7',
@@ -5126,501 +5258,436 @@
     (
         '2.14',
         None,
-        '<p>%(VERSION_STRING)sQuips may be entered or deleted\n'
-        'using <code>quips.cgi</code>.</p>',
+        (
+            '<p>%(VERSION_STRING)sQuips may be entered or deleted\n'
+            'using <code>quips.cgi</code>.</p>'
+        ),
     ),
     (
         '2.17.4',
         None,
-        '<p>%(VERSION_STRING)sQuips may be entered by any\n'
-        'user but must be approved by an administrator before they can be\n'
-        'displayed.</p>',
+        (
+            '<p>%(VERSION_STRING)sQuips may be entered by any\n'
+            'user but must be approved by an administrator before they can be\n'
+            'displayed.</p>'
+        ),
     ),
     (
         '3.3.2',
         None,
-        '\n'
-        '<h3><a id="notes-see_also" name="notes-see_also">References to other '
-        'Bugzillas</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugzilla can record connections to bugs in other\n'
-        'instances of Bugzilla, if the parameter "use_see_also" is set.  The\n'
-        'connections are displayed as clickable URLs and are stored as URLs in\n'
-        '%(the-table-bug_see_also)s.  They are validated according to the\n'
-        'system\'s notion of a valid form for Bugzilla URLs.</p> ',
+        (
+            '\n'
+            '<h3><a id="notes-see_also" name="notes-see_also">References to other '
+            'Bugzillas</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugzilla can record connections to bugs in other\n'
+            'instances of Bugzilla, if the parameter "use_see_also" is set.  The\n'
+            'connections are displayed as clickable URLs and are stored as URLs in\n'
+            '%(the-table-bug_see_also)s.  They are validated according to the\n'
+            'system\'s notion of a valid form for Bugzilla URLs.</p> '
+        ),
     ),
     (
         '2.23.1',
         None,
-        '\n'
-        '<h3><a id="notes-customfields" name="notes-customfields">Custom '
-        'Fields</a></h3>\n'
-        '\n'
-        '<p>%(VERSION_STRING)sBugzilla supports custom fields.  Each custom\n'
-        'field is a new column in %(the-table-bugs)s, with a name beginning\n'
-        '<code>cf_</code>.  The presence of each custom field is indicated by a\n'
-        'row in %(the-table-fielddefs)s, with %(column-fielddefs-custom)s set\n'
-        'to 1.  The type of each custom field is specified by\n'
-        '%(column-fielddefs-type)s:\n'
-        '\n'
-        '<p>The value 1 (FIELD_TYPE_FREETEXT) indicates a free-form text field\n'
-        '(type varchar(255)).</p>\n'
-        '\n',
+        (
+            '\n'
+            '<h3><a id="notes-customfields" name="notes-customfields">Custom '
+            'Fields</a></h3>\n'
+            '\n'
+            '<p>%(VERSION_STRING)sBugzilla supports custom fields.  Each custom\n'
+            'field is a new column in %(the-table-bugs)s, with a name beginning\n'
+            '<code>cf_</code>.  The presence of each custom field is indicated by a\n'
+            'row in %(the-table-fielddefs)s, with %(column-fielddefs-custom)s set\n'
+            'to 1.  The type of each custom field is specified by\n'
+            '%(column-fielddefs-type)s:\n'
+            '\n'
+            '<p>The value 1 (FIELD_TYPE_FREETEXT) indicates a free-form text field\n'
+            '(type varchar(255)).</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.23.3',
         None,
-        '\n'
-        '\n'
-        '<p>%(VERSION_STRING)sThe value 2 (FIELD_TYPE_SINGLE_SELECT) indicates\n'
-        'a single-select field (type varchar(64), not null, default \'---\').\n'
-        'The allowable values of that field are stored in a special table with\n'
-        'the same <code>cf_&lt;name&gt;</code> name as the field, and a schema\n'
-        'like this:</p>\n'
-        '\n'
-        '<table border="1" cellpadding="5" cellspacing="0">\n'
-        '\n'
-        '<tbody>\n'
-        '<a id="table-customfield" name="table-customfield"><tr align="left" '
-        'valign="top">\n'
-        '<th>Field</th>\n'
-        '<th>Type</th>\n'
-        '<th>Default</th>\n'
-        '<th>Properties</th>\n'
-        '<th>Remarks</th>\n'
-        '</tr></a>\n'
-        '\n'
-        '<tr align="left" valign="top">\n'
-        '<th><a id="column-customfield-id" name="column-customfield-id">id</a></th>\n'
-        '<td>smallint</td>\n'
-        '<td>0</td>\n'
-        '<td>auto_increment</td>\n'
-        '<td>a unique ID.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr align="left" valign="top">\n'
-        '<th><a id="column-customfield-value" '
-        'name="column-customfield-value">value</a></th>\n'
-        '<td>varchar(64)</td>\n'
-        '<td>\'\'</td>\n'
-        '<td>-</td>\n'
-        '<td>the text value</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr align="left" valign="top">\n'
-        '<th><a id="column-customfield-sortkey" '
-        'name="column-customfield-sortkey">sortkey</a></th>\n'
-        '<td>smallint</td>\n'
-        '<td>0</td>\n'
-        '<td>-</td>\n'
-        '<td>a number determining the order in which values appear.</td>\n'
-        '</tr>\n'
-        '\n'
-        '<tr align="left" valign="top">\n'
-        '<th><a id="column-customfield-isactive" '
-        'name="column-customfield-isactive">isactive</a></th>\n'
-        '<td>tinyint</td>\n'
-        '<td>1</td>\n'
-        '<td>-</td>\n'
-        '<td>1 if this value is currently available, 0 otherwise</td>\n'
-        '</tr>\n',
+        (
+            '\n\n<p>%(VERSION_STRING)sThe value 2 (FIELD_TYPE_SINGLE_SELECT)'
+            ' indicates\na single-select field (type varchar(64), not null, default'
+            ' \'---\').\nThe allowable values of that field are stored in a special'
+            ' table with\nthe same <code>cf_&lt;name&gt;</code> name as the field, and'
+            ' a schema\nlike this:</p>\n\n<table border="1" cellpadding="5"'
+            ' cellspacing="0">\n\n<tbody>\n<a id="table-customfield"'
+            ' name="table-customfield"><tr align="left"'
+            ' valign="top">\n<th>Field</th>\n<th>Type</th>\n<th>Default</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr></a>\n\n<tr'
+            ' align="left" valign="top">\n<th><a id="column-customfield-id"'
+            ' name="column-customfield-id">id</a></th>\n<td>smallint</td>\n<td>0</td>\n<td>auto_increment</td>\n<td>a'
+            ' unique ID.</td>\n</tr>\n\n<tr align="left" valign="top">\n<th><a'
+            ' id="column-customfield-value"'
+            ' name="column-customfield-value">value</a></th>\n<td>varchar(64)</td>\n<td>\'\'</td>\n<td>-</td>\n<td>the'
+            ' text value</td>\n</tr>\n\n<tr align="left" valign="top">\n<th><a'
+            ' id="column-customfield-sortkey"'
+            ' name="column-customfield-sortkey">sortkey</a></th>\n<td>smallint</td>\n<td>0</td>\n<td>-</td>\n<td>a'
+            ' number determining the order in which values appear.</td>\n</tr>\n\n<tr'
+            ' align="left" valign="top">\n<th><a id="column-customfield-isactive"'
+            ' name="column-customfield-isactive">isactive</a></th>\n<td>tinyint</td>\n<td>1</td>\n<td>-</td>\n<td>1'
+            ' if this value is currently available, 0 otherwise</td>\n</tr>\n'
+        ),
     ),
     (
         '3.3.1',
         None,
-        '\n'
-        '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '<th><a id="column-customfield-visibility_value_id" '
-        'name="column-customfield-visibility_value_id">visibility_value_id</a></th>\n'
-        '<td>smallint</td>\n'
-        '<td>0</td>\n'
-        '<td>-</td>\n'
-        '<td>If set, this value is only available if the chooser field (identified '
-        'by %(column-fielddefs-value_field_id)s) has the value with this ID.  '
-        'Foreign key &lt;field&gt;.id, for example %(column-products-id)s or <a '
-        'href="#column-customfield-id">cf_&lt;field&gt;.id</a>.</td>\n'
-        '</tr>\n',
+        (
+            '\n<tr %(VERSION_COLOUR)s align="left" valign="top">\n<th><a'
+            ' id="column-customfield-visibility_value_id"'
+            ' name="column-customfield-visibility_value_id">visibility_value_id</a></th>\n<td>smallint</td>\n<td>0</td>\n<td>-</td>\n<td>If'
+            ' set, this value is only available if the chooser field (identified by'
+            ' %(column-fielddefs-value_field_id)s) has the value with this ID.  Foreign'
+            ' key &lt;field&gt;.id, for example %(column-products-id)s or <a'
+            ' href="#column-customfield-id">cf_&lt;field&gt;.id</a>.</td>\n</tr>\n'
+        ),
     ),
     (
         '2.23.3',
         None,
-        '\n\n</tbody></table>\n\n<p>Indexes:</p>\n\n<table border="1" cellpadding="5"'
-        ' cellspacing="0">\n\n<tbody>\n<tr align="left"'
-        ' valign="top">\n<th>Name</th>\n<th>Fields</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr>\n\n<tr'
-        ' align="left" valign="top">\n<th><a id="index-customfield-PRIMARY"'
-        ' name="index-customfield-PRIMARY">PRIMARY</a></th>\n<td>id</td>\n<td>unique</td>\n<td>-</td>\n</tr>\n\n<tr'
-        ' align="left" valign="top">\n<th><a id="index-customfield-cf_field_value_idx"'
-        ' name="index-customfield-cf_field_value_idx">cf_&lt;field&gt;_value_idx</a></th>\n<td>value</td>\n<td>unique</td>\n<td>-</td>\n</tr>\n\n<tr'
-        ' align="left" valign="top">\n<th><a'
-        ' id="index-customfield-cf_field_sortkey_idx"'
-        ' name="index-customfield-cf_field_sortkey_idx">cf_&lt;field&gt;_sortkey_idx</a></th>\n<td>sortkey</td>\n'
-        ' <td>-</td>\n<td>-</td>\n</tr>\n\n',
+        (
+            '\n\n</tbody></table>\n\n<p>Indexes:</p>\n\n<table border="1"'
+            ' cellpadding="5" cellspacing="0">\n\n<tbody>\n<tr align="left"'
+            ' valign="top">\n<th>Name</th>\n<th>Fields</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr>\n\n<tr'
+            ' align="left" valign="top">\n<th><a id="index-customfield-PRIMARY"'
+            ' name="index-customfield-PRIMARY">PRIMARY</a></th>\n<td>id</td>\n<td>unique</td>\n<td>-</td>\n</tr>\n\n<tr'
+            ' align="left" valign="top">\n<th><a'
+            ' id="index-customfield-cf_field_value_idx"'
+            ' name="index-customfield-cf_field_value_idx">cf_&lt;field&gt;_value_idx</a></th>\n<td>value</td>\n<td>unique</td>\n<td>-</td>\n</tr>\n\n<tr'
+            ' align="left" valign="top">\n<th><a'
+            ' id="index-customfield-cf_field_sortkey_idx"'
+            ' name="index-customfield-cf_field_sortkey_idx">cf_&lt;field&gt;_sortkey_idx</a></th>\n<td>sortkey</td>\n'
+            ' <td>-</td>\n<td>-</td>\n</tr>\n\n'
+        ),
     ),
     (
         '3.3.1',
         None,
-        '\n'
-        '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
-        '<th><a id="index-customfield-cf_field_visibility_value_id_idx" '
-        'name="index-customfield-cf_field_visibility_value_id_idx">cf_&lt;field&gt;_visibility_value_id_idx</a></th>\n'
-        '<td>visibility_value_id</td>\n'
-        '<td>-</td>\n'
-        '<td>-</td>\n'
-        '</tr>\n',
+        (
+            '\n'
+            '<tr %(VERSION_COLOUR)s align="left" valign="top">\n'
+            '<th><a id="index-customfield-cf_field_visibility_value_id_idx" '
+            'name="index-customfield-cf_field_visibility_value_id_idx">cf_&lt;field&gt;_visibility_value_id_idx</a></th>\n'
+            '<td>visibility_value_id</td>\n'
+            '<td>-</td>\n'
+            '<td>-</td>\n'
+            '</tr>\n'
+        ),
     ),
     ('2.23.3', None, '</tbody></table>'),
     (
         '3.1.2',
         None,
-        '<p>%(VERSION_STRING)sThe value 3 (FIELD_TYPE_MULTI_SELECT)\nindicates a'
-        ' multi-select field.  The allowable values of that field\nare stored in a'
-        ' <code>cf_&lt;name&gt;</code> table as for\nFIELD_TYPE_SINGLE_SELECT, above. '
-        ' The actual values of the field are\nnot stored in %(the-table-bugs)s, unlike'
-        ' other custom fields, Instead\nthey are stored in another table, with the'
-        ' name\n<code>bug_cf_&lt;name&gt;</code>, and a schema like this:</p>\n\n<table'
-        ' border="1" cellpadding="5" cellspacing="0">\n\n<tbody>\n<a'
-        ' id="table-multiselect" name="table-multiselect"><tr align="left"'
-        ' valign="top">\n<th>Field</th>\n<th>Type</th>\n<th>Default</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr></a>\n\n<tr'
-        ' align="left" valign="top">\n<th><a id="column-multiselect-bug_id"'
-        ' name="column-multiselect-bug_id">bug_id</a></th>\n<td>mediumint</td>\n<td>0</td>\n<td></td>\n<td>The'
-        ' bug ID (foreign key %(column-bugs-bug_id)s).</td>\n</tr>\n\n<tr align="left"'
-        ' valign="top">\n<th><a id="column-multiselect-value"'
-        ' name="column-multiselect-value">value</a></th>\n<td>varchar(64)</td>\n<td>\'\'</td>\n<td>-</td>\n<td>the'
-        ' value (foreign key <a'
-        ' href="#column-customfield-value">cf_&lt;name&gt;.value</a>).</td>\n</tr>\n</tbody></table>\n\n<p>Indexes:</p>\n\n<table'
-        ' border="1" cellpadding="5" cellspacing="0">\n\n<tbody>\n<tr align="left"'
-        ' valign="top">\n<th>Name</th>\n<th>Fields</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr>\n\n<tr'
-        ' align="left" valign="top">\n<th><a id="index-multiselect-cf_field_bug_id_idx"'
-        ' name="index-multiselect-cf_field_bug_id_idx">cf_&lt;field&gt;_bug_id_idx</a></th>\n<td>bug_id,'
-        ' value</td>\n<td>unique</td>\n<td>-</td>\n</tr>\n\n</tbody></table>\n\n<p>%(VERSION_STRING)sThe'
-        ' value 4 (FIELD_TYPE_TEXTAREA)\nindicates a large text-box field (type'
-        ' mediumtext).</p>\n',
+        (
+            '<p>%(VERSION_STRING)sThe value 3 (FIELD_TYPE_MULTI_SELECT)\nindicates a'
+            ' multi-select field.  The allowable values of that field\nare stored in a'
+            ' <code>cf_&lt;name&gt;</code> table as for\nFIELD_TYPE_SINGLE_SELECT,'
+            ' above.  The actual values of the field are\nnot stored in'
+            ' %(the-table-bugs)s, unlike other custom fields, Instead\nthey are stored'
+            ' in another table, with the name\n<code>bug_cf_&lt;name&gt;</code>, and a'
+            ' schema like this:</p>\n\n<table border="1" cellpadding="5"'
+            ' cellspacing="0">\n\n<tbody>\n<a id="table-multiselect"'
+            ' name="table-multiselect"><tr align="left"'
+            ' valign="top">\n<th>Field</th>\n<th>Type</th>\n<th>Default</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr></a>\n\n<tr'
+            ' align="left" valign="top">\n<th><a id="column-multiselect-bug_id"'
+            ' name="column-multiselect-bug_id">bug_id</a></th>\n<td>mediumint</td>\n<td>0</td>\n<td></td>\n<td>The'
+            ' bug ID (foreign key %(column-bugs-bug_id)s).</td>\n</tr>\n\n<tr'
+            ' align="left" valign="top">\n<th><a id="column-multiselect-value"'
+            ' name="column-multiselect-value">value</a></th>\n<td>varchar(64)</td>\n<td>\'\'</td>\n<td>-</td>\n<td>the'
+            ' value (foreign key <a'
+            ' href="#column-customfield-value">cf_&lt;name&gt;.value</a>).</td>\n</tr>\n</tbody></table>\n\n<p>Indexes:</p>\n\n<table'
+            ' border="1" cellpadding="5" cellspacing="0">\n\n<tbody>\n<tr align="left"'
+            ' valign="top">\n<th>Name</th>\n<th>Fields</th>\n<th>Properties</th>\n<th>Remarks</th>\n</tr>\n\n<tr'
+            ' align="left" valign="top">\n<th><a'
+            ' id="index-multiselect-cf_field_bug_id_idx"'
+            ' name="index-multiselect-cf_field_bug_id_idx">cf_&lt;field&gt;_bug_id_idx</a></th>\n<td>bug_id,'
+            ' value</td>\n<td>unique</td>\n<td>-</td>\n</tr>\n\n</tbody></table>\n\n<p>%(VERSION_STRING)sThe'
+            ' value 4 (FIELD_TYPE_TEXTAREA)\nindicates a large text-box field (type'
+            ' mediumtext).</p>\n'
+        ),
     ),
     (
         '3.1.3',
         None,
-        '<p>%(VERSION_STRING)sThe value 5 (FIELD_TYPE_DATETIME)\n'
-        'indicates a date/time field (type datetime).</p>\n',
+        (
+            '<p>%(VERSION_STRING)sThe value 5 (FIELD_TYPE_DATETIME)\n'
+            'indicates a date/time field (type datetime).</p>\n'
+        ),
     ),
     (
         '3.3.1',
         None,
-        '<p>%(VERSION_STRING)sThe value 6 (FIELD_TYPE_BUG_ID)\n'
-        'indicates a bug ID field (type mediumint).</p>\n',
+        (
+            '<p>%(VERSION_STRING)sThe value 6 (FIELD_TYPE_BUG_ID)\n'
+            'indicates a bug ID field (type mediumint).</p>\n'
+        ),
     ),
     (
         '2.23.1',
         '2.23.2',
-        '<p>%(VERSION_STRING)sCustom fields are\n'
-        'manipulated from the command-line with the <code>customfield.pl</code>\n'
-        'script.</p>',
+        (
+            '<p>%(VERSION_STRING)sCustom fields are\n'
+            'manipulated from the command-line with the <code>customfield.pl</code>\n'
+            'script.</p>'
+        ),
     ),
     (
         '2.23.3',
         None,
-        '<p>%(VERSION_STRING)sCustom fields are configured\n'
-        'using <code>editfield.cgi</code>.</p>',
+        (
+            '<p>%(VERSION_STRING)sCustom fields are configured\n'
+            'using <code>editfield.cgi</code>.</p>'
+        ),
+    ),
+    (
+        '<h3><a id="notes-tables" name="notes-tables">List of tables</a></h3>\n'
+        '\n'
+        '%(TABLES_TABLE)s\n'
+        '\n'
+        '<h2><a id="section-3" name="section-3">3. The schema</a></h2>\n'
+        '\n'
     ),
-    '<h3><a id="notes-tables" name="notes-tables">List of tables</a></h3>\n'
-    '\n'
-    '%(TABLES_TABLE)s\n'
-    '\n'
-    '<h2><a id="section-3" name="section-3">3. The schema</a></h2>\n'
-    '\n',
 ]
 
 # This afterword is included in the generated schema doc after the
 # schema itself.
 
 afterword = [
-    '\n'
-    '\n'
-    '<h2><a id="section-4" name="section-4">4. Bugzilla History</a></h2>\n'
-    '\n'
-    '<h3><a id="history-release-table" name="history-release-table">Bugzilla '
-    'releases</a></h3>\n'
-    '\n'
-    '<p>This table gives the dates of all the Bugzilla releases since 2.0.</p>\n'
-    '\n'
-    '<table border="1" cellspacing="0" cellpadding="5">\n'
-    '\n'
-    '<thead>\n'
-    '\n'
-    '  <tr align="left">\n'
-    '\n'
-    '    <th>Date</th>\n'
-    '\n'
-    '    <th>Release</th>\n'
-    '\n'
-    '    <th>Notes</th>\n'
-    '  </tr>\n'
-    '\n'
-    '</thead>\n'
-    '\n'
-    '<tbody>\n'
-    '%(VERSIONS_TABLE)s\n'
-    '</tbody>\n'
-    '</table>\n'
-    '\n'
-    '<h3><a id="history-schema-changes" name="history-schema-changes">Bugzilla '
-    'Schema Changes</a></h3>\n'
-    '\n',
     (
-        '2.2',
-        '2.2',
-        '<p>In Bugzilla release 2.2, the following schema\n'
-        'changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
         '\n'
-        '  <li>%(the-table-products)s was added.</li>\n'
         '\n'
-        '  <li>%(column-bugs-qa_contact)s, %(column-bugs-status_whiteboard)s,\n'
-        '  and %(column-bugs-target_milestone)s were added.</li>\n'
+        '<h2><a id="section-4" name="section-4">4. Bugzilla History</a></h2>\n'
         '\n'
-        '  <li>%(column-bugs-op_sys)s changed from tinytext to a non-null enum,\n'
-        '  default All.</li>\n'
+        '<h3><a id="history-release-table" name="history-release-table">Bugzilla '
+        'releases</a></h3>\n'
         '\n'
-        '  <li>\'X-Windows\' was removed from %(column-bugs-rep_platform)s.</li>\n'
+        '<p>This table gives the dates of all the Bugzilla releases since 2.0.</p>\n'
         '\n'
-        '  <li>%(column-components-description)s and\n'
-        '  %(column-components-initialqacontact)s were added.</li>\n'
-        '\n'
-        '  <li>%(column-components-initialowner)s became non-null default \'\'.</li>\n'
-        '\n'
-        '  <li>Indexes %(index-bugs-op_sys)s, %(index-bugs-qa_contact)s, and\n'
-        '  %(index-bugs-target_milestone)s were added.</li>\n'
-        '\n'
-        '  <li>Indexes %(index-cc-bug_id)s and %(index-cc-who)s were added.</li>\n'
+        '<table border="1" cellspacing="0" cellpadding="5">\n'
         '\n'
-        '</ul>\n'
-        '\n',
-    ),
-    (
-        '2.4',
-        '2.4',
-        '<p>In Bugzilla release 2.4, the following schema\n'
-        'changes were made:</p>\n'
+        '<thead>\n'
         '\n'
-        '<ul>\n'
+        '  <tr align="left">\n'
         '\n'
-        '  <li>%(the-table-groups)s, %(column-profiles-groupset)s, and\n'
-        '  %(column-bugs-groupset)s were added, introducing <a\n'
-        '  href="#notes-groups ">groups</a>.</li>\n'
+        '    <th>Date</th>\n'
         '\n'
-        '  <li>%(the-table-dependencies)s was added, introducing <a\n'
-        '  href="#notes-dependencies">dependencies</a>.</li>\n'
+        '    <th>Release</th>\n'
         '\n'
-        '  <li>The value \'blocker\' was added to %(column-bugs-bug_severity)s and the '
-        'default was change from \'critical\' to \'blocker\'.</li>\n'
+        '    <th>Notes</th>\n'
+        '  </tr>\n'
         '\n'
-        '  <li>%(column-bugs-creation_ts)s became non-null, with default 0000-00-00 '
-        '00:00:00, and was added as the index %(index-bugs-creation_ts)s.</li>\n'
+        '</thead>\n'
         '\n'
-        '  <li>%(column-profiles-emailnotification)s was added.</li>\n'
+        '<tbody>\n'
+        '%(VERSIONS_TABLE)s\n'
+        '</tbody>\n'
+        '</table>\n'
         '\n'
-        '  <li>Additional values were permitted in %(column-bugs-op_sys)s.</li>\n'
+        '<h3><a id="history-schema-changes" name="history-schema-changes">Bugzilla '
+        'Schema Changes</a></h3>\n'
         '\n'
-        '</ul>\n'
-        '\n',
+    ),
+    (
+        '2.2',
+        '2.2',
+        (
+            '<p>In Bugzilla release 2.2, the following schema\nchanges were'
+            ' made:</p>\n\n<ul>\n\n  <li>%(the-table-products)s was added.</li>\n\n '
+            ' <li>%(column-bugs-qa_contact)s, %(column-bugs-status_whiteboard)s,\n  and'
+            ' %(column-bugs-target_milestone)s were added.</li>\n\n '
+            ' <li>%(column-bugs-op_sys)s changed from tinytext to a non-null enum,\n '
+            ' default All.</li>\n\n  <li>\'X-Windows\' was removed from'
+            ' %(column-bugs-rep_platform)s.</li>\n\n '
+            ' <li>%(column-components-description)s and\n '
+            ' %(column-components-initialqacontact)s were added.</li>\n\n '
+            ' <li>%(column-components-initialowner)s became non-null default'
+            ' \'\'.</li>\n\n  <li>Indexes %(index-bugs-op_sys)s,'
+            ' %(index-bugs-qa_contact)s, and\n  %(index-bugs-target_milestone)s were'
+            ' added.</li>\n\n  <li>Indexes %(index-cc-bug_id)s and %(index-cc-who)s'
+            ' were added.</li>\n\n</ul>\n\n'
+        ),
+    ),
+    (
+        '2.4',
+        '2.4',
+        (
+            '<p>In Bugzilla release 2.4, the following schema\nchanges were'
+            ' made:</p>\n\n<ul>\n\n  <li>%(the-table-groups)s,'
+            ' %(column-profiles-groupset)s, and\n  %(column-bugs-groupset)s were added,'
+            ' introducing <a\n  href="#notes-groups ">groups</a>.</li>\n\n '
+            ' <li>%(the-table-dependencies)s was added, introducing <a\n '
+            ' href="#notes-dependencies">dependencies</a>.</li>\n\n  <li>The value'
+            ' \'blocker\' was added to %(column-bugs-bug_severity)s and the default was'
+            ' change from \'critical\' to \'blocker\'.</li>\n\n '
+            ' <li>%(column-bugs-creation_ts)s became non-null, with default 0000-00-00'
+            ' 00:00:00, and was added as the index %(index-bugs-creation_ts)s.</li>\n\n'
+            '  <li>%(column-profiles-emailnotification)s was added.</li>\n\n '
+            ' <li>Additional values were permitted in'
+            ' %(column-bugs-op_sys)s.</li>\n\n</ul>\n\n'
+        ),
     ),
     (
         '2.6',
         '2.6',
-        '<p>In Bugzilla release 2.6, the following schema\n'
-        'changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(the-table-attachments)s was added, introducing <a\n'
-        '  href="#notes-attachments ">attachments</a>.</li>\n'
-        '\n'
-        '  <li>%(the-table-dependencies)s was added, introducing <a\n'
-        '  href="#notes-dependencies ">dependencies</a>.</li>\n'
-        '\n'
-        '  <li>The value \'blocker\' was added to %(column-bugs-bug_severity)s and the '
-        'default was change from \'critical\' to \'blocker\'.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-creation_ts)s became non-null, with default 0000-00-00 '
-        '00:00:00, and was added as the index %(index-bugs-creation_ts)s.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-emailnotification)s was added.</li>\n'
-        '\n'
-        '  <li>Additional values were permitted in %(column-bugs-op_sys)s.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.6, the following schema\nchanges were'
+            ' made:</p>\n\n<ul>\n\n  <li>%(the-table-attachments)s was added,'
+            ' introducing <a\n  href="#notes-attachments ">attachments</a>.</li>\n\n '
+            ' <li>%(the-table-dependencies)s was added, introducing <a\n '
+            ' href="#notes-dependencies ">dependencies</a>.</li>\n\n  <li>The value'
+            ' \'blocker\' was added to %(column-bugs-bug_severity)s and the default was'
+            ' change from \'critical\' to \'blocker\'.</li>\n\n '
+            ' <li>%(column-bugs-creation_ts)s became non-null, with default 0000-00-00'
+            ' 00:00:00, and was added as the index %(index-bugs-creation_ts)s.</li>\n\n'
+            '  <li>%(column-profiles-emailnotification)s was added.</li>\n\n '
+            ' <li>Additional values were permitted in'
+            ' %(column-bugs-op_sys)s.</li>\n\n</ul>\n\n'
+        ),
     ),
     (
         '2.8',
         '2.8',
-        '<p>In Bugzilla release 2.8, the following schema\n'
-        'changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(the-table-votes)s, %(column-bugs-votes)s, and\n'
-        '  %(column-products-votesperuser)s were added, introducing <a\n'
-        '  href="#notes-voting">voting</a>.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-product)s was changed from varchar(16) to varchar(64), '
-        'and %(column-products-product)s, %(column-versions-program)s, and '
-        '%(column-components-program)s were all changed from tinytext to '
-        'varchar(64), lengthening product names.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-area)s was removed.</li>\n'
-        '\n'
-        '  <li>%(column-bugs_activity-when)s was renamed as '
-        '%(column-bugs_activity-bug_when)s .</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.8, the following schema\nchanges were'
+            ' made:</p>\n\n<ul>\n\n  <li>%(the-table-votes)s, %(column-bugs-votes)s,'
+            ' and\n  %(column-products-votesperuser)s were added, introducing <a\n '
+            ' href="#notes-voting">voting</a>.</li>\n\n  <li>%(column-bugs-product)s'
+            ' was changed from varchar(16) to varchar(64), and'
+            ' %(column-products-product)s, %(column-versions-program)s, and'
+            ' %(column-components-program)s were all changed from tinytext to'
+            ' varchar(64), lengthening product names.</li>\n\n '
+            ' <li>%(column-bugs-area)s was removed.</li>\n\n '
+            ' <li>%(column-bugs_activity-when)s was renamed as'
+            ' %(column-bugs_activity-bug_when)s .</li>\n\n</ul>\n\n'
+        ),
     ),
     (
         '2.10',
         '2.10',
-        '<p>In Bugzilla release 2.10, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(the-table-keywords)s, %(the-table-keyworddefs)s, and\n'
-        '  %(column-bugs-keywords)s were added, giving <a\n'
-        '  href="#notes-keywords">keywords</a> to bugs.</li>\n'
-        '\n'
-        '  <li>%(the-table-milestones)s and\n'
-        '  %(column-products-defaultmilestone)s were added, to implement <a\n'
-        '  href="#notes-milestones">milestones</a>.</li>\n'
-        '\n'
-        '  <li>%(the-table-fielddefs)s was added, and\n'
-        '  %(column-bugs_activity-field)s was changed to\n'
-        '  %(column-bugs_activity-fieldid)s, decoupling bug history from field\n'
-        '  names and providing longer field descriptions in bug change\n'
-        '  reports.</li>\n'
-        '\n'
-        '  <li>%(the-table-longdescs)s was added, and %(column-bugs-long_desc)s\n'
-        '  was removed, allowing multiple comments per bug.</li>\n'
-        '\n'
-        '  <li>%(the-table-namedqueries)s was added, for <a\n'
-        '  href="#notes-named-queries">named queries</a>.</li>\n'
-        '\n'
-        '  <li>%(the-table-profiles_activity)s was added, recording activity in\n'
-        '  %(the-table-profiles)s.</li>\n'
-        '\n'
-        '  <li>%(the-table-shadowlog)s was added, recording SQL activity for\n'
-        '  reflection into a <a href="#notes-shadow">shadow database</a>.</li>\n'
-        '\n'
-        '  <li>%(the-table-watch)s was added, allowing <a\n'
-        '  href="#notes-watchers">watchers</a>.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-everconfirmed)s,\n'
-        '  %(column-products-maxvotesperbug)s, and\n'
-        '  %(column-products-votestoconfirm)s was added, and UNCONFIRMED was\n'
-        '  added to %(column-bugs-bug_status)s, introducing <a\n'
-        '  href="#notes-voting">bug confirmation by voting</a>.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-lastdiffed)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-blessgroupset)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-disabledtext)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-mybugslink)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-newemailtech)s was added.</li>\n'
-        '\n'
-        '  <li>Additional values were permitted in %(column-bugs-op_sys)s.</li>\n'
-        '\n'
-        '  <li>The default value of %(column-bugs-target_milestone)s changed from \'\' '
-        'to \'---\'.</li>\n'
-        '\n'
-        '  <li>%(column-versions-program)s changed from "null default None" to '
-        '"non-null default \'\'".</li>\n'
-        '\n'
-        '  <li>%(column-cc-who)s was added to the index %(index-cc-bug_id)s.</li>\n'
-        '\n'
-        '  <li>The index %(index-profiles-login_name)s was made unique.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.10, the following schema changes'
+            ' were\nmade:</p>\n\n<ul>\n\n  <li>%(the-table-keywords)s,'
+            ' %(the-table-keyworddefs)s, and\n  %(column-bugs-keywords)s were added,'
+            ' giving <a\n  href="#notes-keywords">keywords</a> to bugs.</li>\n\n '
+            ' <li>%(the-table-milestones)s and\n  %(column-products-defaultmilestone)s'
+            ' were added, to implement <a\n '
+            ' href="#notes-milestones">milestones</a>.</li>\n\n '
+            ' <li>%(the-table-fielddefs)s was added, and\n '
+            ' %(column-bugs_activity-field)s was changed to\n '
+            ' %(column-bugs_activity-fieldid)s, decoupling bug history from field\n '
+            ' names and providing longer field descriptions in bug change\n '
+            ' reports.</li>\n\n  <li>%(the-table-longdescs)s was added, and'
+            ' %(column-bugs-long_desc)s\n  was removed, allowing multiple comments per'
+            ' bug.</li>\n\n  <li>%(the-table-namedqueries)s was added, for <a\n '
+            ' href="#notes-named-queries">named queries</a>.</li>\n\n '
+            ' <li>%(the-table-profiles_activity)s was added, recording activity in\n '
+            ' %(the-table-profiles)s.</li>\n\n  <li>%(the-table-shadowlog)s was added,'
+            ' recording SQL activity for\n  reflection into a <a'
+            ' href="#notes-shadow">shadow database</a>.</li>\n\n '
+            ' <li>%(the-table-watch)s was added, allowing <a\n '
+            ' href="#notes-watchers">watchers</a>.</li>\n\n '
+            ' <li>%(column-bugs-everconfirmed)s,\n  %(column-products-maxvotesperbug)s,'
+            ' and\n  %(column-products-votestoconfirm)s was added, and UNCONFIRMED'
+            ' was\n  added to %(column-bugs-bug_status)s, introducing <a\n '
+            ' href="#notes-voting">bug confirmation by voting</a>.</li>\n\n '
+            ' <li>%(column-bugs-lastdiffed)s was added.</li>\n\n '
+            ' <li>%(column-profiles-blessgroupset)s was added.</li>\n\n '
+            ' <li>%(column-profiles-disabledtext)s was added.</li>\n\n '
+            ' <li>%(column-profiles-mybugslink)s was added.</li>\n\n '
+            ' <li>%(column-profiles-newemailtech)s was added.</li>\n\n  <li>Additional'
+            ' values were permitted in %(column-bugs-op_sys)s.</li>\n\n  <li>The'
+            ' default value of %(column-bugs-target_milestone)s changed from \'\' to'
+            ' \'---\'.</li>\n\n  <li>%(column-versions-program)s changed from "null'
+            ' default None" to "non-null default \'\'".</li>\n\n  <li>%(column-cc-who)s'
+            ' was added to the index %(index-cc-bug_id)s.</li>\n\n  <li>The index'
+            ' %(index-profiles-login_name)s was made unique.</li>\n\n</ul>\n\n'
+        ),
     ),
     (
         '2.12',
         '2.12',
-        '<p>In Bugzilla release 2.12, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '  <li>%(the-table-duplicates)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-emailflags)s was added.</li>\n'
-        '\n'
-        '  <li>The %(column-bugs-resolution)s value <b>MOVED</b> was\n'
-        '  added.</li>\n'
-        '\n'
-        '  <li>A number of additional values were permitted in '
-        '%(column-bugs-op_sys)s.</li>\n'
-        '\n'
-        '  <li>%(column-components-initialowner)s and\n'
-        '  %(column-components-initialqacontact)s changed from "tinytext"\n'
-        '  (foreign key %(column-profiles-login_name)s) to "mediumint" (foreign\n'
-        '  key %(column-profiles-userid)s), default 0.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-disabledtext)s\n'
-        '  changed from "not null" to "null".</li>\n'
-        '\n'
-        '  <li>The default value of %(column-profiles-newemailtech)s\n'
-        '  changed from 0 to 1.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.12, the following schema changes were\n'
+            'made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '  <li>%(the-table-duplicates)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-emailflags)s was added.</li>\n'
+            '\n'
+            '  <li>The %(column-bugs-resolution)s value <b>MOVED</b> was\n'
+            '  added.</li>\n'
+            '\n'
+            '  <li>A number of additional values were permitted in '
+            '%(column-bugs-op_sys)s.</li>\n'
+            '\n'
+            '  <li>%(column-components-initialowner)s and\n'
+            '  %(column-components-initialqacontact)s changed from "tinytext"\n'
+            '  (foreign key %(column-profiles-login_name)s) to "mediumint" (foreign\n'
+            '  key %(column-profiles-userid)s), default 0.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-disabledtext)s\n'
+            '  changed from "not null" to "null".</li>\n'
+            '\n'
+            '  <li>The default value of %(column-profiles-newemailtech)s\n'
+            '  changed from 0 to 1.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '2.14',
         '2.14',
-        '<p>In Bugzilla release 2.14, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '  <li>%(the-table-tokens)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-password)s was\n'
-        '  removed.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-cryptpassword)s and\n'
-        '  %(column-logincookies-cryptpassword)s\n'
-        '  were both changed from varchar(64) to varchar(32).</li>\n'
-        '\n'
-        '  <li>%(column-profiles-newemailtech)s was\n'
-        '  removed.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-emailnotification)s\n'
-        '  was removed.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-reporter_accessible)s,\n'
-        '  %(column-bugs-assignee_accessible)s,\n'
-        '  %(column-bugs-qacontact_accessible)s,\n'
-        '  and %(column-bugs-cclist_accessible)s\n'
-        '  were added.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-version)s changed from\n'
-        '  varchar(16) to varchar(64).</li>\n'
-        '\n'
-        '  <li>%(column-bugs_activity-oldvalue)s and\n'
-        '  %(column-bugs_activity-newvalue)s\n'
-        '  were replaced by %(column-bugs_activity-removed)s and\n'
-        '  %(column-bugs_activity-added)s.</li>\n'
-        '\n'
-        '  <li>%(column-groups-isactive)s was\n'
-        '  added.</li>\n'
-        '\n'
-        '  <li>%(column-longdescs-who)s became an\n'
-        '  index field.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-disabledtext)s\n'
-        '  changed back to "not null".</li>\n'
-        '\n'
-        '</ul>',
+        (
+            '<p>In Bugzilla release 2.14, the following schema changes were\n'
+            'made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '  <li>%(the-table-tokens)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-password)s was\n'
+            '  removed.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-cryptpassword)s and\n'
+            '  %(column-logincookies-cryptpassword)s\n'
+            '  were both changed from varchar(64) to varchar(32).</li>\n'
+            '\n'
+            '  <li>%(column-profiles-newemailtech)s was\n'
+            '  removed.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-emailnotification)s\n'
+            '  was removed.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-reporter_accessible)s,\n'
+            '  %(column-bugs-assignee_accessible)s,\n'
+            '  %(column-bugs-qacontact_accessible)s,\n'
+            '  and %(column-bugs-cclist_accessible)s\n'
+            '  were added.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-version)s changed from\n'
+            '  varchar(16) to varchar(64).</li>\n'
+            '\n'
+            '  <li>%(column-bugs_activity-oldvalue)s and\n'
+            '  %(column-bugs_activity-newvalue)s\n'
+            '  were replaced by %(column-bugs_activity-removed)s and\n'
+            '  %(column-bugs_activity-added)s.</li>\n'
+            '\n'
+            '  <li>%(column-groups-isactive)s was\n'
+            '  added.</li>\n'
+            '\n'
+            '  <li>%(column-longdescs-who)s became an\n'
+            '  index field.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-disabledtext)s\n'
+            '  changed back to "not null".</li>\n'
+            '\n'
+            '</ul>'
+        ),
     ),
     (
         '2.14',
@@ -5630,197 +5697,179 @@
     (
         '2.14.2',
         '2.14.2',
-        '<p>In Bugzilla release 2.14.2, the following schema change was\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-logincookies-hostname)s was\n'
-        '  replaced by %(column-logincookies-ipaddr)s.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.14.2, the following schema change was\n'
+            'made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-logincookies-hostname)s was\n'
+            '  replaced by %(column-logincookies-ipaddr)s.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '2.14.2',
         '2.14.5',
-        '<p>The schema is identical in Bugzilla releases 2.14.2, 2.14.3,\n'
-        '2.14.4, and 2.14.5.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 2.14.2, 2.14.3,\n'
+            '2.14.4, and 2.14.5.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.16rc1',
         '2.16',
-        '<p>In Bugzilla release 2.16 (and the release candidates 2.16rc1 and\n'
-        '2.16rc2), the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>The %(table-attachstatuses)s and %(table-attachstatusdefs)s\n'
-        '  tables were added.</li>\n'
-        '\n'
-        '  <li>%(column-attachments-isobsolete)s was\n'
-        '  added.</li>\n'
-        '\n'
-        '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-assignee_accessible)s\n'
-        '  and %(column-bugs-qacontact_accessible)s\n'
-        '  were removed.</li>\n'
-        '\n'
-        '  <li>%(column-bugs_activity-attach_id)s\n'
-        '  was added.</li>\n'
-        '\n'
-        '  <li>%(column-logincookies-cryptpassword)s\n'
-        '  was removed.</li>\n'
-        '\n'
-        '  <li>The possible values of %(column-tokens-tokentype)s changed, to\n'
-        '  include \'emailold\' and \'emailnew\' (used when changing the email\n'
-        '  address of a Bugzilla user).</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.16 (and the release candidates 2.16rc1 and\n'
+            '2.16rc2), the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>The %(table-attachstatuses)s and %(table-attachstatusdefs)s\n'
+            '  tables were added.</li>\n'
+            '\n'
+            '  <li>%(column-attachments-isobsolete)s was\n'
+            '  added.</li>\n'
+            '\n'
+            '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-assignee_accessible)s\n'
+            '  and %(column-bugs-qacontact_accessible)s\n'
+            '  were removed.</li>\n'
+            '\n'
+            '  <li>%(column-bugs_activity-attach_id)s\n'
+            '  was added.</li>\n'
+            '\n'
+            '  <li>%(column-logincookies-cryptpassword)s\n'
+            '  was removed.</li>\n'
+            '\n'
+            '  <li>The possible values of %(column-tokens-tokentype)s changed, to\n'
+            '  include \'emailold\' and \'emailnew\' (used when changing the email\n'
+            '  address of a Bugzilla user).</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '2.16rc1',
         '2.16.11',
-        '<p>The schema is identical in Bugzilla releases 2.16rc1, 2.16rc2,\n'
-        '2.16, 2.16.1, 2.16.2, 2.16.3, 2.16.4, 2.16.5, 2.16.6, 2.16.7, 2.16.8, '
-        '2.16.9, 2.16.10, and 2.16.11.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 2.16rc1, 2.16rc2,\n'
+            '2.16, 2.16.1, 2.16.2, 2.16.3, 2.16.4, 2.16.5, 2.16.6, 2.16.7, 2.16.8, '
+            '2.16.9, 2.16.10, and 2.16.11.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.17.1',
         '2.17.1',
-        '<p>In Bugzilla release 2.17.1, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li><p>The groups system was radically changed.  This included the\n'
-        '  following detailed schema changes:</p>\n'
-        '    <ul>\n'
-        '\n'
-        '      <li>The %(table-bug_group_map)s, %(table-user_group_map)s,\n'
-        '      and %(table-group_group_map)s tables were added.</li>\n'
-        '\n'
-        '      <li>%(column-groups-bit)s was replaced\n'
-        '      with %(column-groups-id)s.</li>\n'
-        '\n'
-        '      <li>%(column-groups-last_changed)s was\n'
-        '      added.</li>\n'
-        '\n'
-        '      <li>%(column-bugs-groupset)s, %(column-profiles-groupset)s and '
-        '%(column-profiles-blessgroupset)s\n'
-        '      were dropped.</li> </ul> </li>\n'
-        '\n'
-        '  <li>A new <a href="#notes-flags">flags</a> system was introduced,\n'
-        '  adding the tables %(table-flags)s, %(table-flagtypes)s,\n'
-        '  %(table-flaginclusions)s, and %(table-flagexclusions)s.  This allows\n'
-        '  status flags to be defined and used on both attachments and bugs.\n'
-        '  This replaces the "attachment statuses" feature, so the\n'
-        '  %(table-attachstatuses)s and %(table-attachstatusdefs)s tables were\n'
-        '  removed.</li>\n'
-        '\n'
-        '  <li>%(the-table-quips)s was added.</li>\n'
-        '\n'
-        '  <li>Products got IDs in addition to names, and product name columns\n'
-        '  were replaced with product ID columns: %(column-bugs-product)s was\n'
-        '  replaced with %(column-bugs-product_id)s,\n'
-        '  %(column-components-program)s was replaced with\n'
-        '  %(column-components-product_id)s, %(column-milestones-product)s was\n'
-        '  replaced with %(column-milestones-product_id)s,\n'
-        '  %(column-versions-program)s was replaced with\n'
-        '  %(column-versions-product_id)s, and %(column-products-product)s was\n'
-        '  replaced with %(column-products-id)s and\n'
-        '  %(column-products-name)s.</li>\n'
-        '\n'
-        '  <li>Components got IDs in addition to names, and the component name\n'
-        '  column was replaced with a component ID column: %(column-bugs-component)s '
-        'was replaced with\n'
-        '  %(column-bugs-component_id)s, and %(column-components-value)s was '
-        'replaced\n'
-        '  with %(column-components-id)s and %(column-components-name)s.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-estimated_time)s, %(column-bugs-remaining_time)s, and '
-        '%(column-longdescs-work_time)s were\n'
-        '  added.</li>\n'
-        '\n'
-        '  <li>%(column-attachments-isprivate)s and\n'
-        '  %(column-longdescs-isprivate)s were\n'
-        '  added.</li>\n'
-        '\n'
-        '  <li>%(column-attachments-creation_ts)s\n'
-        '  changed from a timestamp to a datetime, default \'0000-00-00\n'
-        '  00:00:00\'.</li>\n'
-        '\n'
-        '  <li>%(column-attachments-filename)s\n'
-        '  changed from a mediumtext to a varchar(100).</li>\n'
-        '\n'
-        '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-alias)s was added.</li>\n'
-        '\n'
-        '  <li>The unused column %(column-namedqueries-watchfordiffs)s\n'
-        '  was removed.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-refreshed_when)s\n'
-        '  was added.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.17.1, the following schema changes'
+            ' were\nmade:</p>\n\n<ul>\n\n  <li><p>The groups system was radically'
+            ' changed.  This included the\n  following detailed schema changes:</p>\n  '
+            '  <ul>\n\n      <li>The %(table-bug_group_map)s,'
+            ' %(table-user_group_map)s,\n      and %(table-group_group_map)s tables'
+            ' were added.</li>\n\n      <li>%(column-groups-bit)s was replaced\n     '
+            ' with %(column-groups-id)s.</li>\n\n     '
+            ' <li>%(column-groups-last_changed)s was\n      added.</li>\n\n     '
+            ' <li>%(column-bugs-groupset)s, %(column-profiles-groupset)s and'
+            ' %(column-profiles-blessgroupset)s\n      were dropped.</li> </ul>'
+            ' </li>\n\n  <li>A new <a href="#notes-flags">flags</a> system was'
+            ' introduced,\n  adding the tables %(table-flags)s, %(table-flagtypes)s,\n '
+            ' %(table-flaginclusions)s, and %(table-flagexclusions)s.  This allows\n '
+            ' status flags to be defined and used on both attachments and bugs.\n  This'
+            ' replaces the "attachment statuses" feature, so the\n '
+            ' %(table-attachstatuses)s and %(table-attachstatusdefs)s tables were\n '
+            ' removed.</li>\n\n  <li>%(the-table-quips)s was added.</li>\n\n '
+            ' <li>Products got IDs in addition to names, and product name columns\n '
+            ' were replaced with product ID columns: %(column-bugs-product)s was\n '
+            ' replaced with %(column-bugs-product_id)s,\n '
+            ' %(column-components-program)s was replaced with\n '
+            ' %(column-components-product_id)s, %(column-milestones-product)s was\n '
+            ' replaced with %(column-milestones-product_id)s,\n '
+            ' %(column-versions-program)s was replaced with\n '
+            ' %(column-versions-product_id)s, and %(column-products-product)s was\n '
+            ' replaced with %(column-products-id)s and\n '
+            ' %(column-products-name)s.</li>\n\n  <li>Components got IDs in addition to'
+            ' names, and the component name\n  column was replaced with a component ID'
+            ' column: %(column-bugs-component)s was replaced with\n '
+            ' %(column-bugs-component_id)s, and %(column-components-value)s was'
+            ' replaced\n  with %(column-components-id)s and'
+            ' %(column-components-name)s.</li>\n\n  <li>%(column-bugs-estimated_time)s,'
+            ' %(column-bugs-remaining_time)s, and %(column-longdescs-work_time)s were\n'
+            '  added.</li>\n\n  <li>%(column-attachments-isprivate)s and\n '
+            ' %(column-longdescs-isprivate)s were\n  added.</li>\n\n '
+            ' <li>%(column-attachments-creation_ts)s\n  changed from a timestamp to a'
+            ' datetime, default \'0000-00-00\n  00:00:00\'.</li>\n\n '
+            ' <li>%(column-attachments-filename)s\n  changed from a mediumtext to a'
+            ' varchar(100).</li>\n\n  <li>The values permitted in'
+            ' %(column-bugs-op_sys)s changed.</li>\n\n  <li>%(column-bugs-alias)s was'
+            ' added.</li>\n\n  <li>The unused column'
+            ' %(column-namedqueries-watchfordiffs)s\n  was removed.</li>\n\n '
+            ' <li>%(column-profiles-refreshed_when)s\n  was added.</li>\n\n</ul>\n\n'
+        ),
     ),
     (
         '2.17.3',
         '2.17.3',
-        '<p>In Bugzilla release 2.17.3, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(the-table-group_control_map)s was added.</li>\n'
-        '\n'
-        '  <li>%(the-table-shadowlog)s was removed.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.17.3, the following schema changes were\n'
+            'made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(the-table-group_control_map)s was added.</li>\n'
+            '\n'
+            '  <li>%(the-table-shadowlog)s was removed.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '2.17.4',
         '2.17.4',
-        '<p>In Bugzilla release 2.17.4, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-quips-approved)s was added.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.17.4, the following schema changes were\n'
+            'made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-quips-approved)s was added.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '2.17.5',
         '2.17.5',
-        '<p>In Bugzilla release 2.17.5, the following schema changes were\n'
-        'made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>The %(table-series)s, %(table-series_categories)s,\n'
-        '  %(table-series_data)s, and %(table-user_series_map)s tables were\n'
-        '  added, to support <a href="#notes-charts">the new charts\n'
-        '  system</a>.</li>\n'
-        '\n'
-        '  <li>%(column-votes-count)s was renamed as '
-        '%(column-votes-vote_count)s.</li>\n'
-        '\n'
-        '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-short_desc)s and %(column-longdescs-thetext)s became\n'
-        '  fulltext index fields, allowing quicker full text searching.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla release 2.17.5, the following schema changes were\n'
+            'made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>The %(table-series)s, %(table-series_categories)s,\n'
+            '  %(table-series_data)s, and %(table-user_series_map)s tables were\n'
+            '  added, to support <a href="#notes-charts">the new charts\n'
+            '  system</a>.</li>\n'
+            '\n'
+            '  <li>%(column-votes-count)s was renamed as '
+            '%(column-votes-vote_count)s.</li>\n'
+            '\n'
+            '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-short_desc)s and %(column-longdescs-thetext)s became\n'
+            '  fulltext index fields, allowing quicker full text searching.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '2.17.5',
@@ -5830,30 +5879,34 @@
     (
         '2.17.7',
         '2.17.7',
-        '<p>In Bugzilla 2.17.7, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-bugs-short_desc)s\n'
-        '  changed to "not null".</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.17.7, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-bugs-short_desc)s\n'
+            '  changed to "not null".</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.18rc1',
         '2.18rc1',
-        '<p>In Bugzilla 2.18rc1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
-        '\n'
-        '  <li>%(column-user_group_map-isderived)s\n'
-        '  was replaced by %(column-user_group_map-grant_type)s.</li>\n'
-        '  <li>%(column-flags-is_active)s was\n'
-        '  added.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.18rc1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>The values permitted in %(column-bugs-op_sys)s changed.</li>\n'
+            '\n'
+            '  <li>%(column-user_group_map-isderived)s\n'
+            '  was replaced by %(column-user_group_map-grant_type)s.</li>\n'
+            '  <li>%(column-flags-is_active)s was\n'
+            '  added.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.18rc2',
@@ -5863,24 +5916,26 @@
     (
         '2.18rc3',
         '2.18rc3',
-        '<p>In Bugzilla 2.18rc3, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(the-table-user_series_map)s was removed, replaced in part by\n'
-        '  %(column-series-public)s.</li>\n'
-        '\n'
-        '  <li>%(the-table-category_group_map)s was added, providing\n'
-        '  group-level access control for time-series charts.</li>\n'
-        '\n'
-        '  <li>%(column-series_categories-category_id)s was renamed as\n'
-        '  %(column-series_categories-id)s.</li>\n'
-        '\n'
-        '  <li>%(column-series_data-date)s was renamed as\n'
-        '  %(column-series_data-series_date)s, and %(column-series_data-value)s\n'
-        '  was renamed as %(column-series_data-series_value)s.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.18rc3, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(the-table-user_series_map)s was removed, replaced in part by\n'
+            '  %(column-series-public)s.</li>\n'
+            '\n'
+            '  <li>%(the-table-category_group_map)s was added, providing\n'
+            '  group-level access control for time-series charts.</li>\n'
+            '\n'
+            '  <li>%(column-series_categories-category_id)s was renamed as\n'
+            '  %(column-series_categories-id)s.</li>\n'
+            '\n'
+            '  <li>%(column-series_data-date)s was renamed as\n'
+            '  %(column-series_data-series_date)s, and %(column-series_data-value)s\n'
+            '  was renamed as %(column-series_data-series_value)s.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.18',
@@ -5890,458 +5945,470 @@
     (
         '2.18.1',
         '2.18.1',
-        '<p>In Bugzilla 2.18.1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-fielddefs-obsolete)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-quips-userid)s was changed from "not null default 0" to '
-        '"null".</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.18.1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-fielddefs-obsolete)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-quips-userid)s was changed from "not null default 0" to '
+            '"null".</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.18.2',
         '2.18.2',
-        '<p>In Bugzilla 2.18.2, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-bugs-creation_ts)s changed from "not null" to "null".</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.18.2, the following schema changes were'
+            ' made:</p>\n\n<ul>\n\n  <li>%(column-bugs-creation_ts)s changed from "not'
+            ' null" to "null".</li>\n\n</ul>\n'
+        ),
     ),
     (
         '2.18.3',
         '2.18.6',
-        '<p>The schema is identical in Bugzilla releases 2.18.2, 2.18.3, 2.18.4, '
-        '2.18.5, and 2.18.6.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 2.18.2, 2.18.3, 2.18.4, '
+            '2.18.5, and 2.18.6.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.19.1',
         '2.19.1',
-        '<p>In Bugzilla 2.19.1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>Because 2.19.1 predates the 2.18 series of releases, the changes\n'
-        '  made in that series are "undone" in 2.19.1 (and redone later in the\n'
-        '  2.19/2.20rc series):\n'
-        '\n'
-        '    <ul>\n'
-        '      <li>%(column-fielddefs-obsolete)s was removed again.</li>\n'
-        '\n'
-        '      <li>%(column-bugs-creation_ts)s returned to being "not null".</li>\n'
-        '\n'
-        '      <li>%(column-quips-userid)s returned to being "not null default '
-        '0".</li>\n'
-        '    </ul>\n'
-        '\n'
-        '  <li>%(the-table-classifications)s and\n'
-        '  %(column-products-classification_id)s were added, to support <a\n'
-        '  href="#notes-products">the new classifications\n'
-        '  system</a>.</li>\n'
-        '\n'
-        '  <li>%(column-group_group_map-isbless)s was replaced by\n'
-        '  %(column-group_group_map-grant_type)s.</li>\n'
-        '\n'
-        '  <li>%(column-logincookies-lastused)s changed from a timestamp to a\n'
-        '  datetime, default \'0000-00-00 00:00:00\'.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-extern_id)s was added.</li>\n'
-        '\n'
-        '  <li>The %(table-whine_events)s, %(table-whine_queries)s, and <a\n'
-        '  %(the-table-whine_schedules)s tables were added, to support <a\n'
-        '  href="#notes-whine">the new whining system</a>.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.19.1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>Because 2.19.1 predates the 2.18 series of releases, the changes\n'
+            '  made in that series are "undone" in 2.19.1 (and redone later in the\n'
+            '  2.19/2.20rc series):\n'
+            '\n'
+            '    <ul>\n'
+            '      <li>%(column-fielddefs-obsolete)s was removed again.</li>\n'
+            '\n'
+            '      <li>%(column-bugs-creation_ts)s returned to being "not null".</li>\n'
+            '\n'
+            '      <li>%(column-quips-userid)s returned to being "not null default '
+            '0".</li>\n'
+            '    </ul>\n'
+            '\n'
+            '  <li>%(the-table-classifications)s and\n'
+            '  %(column-products-classification_id)s were added, to support <a\n'
+            '  href="#notes-products">the new classifications\n'
+            '  system</a>.</li>\n'
+            '\n'
+            '  <li>%(column-group_group_map-isbless)s was replaced by\n'
+            '  %(column-group_group_map-grant_type)s.</li>\n'
+            '\n'
+            '  <li>%(column-logincookies-lastused)s changed from a timestamp to a\n'
+            '  datetime, default \'0000-00-00 00:00:00\'.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-extern_id)s was added.</li>\n'
+            '\n'
+            '  <li>The %(table-whine_events)s, %(table-whine_queries)s, and <a\n'
+            '  %(the-table-whine_schedules)s tables were added, to support <a\n'
+            '  href="#notes-whine">the new whining system</a>.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.19.2',
         '2.19.2',
-        '<p>In Bugzilla 2.19.2, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-flagtypes-grant_group_id)s and\n'
-        '  %(column-flagtypes-request_group_id)s were added.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.19.2, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-flagtypes-grant_group_id)s and\n'
+            '  %(column-flagtypes-request_group_id)s were added.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.19.3',
         '2.19.3',
-        '<p>In Bugzilla 2.19.3, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>All the secondary indexes were given new names, based on the\n'
-        '  names of the table and first indexed column.</li>\n'
-        '\n'
-        '  <li>The %(table-bug_severity)s, %(table-bug_status)s,\n'
-        '  %(table-op_sys)s, %(table-priority)s, %(table-rep_platform)s, and\n'
-        '  %(table-resolution)s tables were added, and the matching bug fields\n'
-        '  (%(column-bugs-bug_severity)s, %(column-bugs-bug_status)s,\n'
-        '  %(column-bugs-op_sys)s, %(column-bugs-priority)s,\n'
-        '  %(column-bugs-rep_platform)s, and %(column-bugs-resolution)s) were\n'
-        '  changed from enumerated types into varchar(64) foreign keys for\n'
-        '  these new tables.  The effect of this is to remove all enumerated\n'
-        '  types from the schema (improving portability to other DBMSes), and\n'
-        '  in principle to allow the administrator to modify the set of\n'
-        '  allowable values.</li>\n'
-        '\n'
-        '  <li>The table %(table-bz_schema)s was added.</li>\n'
-        '\n'
-        '  <li>The tables %(table-profile_setting)s, %(table-setting)s, and\n'
-        '  %(table-setting_value)s were added, for <a\n'
-        '  href="#notes-settings">the settings system</a>.</li>\n'
-        '\n'
-        '  <li>The table %(table-email_setting)s was added, replacing\n'
-        '  %(column-profiles-emailflags)s.</li>\n'
-        '\n'
-        '  <li>The indexes %(index-bugs_activity-bugs_activity_who_idx)s,\n'
-        '  %(index-attachments-attachments_submitter_id_idx)s,\n'
-        '  %(index-versions-versions_product_id_idx)s, and\n'
-        '  %(index-flags-flags_type_id_idx)s were added.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-deadline)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-delta_ts)s changed from a timestamp to a '
-        'datetime.</li>\n'
-        '\n'
-        '  <li>%(column-bugs-lastdiffed)s changed from "not null" to "null".</li>\n'
-        '\n'
-        '  <li>%(column-bugs-qa_contact)s and\n'
-        '  %(column-components-initialqacontact)s changed from "not null" to\n'
-        '  "null".</li>\n'
-        '\n'
-        '  <li>%(column-fielddefs-obsolete)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-longdescs-already_wrapped)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-cryptpassword)s was changed from varchar(34)\n'
-        '  to varchar(128).</li>\n'
-        '\n'
-        '  <li>%(column-quips-approved)s and %(column-series-public)s changed\n'
-        '  from tinyint(1) to tinyint.</li>\n'
-        '\n'
-        '  <li>%(column-versions-value)s changed from "tinytext null" to\n'
-        '  "varchar(64) not null".</li>\n'
-        '\n'
-        '  <li>%(column-whine_schedules-mailto_userid)s was replaced by\n'
-        '  %(column-whine_schedules-mailto)s and\n'
-        '  %(column-whine_schedules-mailto_type)s.</li>\n'
-        '\n'
-        '  <li>The index %(index-series-creator)s was removed.</li>\n'
-        '\n'
-        '  <li>%(column-quips-userid)s was changed from "not null default 0" to '
-        '"null".</li>\n'
-        '\n'
-        '  </ul>\n',
+        (
+            '<p>In Bugzilla 2.19.3, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>All the secondary indexes were given new names, based on the\n'
+            '  names of the table and first indexed column.</li>\n'
+            '\n'
+            '  <li>The %(table-bug_severity)s, %(table-bug_status)s,\n'
+            '  %(table-op_sys)s, %(table-priority)s, %(table-rep_platform)s, and\n'
+            '  %(table-resolution)s tables were added, and the matching bug fields\n'
+            '  (%(column-bugs-bug_severity)s, %(column-bugs-bug_status)s,\n'
+            '  %(column-bugs-op_sys)s, %(column-bugs-priority)s,\n'
+            '  %(column-bugs-rep_platform)s, and %(column-bugs-resolution)s) were\n'
+            '  changed from enumerated types into varchar(64) foreign keys for\n'
+            '  these new tables.  The effect of this is to remove all enumerated\n'
+            '  types from the schema (improving portability to other DBMSes), and\n'
+            '  in principle to allow the administrator to modify the set of\n'
+            '  allowable values.</li>\n'
+            '\n'
+            '  <li>The table %(table-bz_schema)s was added.</li>\n'
+            '\n'
+            '  <li>The tables %(table-profile_setting)s, %(table-setting)s, and\n'
+            '  %(table-setting_value)s were added, for <a\n'
+            '  href="#notes-settings">the settings system</a>.</li>\n'
+            '\n'
+            '  <li>The table %(table-email_setting)s was added, replacing\n'
+            '  %(column-profiles-emailflags)s.</li>\n'
+            '\n'
+            '  <li>The indexes %(index-bugs_activity-bugs_activity_who_idx)s,\n'
+            '  %(index-attachments-attachments_submitter_id_idx)s,\n'
+            '  %(index-versions-versions_product_id_idx)s, and\n'
+            '  %(index-flags-flags_type_id_idx)s were added.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-deadline)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-delta_ts)s changed from a timestamp to a '
+            'datetime.</li>\n'
+            '\n'
+            '  <li>%(column-bugs-lastdiffed)s changed from "not null" to "null".</li>\n'
+            '\n'
+            '  <li>%(column-bugs-qa_contact)s and\n'
+            '  %(column-components-initialqacontact)s changed from "not null" to\n'
+            '  "null".</li>\n'
+            '\n'
+            '  <li>%(column-fielddefs-obsolete)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-longdescs-already_wrapped)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-profiles-cryptpassword)s was changed from varchar(34)\n'
+            '  to varchar(128).</li>\n'
+            '\n'
+            '  <li>%(column-quips-approved)s and %(column-series-public)s changed\n'
+            '  from tinyint(1) to tinyint.</li>\n'
+            '\n'
+            '  <li>%(column-versions-value)s changed from "tinytext null" to\n'
+            '  "varchar(64) not null".</li>\n'
+            '\n'
+            '  <li>%(column-whine_schedules-mailto_userid)s was replaced by\n'
+            '  %(column-whine_schedules-mailto)s and\n'
+            '  %(column-whine_schedules-mailto_type)s.</li>\n'
+            '\n'
+            '  <li>The index %(index-series-creator)s was removed.</li>\n'
+            '\n'
+            '  <li>%(column-quips-userid)s was changed from "not null default 0" to '
+            '"null".</li>\n'
+            '\n'
+            '  </ul>\n'
+        ),
     ),
     (
         '2.20rc1',
         '2.20rc1',
-        '<p>In Bugzilla 2.20rc1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-bugs-creation_ts)s changed from "not null" to "null".</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.20rc1, the following schema changes were'
+            ' made:</p>\n\n<ul>\n\n  <li>%(column-bugs-creation_ts)s changed from "not'
+            ' null" to "null".</li>\n\n</ul>\n'
+        ),
     ),
     (
         '2.20rc2',
         '2.20rc2',
-        '<p>In Bugzilla 2.20rc2, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-attachments-bug_id)s was added to the index '
-        '%(index-attachments-attachments_submitter_id_idx)s.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.20rc2, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-attachments-bug_id)s was added to the index '
+            '%(index-attachments-attachments_submitter_id_idx)s.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.20',
         '2.20.7',
-        '<p>The schema is identical in Bugzilla releases 2.20rc2, 2.20, 2.20.1, '
-        '2.20.2, 2.20.3, 2.20.4, 2.20.5, 2.20.6, and 2.20.7.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 2.20rc2, 2.20, 2.20.1, '
+            '2.20.2, 2.20.3, 2.20.4, 2.20.5, 2.20.6, and 2.20.7.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.21.1',
         '2.21.1',
-        '<p>In Bugzilla 2.21.1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>The table %(table-attach_data)s was added, replacing\n'
-        '  %(column-attachments-thedata)s.  This makes SQL queries on '
-        '%(the-table-attachments)s go faster.</li>\n'
-        '\n'
-        '  <li>%(column-series-public)s was renamed %(column-series-is_public)s '
-        '("public" is a keyword in Oracle).</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.21.1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>The table %(table-attach_data)s was added, replacing\n'
+            '  %(column-attachments-thedata)s.  This makes SQL queries on '
+            '%(the-table-attachments)s go faster.</li>\n'
+            '\n'
+            '  <li>%(column-series-public)s was renamed %(column-series-is_public)s '
+            '("public" is a keyword in Oracle).</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.22rc1',
         '2.22rc1',
-        '<p>In Bugzilla 2.22rc1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-attachments-isurl)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-namedqueries-query_type)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-logincookies-cookie)s was changed from "mediumint\n'
-        '  auto_increment" to "varchar(16)", to hold a randomly-generated\n'
-        '  (and therefore harder-to-guess) cookie.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.22rc1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-attachments-isurl)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-namedqueries-query_type)s was added.</li>\n'
+            '\n'
+            '  <li>%(column-logincookies-cookie)s was changed from "mediumint\n'
+            '  auto_increment" to "varchar(16)", to hold a randomly-generated\n'
+            '  (and therefore harder-to-guess) cookie.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.22',
         '2.22.7',
-        '<p>The schema is identical in Bugzilla releases 2.22rc1, 2.22, 2.22.1, '
-        '2.22.2, 2.22.3, 2.22.4, 2.22.5, 2.22.6, and 2.22.7.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 2.22rc1, 2.22, 2.22.1, '
+            '2.22.2, 2.22.3, 2.22.4, 2.22.5, 2.22.6, and 2.22.7.</p>\n'
+            '\n'
+        ),
     ),
     (
         '2.23.1',
         '2.23.1',
-        '<p>In Bugzilla 2.23.1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li><b>Custom fields</b> were added: columns named '
-        '<code>cf_&lt;name&gt;</code> in %(the-table-bugs)s.</li>\n'
-        '\n'
-        '  <li>%(column-fielddefs-custom)s and %(column-fielddefs-type)s were '
-        'added.</li>\n'
-        '\n'
-        '  <li>%(column-flags-id)s became "auto_increment" and\n'
-        '  %(column-flags-is_active)s was removed.  Dead flags are now removed\n'
-        '  from the database instead of being marked inactive.</li>\n'
-        '\n'
-        '  <li>%(column-longdescs-comment_id)s was added, as a primary key on '
-        '%(table-longdescs)s.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.23.1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li><b>Custom fields</b> were added: columns named '
+            '<code>cf_&lt;name&gt;</code> in %(the-table-bugs)s.</li>\n'
+            '\n'
+            '  <li>%(column-fielddefs-custom)s and %(column-fielddefs-type)s were '
+            'added.</li>\n'
+            '\n'
+            '  <li>%(column-flags-id)s became "auto_increment" and\n'
+            '  %(column-flags-is_active)s was removed.  Dead flags are now removed\n'
+            '  from the database instead of being marked inactive.</li>\n'
+            '\n'
+            '  <li>%(column-longdescs-comment_id)s was added, as a primary key on '
+            '%(table-longdescs)s.</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.23.2',
         '2.23.2',
-        '<p>In Bugzilla 2.23.2, the following schema change was made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>%(column-bugs-short_desc)s changed from mediumtext to '
-        'varchar(255).</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.23.2, the following schema change was made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '  <li>%(column-bugs-short_desc)s changed from mediumtext to '
+            'varchar(255).</li>\n'
+            '\n'
+            '</ul>\n'
+        ),
     ),
     (
         '2.23.3',
         '2.23.3',
-        '<p>In Bugzilla 2.23.3, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '  <li>Single-select <b>custom fields</b> were added; allowable values\n'
-        '  are stored in tables named <code>cf_&lt;name&gt;</code>.</li>\n'
-        '\n'
-        '  <li>Shared named queries were added, by adding tables\n'
-        '  %(table-namedqueries_link_in_footer)s and\n'
-        '  %(table-namedquery_group_map)s, column %(column-namedqueries-id)s,\n'
-        '  and index %(index-namedqueries-PRIMARY)s, and removing\n'
-        '  %(column-namedqueries-linkinfooter)s.</li>\n'
-        '\n'
-        '  <li>%(the-table-component_cc)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-classifications-sortkey)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-setting-subclass)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-fielddefs-enter_bug)s was added.</li>\n'
-        '\n'
-        '  <li>%(column-fielddefs-fieldid)s was renamed to '
-        '%(column-fielddefs-id)s.</li>\n'
-        '\n'
-        '  <li>%(column-flagtypes-id)s and %(column-keyworddefs-id)s became '
-        '"auto_increment".</li>\n'
-        '\n'
-        '  <li>%(column-longdescs-thetext)s became "not null".</li>\n'
-        '\n'
-        '  <li>%(column-longdescs-bug_id)s was added to the index '
-        '%(index-longdescs-longdescs_who_idx)s.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-realname)s became "not null".</li>\n'
-        '\n'
-        '  <li>%(column-series-creator)s changed from "not null" to "null".</li>\n'
-        '\n'
-        '  <li>%(column-tokens-userid)s changed from "not null" to "null".</li>\n'
-        '\n'
-        '  <li>%(column-profiles-disable_mail)s was added.</li>\n'
-        '\n'
-        '  <li>The index %(index-bugs-bugs_short_desc_idx)s was removed.</li>\n'
-        '\n'
-        '  <li>%(column-profiles-refreshed_when)s and %(column-groups-last_changed)s '
-        'were removed.</li>\n'
-        '\n'
-        '</ul>',
+        (
+            '<p>In Bugzilla 2.23.3, the following schema changes were'
+            ' made:</p>\n\n<ul>\n\n  <li>Single-select <b>custom fields</b> were added;'
+            ' allowable values\n  are stored in tables named'
+            ' <code>cf_&lt;name&gt;</code>.</li>\n\n  <li>Shared named queries were'
+            ' added, by adding tables\n  %(table-namedqueries_link_in_footer)s and\n '
+            ' %(table-namedquery_group_map)s, column %(column-namedqueries-id)s,\n  and'
+            ' index %(index-namedqueries-PRIMARY)s, and removing\n '
+            ' %(column-namedqueries-linkinfooter)s.</li>\n\n '
+            ' <li>%(the-table-component_cc)s was added.</li>\n\n '
+            ' <li>%(column-classifications-sortkey)s was added.</li>\n\n '
+            ' <li>%(column-setting-subclass)s was added.</li>\n\n '
+            ' <li>%(column-fielddefs-enter_bug)s was added.</li>\n\n '
+            ' <li>%(column-fielddefs-fieldid)s was renamed to'
+            ' %(column-fielddefs-id)s.</li>\n\n  <li>%(column-flagtypes-id)s and'
+            ' %(column-keyworddefs-id)s became "auto_increment".</li>\n\n '
+            ' <li>%(column-longdescs-thetext)s became "not null".</li>\n\n '
+            ' <li>%(column-longdescs-bug_id)s was added to the index'
+            ' %(index-longdescs-longdescs_who_idx)s.</li>\n\n '
+            ' <li>%(column-profiles-realname)s became "not null".</li>\n\n '
+            ' <li>%(column-series-creator)s changed from "not null" to "null".</li>\n\n'
+            '  <li>%(column-tokens-userid)s changed from "not null" to "null".</li>\n\n'
+            '  <li>%(column-profiles-disable_mail)s was added.</li>\n\n  <li>The index'
+            ' %(index-bugs-bugs_short_desc_idx)s was removed.</li>\n\n '
+            ' <li>%(column-profiles-refreshed_when)s and %(column-groups-last_changed)s'
+            ' were removed.</li>\n\n</ul>'
+        ),
     ),
     (
         '2.23.4',
         '2.23.4',
-        '<p>In Bugzilla 2.23.4, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '    <li>%(column-milestones-id)s and %(column-versions-id)s were\n'
-        '    added, as PRIMARY indexes (increasing consistency: no objects are\n'
-        '    now identified solely by user-specified strings, although some\n'
-        '    cross-table references are still by string IDs rather than\n'
-        '    auto-generated integer IDs).</li>\n'
-        '\n'
-        '    <li>%(column-group_control_map-canconfirm)s, '
-        '%(column-group_control_map-editbugs)s, and\n'
-        '    %(column-group_control_map-editcomponents)s were added.</li>\n'
-        '\n'
-        '    <li>%(column-longdescs-type)s and %(column-longdescs-extra_data)s were '
-        'added.</li>\n'
-        '\n'
-        '</ul>\n',
+        (
+            '<p>In Bugzilla 2.23.4, the following schema changes were'
+            ' made:</p>\n\n<ul>\n\n    <li>%(column-milestones-id)s and'
+            ' %(column-versions-id)s were\n    added, as PRIMARY indexes (increasing'
+            ' consistency: no objects are\n    now identified solely by user-specified'
+            ' strings, although some\n    cross-table references are still by string'
+            ' IDs rather than\n    auto-generated integer IDs).</li>\n\n   '
+            ' <li>%(column-group_control_map-canconfirm)s,'
+            ' %(column-group_control_map-editbugs)s, and\n   '
+            ' %(column-group_control_map-editcomponents)s were added.</li>\n\n   '
+            ' <li>%(column-longdescs-type)s and %(column-longdescs-extra_data)s were'
+            ' added.</li>\n\n</ul>\n'
+        ),
     ),
     (
         '3.0rc1',
         '3.0.9',
-        '<p>The schema is identical in Bugzilla releases 2.23.4, 3.0rc1, 3.0, 3.0.1, '
-        '3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.8, and 3.0.9.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 2.23.4, 3.0rc1, 3.0,'
+            ' 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.8, and'
+            ' 3.0.9.</p>\n\n'
+        ),
     ),
     (
         '3.1.1',
         '3.1.1',
-        '<p>In Bugzilla 3.1.1, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '<li>%(the-table-status_workflow)s, and %(column-bug_status-is_open)s,\n'
-        'were added, to provide configurable <a '
-        'href="#notes-workflow">workflow</a>.</li>\n'
-        '\n'
-        '<li>%(column-groups-icon_url)s was added.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla 3.1.1, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '<li>%(the-table-status_workflow)s, and %(column-bug_status-is_open)s,\n'
+            'were added, to provide configurable <a '
+            'href="#notes-workflow">workflow</a>.</li>\n'
+            '\n'
+            '<li>%(column-groups-icon_url)s was added.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '3.1.2',
         '3.1.2',
-        '<p>In Bugzilla 3.1.2, the following schema change was made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '<li>Multi-select custom fields were added, with <a '
-        'href="#table-multiselect">this schema</a>;</li>\n'
-        '\n'
-        '<li>Large text box custom fields were added.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla 3.1.2, the following schema change was made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '<li>Multi-select custom fields were added, with <a '
+            'href="#table-multiselect">this schema</a>;</li>\n'
+            '\n'
+            '<li>Large text box custom fields were added.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '3.1.3',
         '3.1.3',
-        '<p>In Bugzilla 3.1.3, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '<li>%(column-attachments-modification_time)s and\n'
-        '%(index-attachments-attachments_modification_time_idx)s were\n'
-        'added.</li>\n'
-        '\n'
-        '<li>Date/time custom fields were added;</li>\n'
-        '\n'
-        '<li>These fields changed from mediumtext to tinytext:\n'
-        '%(column-attachments-description)s, %(column-attachments-mimetype)s,\n'
-        '%(column-fielddefs-description)s.</li>\n'
-        '\n'
-        '<li>These fields changed from text to mediumtext:\n'
-        '%(column-bugs-bug_file_loc)s, %(column-flagtypes-description)s,\n'
-        '%(column-groups-description)s, and %(column-quips-quip)s.</li>\n'
-        '\n'
-        '<li>%(column-flagtypes-description)s became "not null".</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla 3.1.3, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '<li>%(column-attachments-modification_time)s and\n'
+            '%(index-attachments-attachments_modification_time_idx)s were\n'
+            'added.</li>\n'
+            '\n'
+            '<li>Date/time custom fields were added;</li>\n'
+            '\n'
+            '<li>These fields changed from mediumtext to tinytext:\n'
+            '%(column-attachments-description)s, %(column-attachments-mimetype)s,\n'
+            '%(column-fielddefs-description)s.</li>\n'
+            '\n'
+            '<li>These fields changed from text to mediumtext:\n'
+            '%(column-bugs-bug_file_loc)s, %(column-flagtypes-description)s,\n'
+            '%(column-groups-description)s, and %(column-quips-quip)s.</li>\n'
+            '\n'
+            '<li>%(column-flagtypes-description)s became "not null".</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '3.1.4',
         '3.1.4',
-        '<p>In Bugzilla 3.1.4, the following schema change was made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '<li>%(the-table-bugs_fulltext)s was added and the index\n'
-        '%(index-longdescs-longdescs_thetext_idx)s was removed, improving the\n'
-        'performance of full-text searching.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla 3.1.4, the following schema change was made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '<li>%(the-table-bugs_fulltext)s was added and the index\n'
+            '%(index-longdescs-longdescs_thetext_idx)s was removed, improving the\n'
+            'performance of full-text searching.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '3.2rc1',
         '3.2.5',
-        '<p>The schema is identical in Bugzilla releases 3.1.4, 3.2rc1, 3.2rc2, 3.2, '
-        '3.2.1, 3.2.2, 3.2.3, 3.2.4, and 3.2.5.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 3.1.4, 3.2rc1, 3.2rc2,'
+            ' 3.2, 3.2.1, 3.2.2, 3.2.3, 3.2.4, and 3.2.5.</p>\n\n'
+        ),
     ),
     (
         '3.3.1',
         '3.3.1',
-        '<p>In Bugzilla 3.3.1, the following schema changes were'
-        ' made:</p>\n\n<ul>\n\n<li>Bug ID custom fields were added;</li>\n\n<li>The'
-        ' tables %(table-ts_error)s, %(table-ts_exitstatus)s,\n%(table-ts_funcmap)s,'
-        ' %(table-ts_job)s, and %(table-ts_note)s were\nadded.  These tables are'
-        ' created and used by TheSchwartz job queue\nsystem, to run the background'
-        ' email sending system.</li>\n\n<li>%(column-fielddefs-visibility_field_id)s'
-        ' and\n%(column-fielddefs-visibility_value_id)s were added, to allow the'
-        ' display of\neach custom field to depend on the value of a select'
-        ' field.</li>\n\n<li>%(column-fielddefs-value_field_id)s,'
-        ' %(column-bug_severity-visibility_value_id)s,\n%(column-bug_status-visibility_value_id)s,'
-        ' %(column-op_sys-visibility_value_id)s,\n%(column-priority-visibility_value_id)s,'
-        ' %(column-rep_platform-visibility_value_id)s,\n%(column-resolution-visibility_value_id)s,'
-        ' and <a'
-        ' href="#column-customfield-visibility_value_id">\ncf_&lt;field&gt;.visibility_value_id</a>'
-        ' were added, to\nallow the availability of individual values of single-select'
-        ' and multi-select fields to depend on the value of another select'
-        ' field.</li>\n\n<li>New indexes'
-        ' %(index-fielddefs-fielddefs_value_field_id_idx)s,\n%(index-bug_severity-bug_severity_visibility_value_id_idx)s,\n%(index-bug_status-bug_status_visibility_value_id_idx)s,\n%(index-op_sys-op_sys_visibility_value_id_idx)s,\n%(index-priority-priority_visibility_value_id_idx)s,\n%(index-rep_platform-rep_platform_visibility_value_id_idx)s,\n%(index-resolution-resolution_visibility_value_id_idx)s,'
-        ' and\n<a'
-        ' href="#index-customfield-cf_field_visibility_value_id">cf_&lt;field&gt;:cf_&lt;field&gt;_visibility_value_id_idx</a>'
-        ' were added, to support use of the above new'
-        ' fields.</li>\n<li>%(column-group_control_map-product_id)s changed from'
-        ' mediumint to smallint.</li>\n\n</ul>\n\n',
+        (
+            '<p>In Bugzilla 3.3.1, the following schema changes were'
+            ' made:</p>\n\n<ul>\n\n<li>Bug ID custom fields were added;</li>\n\n<li>The'
+            ' tables %(table-ts_error)s,'
+            ' %(table-ts_exitstatus)s,\n%(table-ts_funcmap)s, %(table-ts_job)s, and'
+            ' %(table-ts_note)s were\nadded.  These tables are created and used by'
+            ' TheSchwartz job queue\nsystem, to run the background email sending'
+            ' system.</li>\n\n<li>%(column-fielddefs-visibility_field_id)s'
+            ' and\n%(column-fielddefs-visibility_value_id)s were added, to allow the'
+            ' display of\neach custom field to depend on the value of a select'
+            ' field.</li>\n\n<li>%(column-fielddefs-value_field_id)s,'
+            ' %(column-bug_severity-visibility_value_id)s,\n%(column-bug_status-visibility_value_id)s,'
+            ' %(column-op_sys-visibility_value_id)s,\n%(column-priority-visibility_value_id)s,'
+            ' %(column-rep_platform-visibility_value_id)s,\n%(column-resolution-visibility_value_id)s,'
+            ' and <a'
+            ' href="#column-customfield-visibility_value_id">\ncf_&lt;field&gt;.visibility_value_id</a>'
+            ' were added, to\nallow the availability of individual values of'
+            ' single-select and multi-select fields to depend on the value of another'
+            ' select field.</li>\n\n<li>New indexes'
+            ' %(index-fielddefs-fielddefs_value_field_id_idx)s,\n%(index-bug_severity-bug_severity_visibility_value_id_idx)s,\n%(index-bug_status-bug_status_visibility_value_id_idx)s,\n%(index-op_sys-op_sys_visibility_value_id_idx)s,\n%(index-priority-priority_visibility_value_id_idx)s,\n%(index-rep_platform-rep_platform_visibility_value_id_idx)s,\n%(index-resolution-resolution_visibility_value_id_idx)s,'
+            ' and\n<a'
+            ' href="#index-customfield-cf_field_visibility_value_id">cf_&lt;field&gt;:cf_&lt;field&gt;_visibility_value_id_idx</a>'
+            ' were added, to support use of the above new'
+            ' fields.</li>\n<li>%(column-group_control_map-product_id)s changed from'
+            ' mediumint to smallint.</li>\n\n</ul>\n\n'
+        ),
     ),
     (
         '3.3.2',
         '3.3.2',
-        '<p>In Bugzilla 3.3.2, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '<li>%(the-table-bug_see_also)s was added.</li>\n'
-        '\n'
-        '<li>%(column-fielddefs-buglist)s was added.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla 3.3.2, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '<li>%(the-table-bug_see_also)s was added.</li>\n'
+            '\n'
+            '<li>%(column-fielddefs-buglist)s was added.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '3.3.3',
@@ -6351,508 +6418,521 @@
     (
         '3.3.4',
         '3.3.4',
-        '<p>In Bugzilla 3.3.4, the following schema changes were made:</p>\n'
-        '\n'
-        '<ul>\n'
-        '\n'
-        '<li>The index %(index-profiles-profiles_extern_id_idx)s was added.</li>\n'
-        '\n'
-        '</ul>\n'
-        '\n',
+        (
+            '<p>In Bugzilla 3.3.4, the following schema changes were made:</p>\n'
+            '\n'
+            '<ul>\n'
+            '\n'
+            '<li>The index %(index-profiles-profiles_extern_id_idx)s was added.</li>\n'
+            '\n'
+            '</ul>\n'
+            '\n'
+        ),
     ),
     (
         '3.4rc1',
         '3.4.1',
-        '<p>The schema is identical in Bugzilla releases 3.3.4, 3.4rc1, 3.4, 3.4.1, '
-        'and 3.4.2.</p>\n'
-        '\n',
+        (
+            '<p>The schema is identical in Bugzilla releases 3.3.4, 3.4rc1, 3.4, 3.4.1,'
+            ' and 3.4.2.</p>\n\n'
+        ),
     ),
-    '<h2><a id="section-5" name="section-5">5. Example queries</a></h2>\n'
-    '\n'
-    '<p>To select bug number <em>n</em>:</p>\n'
-    '\n'
-    '<blockquote><code>\n'
-    'select * from bugs where bug_id = <em>n</em>\n'
-    '</code></blockquote>\n'
-    '\n'
-    '<p>To get a complete list of user ids and email addresses:</p>\n'
-    '\n'
-    '<blockquote><code>\n'
-    'select userid, login_name from profiles\n'
-    '</code></blockquote>\n'
-    '\n'
-    '<p>To get the email address of user <em>n</em>:</p>\n'
-    '\n'
-    '<blockquote><code>\n'
-    'select login_name from profiles where userid = <em>n</em>\n'
-    '</code></blockquote>\n'
-    '\n'
-    '<p>To get the set of cc addresses of bug <em>n</em>:</p>\n'
-    '\n'
-    '<blockquote><code>\n'
-    'select login_name from cc, profiles\n'
-    ' where cc.bug_id = <em>n</em>\n'
-    '   and profiles.userid = cc.who\n'
-    '</code></blockquote>\n'
-    '\n',
     (
-        '2.10',
-        None,
-        '<p>%(VERSION_STRING)sTo select the long descriptions\n'
-        'of bug <em>n</em>, together with the name and email address of the\n'
-        'commenters:</p>\n'
+        '<h2><a id="section-5" name="section-5">5. Example queries</a></h2>\n'
+        '\n'
+        '<p>To select bug number <em>n</em>:</p>\n'
+        '\n'
+        '<blockquote><code>\n'
+        'select * from bugs where bug_id = <em>n</em>\n'
+        '</code></blockquote>\n'
+        '\n'
+        '<p>To get a complete list of user ids and email addresses:</p>\n'
+        '\n'
+        '<blockquote><code>\n'
+        'select userid, login_name from profiles\n'
+        '</code></blockquote>\n'
+        '\n'
+        '<p>To get the email address of user <em>n</em>:</p>\n'
+        '\n'
+        '<blockquote><code>\n'
+        'select login_name from profiles where userid = <em>n</em>\n'
+        '</code></blockquote>\n'
+        '\n'
+        '<p>To get the set of cc addresses of bug <em>n</em>:</p>\n'
         '\n'
         '<blockquote><code>\n'
-        'select profiles.login_name, profiles.realname,\n'
-        '       longdescs.bug_when, longdescs.thetext\n'
-        '  from longdescs, profiles\n'
-        ' where profiles.userid = longdescs.who\n'
-        '   and longdescs.bug_id = <em>n</em>\n'
-        ' order by longdescs.bug_when\n'
-        '</code></blockquote>',
+        'select login_name from cc, profiles\n'
+        ' where cc.bug_id = <em>n</em>\n'
+        '   and profiles.userid = cc.who\n'
+        '</code></blockquote>\n'
+        '\n'
+    ),
+    (
+        '2.10',
+        None,
+        (
+            '<p>%(VERSION_STRING)sTo select the long descriptions\n'
+            'of bug <em>n</em>, together with the name and email address of the\n'
+            'commenters:</p>\n'
+            '\n'
+            '<blockquote><code>\n'
+            'select profiles.login_name, profiles.realname,\n'
+            '       longdescs.bug_when, longdescs.thetext\n'
+            '  from longdescs, profiles\n'
+            ' where profiles.userid = longdescs.who\n'
+            '   and longdescs.bug_id = <em>n</em>\n'
+            ' order by longdescs.bug_when\n'
+            '</code></blockquote>'
+        ),
     ),
     ('2.4', None, '<p>To find out the groups of user <em>n</em>:</p>'),
     (
         '2.4',
         '2.16.7',
-        '<p>%(VERSION_STRING)s</p>\n'
-        '\n'
-        '<blockquote><code>\n'
-        'select groupset from profiles where userid = <em>n</em>\n'
-        '</code></blockquote>',
+        (
+            '<p>%(VERSION_STRING)s</p>\n'
+            '\n'
+            '<blockquote><code>\n'
+            'select groupset from profiles where userid = <em>n</em>\n'
+            '</code></blockquote>'
+        ),
     ),
     (
         '2.17.1',
         None,
-        '<p>%(VERSION_STRING)s</p>\n'
-        '\n'
-        '<blockquote><code>\n'
-        'select group_id from user_group_map where userid = <em>n</em> and '
-        'isbless=0\n'
-        '</code></blockquote>',
+        (
+            '<p>%(VERSION_STRING)s</p>\n'
+            '\n'
+            '<blockquote><code>\n'
+            'select group_id from user_group_map where userid = <em>n</em> and '
+            'isbless=0\n'
+            '</code></blockquote>'
+        ),
+    ),
+    (
+        '<h2><a id="section-A" name="section-A">A. References</a></h2>\n'
+        '\n'
+        '\n'
+        '<h2><a id="section-B" name="section-B">B. Document History</a></h2>\n'
+        '\n'
+        '<table>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2000-11-14</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Created.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td> 2001-03-02 </td>\n'
+        '\n'
+        '    <td> <a href="mailto:rb@ravenbrook.com">RB</a> </td>\n'
+        '\n'
+        '    <td> Transferred copyright to Perforce under their license. </td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td> 2001-04-06 </td>\n'
+        '\n'
+        '    <td> <a href="mailto:nb@ravenbrook.com">NB</a> </td>\n'
+        '\n'
+        '    <td> Added sample queries. </td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2001-09-12</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Updated to reflect schema updates in Bugzilla 2.12 and 2.14</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2002-01-31</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added notes on Bugzilla 2.14.1.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2002-05-31</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Updated for Bugzilla 2.16 (based on 2.16rc1).</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2002-09-26</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Updated for Bugzilla 2.16/2.14.2/2.14.3.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2002-10-04</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added notes on Bugzilla 2.14.4 and 2.16.1, and on identical '
+        'schemas.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-05-14</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added extensive notes on schema changes, in section 2.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-06-06</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added table of Bugzilla releases showing release date and support '
+        'status.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-06-06</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added notes on schema changes in 2.17.x.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-06-13</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added first cut at description of new Bugzilla tables.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-06-27</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Added more on recent schema changes.  Colour-coded all schema\n'
+        '  changes.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-07-09</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Completely changed the way this document is produced.  The\n'
+        '    schema tables themselves are now created and coloured\n'
+        '    automatically by querying MySQL.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-11-04</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add Bugzilla 2.16.4 and 2.17.5.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2003-11-10</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add Bugzilla 2.17.6.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-03-19</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add Bugzilla 2.17.7; improve documentation of the groups system; '
+        'improve automated schema change descriptions.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-03-26</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add documentation of the flags system, the time series system, and '
+        'the time tracking system.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-04-30</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Correct some documentation of the time series system based on '
+        'feedback from the author.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-07-14</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.16.6 and 2.18rc1.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-07-28</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.18rc2.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-11-11</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.16.7, 2.18rc3, 2.19.1.  Change document-generation code\n'
+        '    to improve colouring, link consistency, control, and\n'
+        '    robustness.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-11-12</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Turn into CGI, using schemas stored in Python pickles.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-11-13</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.0, 2.2, 2.4, 2.6. 2.8, for completeness.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2004-12-03</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add notes on quips and a few missing foreign key links.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2005-01-18</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.16.8, 2.18, and 2.19.2.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2005-05-19</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.16.9, 2.16.10, 2.18.1, and (preliminarily) 2.19.3.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2005-09-15</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.18.2, 2.18.3, 2.20rc1, 2.20rc2, and complete remarks for '
+        '2.19.3.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2005-10-03</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.18.4, 2.20, 2.21.1</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2006-05-18</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add 2.16.11, 2.18.5, 2.20.1, 2.22rc1, 2.20.2, 2.22, 2.23.1.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2006-10-31</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add recent releases, to 2.18.6, 2.20.3, 2.22.1, 2.23.3.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2007-05-11</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add recent releases 2.20.4, 2.22.2, 2.23.4, 3.0rc1, 3.0.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2008-02-29</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add recent releases 3.0.1, 3.0.2, 3.0.3, 3.1.1, 3.1.2, 3.1.3.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2009-07-31</td>\n'
+        '\n'
+        '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
+        '\n'
+        '    <td>Add recent releases 2.20.7, 2.22.5, 2.22.6, 2.22.7, 3.0.5, 3.0.6, '
+        '3.0.7, 3.0.8, 3.2rc1, 3.2rc2, 3.2, 3.2.1, 3.2.2, 3.2.3, 3.2.4, 3.3.1, 3.3.2, '
+        '3.3.3, 3.3.4, 3.4rc1, and 3.4.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2024-04-26</td>\n'
+        '\n'
+        '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
+        '\n'
+        '    <td>Add ancient releases 3.0.10, 3.0.11, 3.2.6, 3.2.7, 3.2.8, 3.2.9, '
+        '3.2.10, 3.4.3, 3.4.4, 3.4.5, 3.4.6, 3.4.7, 3.4.8, 3.4.9, 3.4.10, 3.4.11, '
+        '3.4.12, 3.4.13, 3.4.14, 3.5.1, and 3.5.2.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2024-04-27</td>\n'
+        '\n'
+        '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
+        '\n'
+        '    <td>Add ancient releases 3.5.3, 3.6rc1, 3.6, 3.6.1, 3.6.2, 3.6.3, 3.6.4, '
+        '3.6.5, 3.6.6, 3.6.7, 3.6.8, 3.6.9, 3.6.10, 3.6.11, 3.6.12, 3.6.13, 3.7.1, '
+        '3.7.2, 3.7.3, 4.0rc1, 4.0rc2, 4.0, 4.0.1, 4.0.2, 4.0.3, 4.0.4, 4.0.5, 4.0.6, '
+        '4.0.7, 4.0.8, 4.0.9, 4.0.10, 4.0.11, 4.0.12, 4.0.13, 4.0.14, 4.0.15, 4.0.16, '
+        '4.0.17, 4.0.18, 4.1.1, 4.1.2, 4.1.3, and 4.2rc1.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2024-04-28</td>\n'
+        '\n'
+        '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
+        '\n'
+        '    <td>Add ancient releases 4.2rc1, 4.2rc2, 4.2, 4.2.1, 4.2.2, 4.2.3, '
+        '4.2.4, 4.2.5, 4.2.6, 4.2.7, 4.2.8, 4.2.9, 4.2.10, 4.2.11, 4.2.12, 4.2.13, '
+        '4.2.14, 4.2.15, and 4.2.16.</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '  <tr valign="top">\n'
+        '\n'
+        '    <td>2024-05-01</td>\n'
+        '\n'
+        '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
+        '\n'
+        '    <td>Add releases 4.3.1, 4.3.2, 4.3.3, 4.4rc1, 4.4rc2, 4.4, 4.4.1, 4.4.2, '
+        '4.4.3, 4.4.4, 4.4.5, 4.4.6, 4.4.7, 4.4.8, 4.4.9, 4.4.10, 4.4.11, 4.4.12, '
+        '4.4.13, 4.4.14, 4.5.1, 4.5.2, 4.5.3, 4.5.4, 4.5.5, 4.5.6, 5.0rc1, 5.0rc2, '
+        '5.0rc3, 5.0, 5.0.1, 5.0.2, 5.0.3, 5.0.4, 5.0.4.1, 5.0.5, 5.0.6, 5.2, 5.1.1, '
+        '5.1.2, and 5.3.3</td>\n'
+        '\n'
+        '  </tr>\n'
+        '\n'
+        '</table>\n'
+        '\n'
+        '<hr/>\n'
+        '\n'
+        '<div align="center">\n'
+        '<p><small>Generated at %(TIME)s<br/>\n'
+        'by <code>%(SCRIPT_ID)s</code><br/>\n'
+        'from <code>%(REMARKS_ID)s</code></small></p>\n'
+        '</div>\n'
+        '\n'
     ),
-    '<h2><a id="section-A" name="section-A">A. References</a></h2>\n'
-    '\n'
-    '\n'
-    '<h2><a id="section-B" name="section-B">B. Document History</a></h2>\n'
-    '\n'
-    '<table>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2000-11-14</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Created.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td> 2001-03-02 </td>\n'
-    '\n'
-    '    <td> <a href="mailto:rb@ravenbrook.com">RB</a> </td>\n'
-    '\n'
-    '    <td> Transferred copyright to Perforce under their license. </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td> 2001-04-06 </td>\n'
-    '\n'
-    '    <td> <a href="mailto:nb@ravenbrook.com">NB</a> </td>\n'
-    '\n'
-    '    <td> Added sample queries. </td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2001-09-12</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Updated to reflect schema updates in Bugzilla 2.12 and 2.14</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2002-01-31</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added notes on Bugzilla 2.14.1.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2002-05-31</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Updated for Bugzilla 2.16 (based on 2.16rc1).</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2002-09-26</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Updated for Bugzilla 2.16/2.14.2/2.14.3.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2002-10-04</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added notes on Bugzilla 2.14.4 and 2.16.1, and on identical '
-    'schemas.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-05-14</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added extensive notes on schema changes, in section 2.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-06-06</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added table of Bugzilla releases showing release date and support '
-    'status.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-06-06</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added notes on schema changes in 2.17.x.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-06-13</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added first cut at description of new Bugzilla tables.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-06-27</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Added more on recent schema changes.  Colour-coded all schema\n'
-    '  changes.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-07-09</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Completely changed the way this document is produced.  The\n'
-    '    schema tables themselves are now created and coloured\n'
-    '    automatically by querying MySQL.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-11-04</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add Bugzilla 2.16.4 and 2.17.5.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2003-11-10</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add Bugzilla 2.17.6.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-03-19</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add Bugzilla 2.17.7; improve documentation of the groups system; '
-    'improve automated schema change descriptions.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-03-26</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add documentation of the flags system, the time series system, and '
-    'the time tracking system.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-04-30</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Correct some documentation of the time series system based on '
-    'feedback from the author.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-07-14</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.16.6 and 2.18rc1.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-07-28</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.18rc2.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-11-11</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.16.7, 2.18rc3, 2.19.1.  Change document-generation code\n'
-    '    to improve colouring, link consistency, control, and\n'
-    '    robustness.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-11-12</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Turn into CGI, using schemas stored in Python pickles.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-11-13</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.0, 2.2, 2.4, 2.6. 2.8, for completeness.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2004-12-03</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add notes on quips and a few missing foreign key links.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2005-01-18</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.16.8, 2.18, and 2.19.2.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2005-05-19</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.16.9, 2.16.10, 2.18.1, and (preliminarily) 2.19.3.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2005-09-15</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.18.2, 2.18.3, 2.20rc1, 2.20rc2, and complete remarks for '
-    '2.19.3.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2005-10-03</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.18.4, 2.20, 2.21.1</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2006-05-18</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add 2.16.11, 2.18.5, 2.20.1, 2.22rc1, 2.20.2, 2.22, 2.23.1.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2006-10-31</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add recent releases, to 2.18.6, 2.20.3, 2.22.1, 2.23.3.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2007-05-11</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add recent releases 2.20.4, 2.22.2, 2.23.4, 3.0rc1, 3.0.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2008-02-29</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add recent releases 3.0.1, 3.0.2, 3.0.3, 3.1.1, 3.1.2, 3.1.3.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2009-07-31</td>\n'
-    '\n'
-    '    <td><a href="mailto:nb@ravenbrook.com">NB</a></td>\n'
-    '\n'
-    '    <td>Add recent releases 2.20.7, 2.22.5, 2.22.6, 2.22.7, 3.0.5, 3.0.6, '
-    '3.0.7, 3.0.8, 3.2rc1, 3.2rc2, 3.2, 3.2.1, 3.2.2, 3.2.3, 3.2.4, 3.3.1, 3.3.2, '
-    '3.3.3, 3.3.4, 3.4rc1, and 3.4.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2024-04-26</td>\n'
-    '\n'
-    '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
-    '\n'
-    '    <td>Add ancient releases 3.0.10, 3.0.11, 3.2.6, 3.2.7, 3.2.8, 3.2.9, '
-    '3.2.10, 3.4.3, 3.4.4, 3.4.5, 3.4.6, 3.4.7, 3.4.8, 3.4.9, 3.4.10, 3.4.11, '
-    '3.4.12, 3.4.13, 3.4.14, 3.5.1, and 3.5.2.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2024-04-27</td>\n'
-    '\n'
-    '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
-    '\n'
-    '    <td>Add ancient releases 3.5.3, 3.6rc1, 3.6, 3.6.1, 3.6.2, 3.6.3, 3.6.4, '
-    '3.6.5, 3.6.6, 3.6.7, 3.6.8, 3.6.9, 3.6.10, 3.6.11, 3.6.12, 3.6.13, 3.7.1, '
-    '3.7.2, 3.7.3, 4.0rc1, 4.0rc2, 4.0, 4.0.1, 4.0.2, 4.0.3, 4.0.4, 4.0.5, 4.0.6, '
-    '4.0.7, 4.0.8, 4.0.9, 4.0.10, 4.0.11, 4.0.12, 4.0.13, 4.0.14, 4.0.15, 4.0.16, '
-    '4.0.17, 4.0.18, 4.1.1, 4.1.2, 4.1.3, and 4.2rc1.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2024-04-28</td>\n'
-    '\n'
-    '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
-    '\n'
-    '    <td>Add ancient releases 4.2rc1, 4.2rc2, 4.2, 4.2.1, 4.2.2, 4.2.3, '
-    '4.2.4, 4.2.5, 4.2.6, 4.2.7, 4.2.8, 4.2.9, 4.2.10, 4.2.11, 4.2.12, 4.2.13, '
-    '4.2.14, 4.2.15, and 4.2.16.</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '  <tr valign="top">\n'
-    '\n'
-    '    <td>2024-05-01</td>\n'
-    '\n'
-    '    <td><a href="https://github.com/justdave">justdave</a></td>\n'
-    '\n'
-    '    <td>Add releases 4.3.1, 4.3.2, 4.3.3, 4.4rc1, 4.4rc2, 4.4, 4.4.1, 4.4.2, '
-    '4.4.3, 4.4.4, 4.4.5, 4.4.6, 4.4.7, 4.4.8, 4.4.9, 4.4.10, 4.4.11, 4.4.12, '
-    '4.4.13, 4.4.14, 4.5.1, 4.5.2, 4.5.3, 4.5.4, 4.5.5, 4.5.6, 5.0rc1, 5.0rc2, '
-    '5.0rc3, 5.0, 5.0.1, 5.0.2, 5.0.3, 5.0.4, 5.0.4.1, 5.0.5, 5.0.6, 5.2, 5.1.1, '
-    '5.1.2, and 5.3.3</td>\n'
-    '\n'
-    '  </tr>\n'
-    '\n'
-    '</table>\n'
-    '\n'
-    '<hr/>\n'
-    '\n'
-    '<div align="center">\n'
-    '<p><small>Generated at %(TIME)s<br/>\n'
-    'by <code>%(SCRIPT_ID)s</code><br/>\n'
-    'from <code>%(REMARKS_ID)s</code></small></p>\n'
-    '</div>\n'
-    '\n',
 ]
 
 remarks_id = '$Id$'
diff --git a/schema_remarks_template.txt b/template_schema_remarks.txt
similarity index 100%
rename from schema_remarks_template.txt
rename to template_schema_remarks.txt
diff --git a/updating.rst b/updating.rst
index f4e0903..afce764 100644
--- a/updating.rst
+++ b/updating.rst
@@ -49,11 +49,11 @@ For any given release of Bugzilla, the process goes something like this:
 - To validate that you got the version numbers entered in all the right
   spots, you can run::
 
-  > ./make_schema_doc.py validate
+  > ./schema-tool validate
 
 - Then test the schema doc, either through the CGI or by hand::
 
-  > ./make_schema_doc.py test 3.0.0 3.8.12
+  > ./schema-tool test 3.0.0 3.8.12
 
   This will generate a list of errors, complaining about schema
   changes (new or removed tables, columns or indexes) which aren't
@@ -100,8 +100,8 @@ For any given release of Bugzilla, the process goes something like this:
   versions needs to be shown, appropriately marked.
   
 - Here are some examples.  Look at the "Groups" section of these:
-  <http://www.ravenbrook.com/tool/bugzilla-schema/?action=range&from=2.8&to=2.12&view=View+schema#notes-groups>
-  <http://www.ravenbrook.com/tool/bugzilla-schema/?action=range&from=2.8&to=3.2&view=View+schema#notes-groups>
+  <https://schema.bugzilla.org/?action=range&from=2.8&to=2.12&view=View+schema#notes-groups>
+  <https://schema.bugzilla.org/?action=range&from=2.8&to=3.2&view=View+schema#notes-groups>
   
   All those "From 2.10" and "Up to and including 2.8" and "From 2.12
   to 2.16" remarks, and the colours, are automatically generated and
diff --git a/venvwrapper.sh b/venvwrapper.sh
new file mode 100755
index 0000000..1fd5e69
--- /dev/null
+++ b/venvwrapper.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e
+if [ -z "$1" ]; then
+  echo "This is designed to be used as a shebang interpreter at the top of"
+  echo "Python CGI files to allow them to make use of the virtualenv setup."
+  echo "Don't use it directly."
+  exit -1
+fi
+if [ ! -e "venv" ]; then
+  echo "VirtualEnv does not exist. Creating..."
+  virtualenv -p python3 venv > /dev/null
+  source venv/bin/activate
+  pip install -r requirements.txt > /dev/null
+  deactivate
+fi
+source venv/bin/activate
+python3 "$@"