Alex Willmer avatar Alex Willmer committed 16cb839 Merge

Merge 0.12-stable into ticket-links-trunk

Comments (0)

Files changed (233)

 *~
 .*.sw[op]
 .svn
-.git
+.hg
 build
 dist
 Trac.egg-info
 [patterns]
+contrib/trac-svn-hook = LF
 **.cmd = CRLF
 ** = native
  * Jeroen Ruigrok van der Werven <asmodai@in-nomine.org>
  * Odd Simon Simonsen <simon-code@bvnetwork.no>
  * Remy Blank <remy.blank@pobox.com>
+ * Jun Omae <jun66j5@gmail.com>
 
 See also THANKS for people who have contributed to the project.
-Copyright (C) 2003-2009 Edgewall Software
+Copyright (C) 2003-2011 Edgewall Software
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
+Trac 0.12.2 (January 31, 2011)
+http://svn.edgewall.org/repos/trac/tags/trac-0.12.2
+
+This list contains only a few highlights:
+ - install: improved robustness of Trac installation if Babel is
+   installed after the fact (#9439, #9595, #9961)
+ - notifications: support for Asian character width (#4717)
+ - roadmap: fix display of progress bar in some corner cases (#9718)
+   and respect the overall_completion milestone group setting (#9721)
+ - reports: reports and queries look much better, as the columns now
+   keep the same width across groups; the absence of word wrapping in
+   reports has been fixed (#9825)
+ - web admin: improved layout (#8866, #9963)
+ - web: it's now possible to log in different Trac instances sharing
+   the same URL prefix (e.g. /project and /project-test) (#9951)
+
+Trac 0.12.1 (October 9, 2010)
+http://svn.edgewall.org/repos/trac/tags/trac-0.12.1
+
+This list contains only a few highlights:
+ - db: improve concurrency behavior (#9111)
+ - fcgi: add an environment variable `TRAC_USE_FLUP` to control the usage of flup vs. bundled _fcgi.py (defaults to 0, i.e. use bundled as before)
+ - svn authz: improve compatibility with svn 1.5 format (#8289)
+ - milestone: allow to set the time for the due date (#6369, #9582)
+ - ticket: fixes for the CC: property (#8597, #9522)
+ - notification: improved the formatting of ticket fields in notification e-mails (#9484, #9494) 
+ - i18n: added a configuration option to set the default language (#8117)
+ - several fixes for upgrade (#9400, #9416, #9483, #9556)
+
 Trac 0.12 'Babel' (June 13, 2010)
 http://svn.edgewall.org/repos/trac/tags/trac-0.12
 
 
   [db=...]            variable for selecting database backend
   [test=...]          variable for selecting a single test file
-  [coverageopts=...]  variable containing extra optios for coverage
+  [testopts=...]      variable containing extra options for running tests
+  [coverageopts=...]  variable containing extra options for coverage
 
  ---------------- Standalone test server
 
 
 ifdef test
 all: status
-	python $(test)
+	python $(test) $(testopts)
 else
 all: help
 endif
 	@echo -n "$(@): "
 	@msgfmt --check $(messages.po) && msgfmt --check $(messages-js.po) \
 	 && echo OK
+	@rm -f messages.mo
 
 stats: pre-stats $(addprefix stats-,$(locales))
 
 test: unit-test functional-test
 
 unit-test: Trac.egg-info
-	python ./trac/test.py --skip-functional-tests
+	python ./trac/test.py --skip-functional-tests $(testopts)
 
 functional-test: Trac.egg-info
-	python trac/tests/functional/__init__.py -v
+	python trac/tests/functional/__init__.py -v $(testopts)
 
 test-wiki:
-	python trac/tests/allwiki.py
+	python trac/tests/allwiki.py $(testopts)
 
 # ----------------------------------------------------------------------------
 #
 
 ifdef test
 test-coverage:
-	coverage run $(test)
+	coverage run $(test) $(testopts)
 else
 test-coverage: unit-test-coverage functional-test-coverage
 endif
  * Erik Bray                      hyugaricdeau@gmail.com
  * Toni Brkic                     toni.brkic@switchcore.com
  * Rocky Burt                     rocky.burt@myrealbox.com
+ * Tomáš Čapek                    soulcharmer@gmail.com
  * Shane Caraveo                  shanec@activestate.com
  * Eli Carter                     eli.carter@commprove.com
  * José manuel Castroagudín Silva

contrib/bugzilla2trac.py

             print "  inserting severity '%s' - '%s'" % (value, i)
             c.execute("""INSERT INTO enum (type, name, value)
                                    VALUES (%s, %s, %s)""",
-                      ("severity", value.encode('utf-8'), i))
+                      ("severity", value, i))
         self.db().commit()
 
     def setPriorityList(self, s):
             print "  inserting priority '%s' - '%s'" % (value, i)
             c.execute("""INSERT INTO enum (type, name, value)
                                    VALUES (%s, %s, %s)""",
-                      ("priority", value.encode('utf-8'), i))
+                      ("priority", value, i))
         self.db().commit()
 
 
             print "  inserting component '%s', owner '%s'" % \
                             (comp[key], comp['owner'])
             c.execute("INSERT INTO component (name, owner) VALUES (%s, %s)",
-                      (comp[key].encode('utf-8'),
-                       comp['owner'].encode('utf-8')))
+                      (comp[key], comp['owner']))
         self.db().commit()
 
     def setVersionList(self, v, key):
         for vers in v:
             print "  inserting version '%s'" % (vers[key])
             c.execute("INSERT INTO version (name) VALUES (%s)",
-                      (vers[key].encode('utf-8'),))
+                      (vers[key],))
         self.db().commit()
 
     def setMilestoneList(self, m, key):
             milestone = ms[key]
             print "  inserting milestone '%s'" % (milestone)
             c.execute("INSERT INTO milestone (name) VALUES (%s)",
-                      (milestone.encode('utf-8'),))
+                      (milestone,))
         self.db().commit()
 
     def addTicket(self, id, time, changetime, component, severity, priority,
                   summary, description, keywords, customfields):
         c = self.db().cursor()
 
-        desc = description.encode('utf-8')
+        desc = description
         type = "defect"
 
         if SEVERITIES:
                                          keywords)
                                  VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s,
                                          %s, %s, %s, %s, %s, %s, %s, %s)""",
-                  (id, type.encode('utf-8'), datetime2epoch(time),
-                   datetime2epoch(changetime), component.encode('utf-8'),
-                   severity.encode('utf-8'), priority.encode('utf-8'), owner,
-                   reporter, cc, version, milestone.encode('utf-8'),
-                   status.lower(), resolution, summary.encode('utf-8'), desc,
+                  (id, type, datetime2epoch(time),
+                   datetime2epoch(changetime), component,
+                   severity, priority, owner,
+                   reporter, cc, version, milestone,
+                   status.lower(), resolution, summary, desc,
                    keywords))
 
         self.db().commit()
 
         c.execute("""INSERT INTO ticket_custom (ticket, name, value)
                                  VALUES (%s, %s, %s)""",
-                  (ticket_id, field_name.encode('utf-8'), field_value.encode('utf-8')))
+                  (ticket_id, field_name, field_value))
 
         self.db().commit()
 
     def addTicketComment(self, ticket, time, author, value):
-        comment = value.encode('utf-8')
+        comment = value
 
         if PREFORMAT_COMMENTS:
           comment = '{{{\n%s\n}}}' % comment
                                                 oldvalue, newvalue)
                                         VALUES (%s, %s, %s, %s, %s, %s)""",
                   (ticket, datetime2epoch(time), author, field,
-                   oldvalue.encode('utf-8'), newvalue.encode('utf-8')))
+                   oldvalue, newvalue))
         self.db().commit()
 
     def addAttachment(self, author, a):
         if a['filename'] != '':
-            description = a['description'].encode('utf-8')
+            description = a['description']
             id = a['bug_id']
-            filename = a['filename'].encode('utf-8')
+            filename = a['filename']
             filedata = StringIO.StringIO(a['thedata'])
             filesize = len(filedata.getvalue())
             time = a['creation_ts']
             (_db, _host, _user, ("*" * len(_password)))
     mysql_con = MySQLdb.connect(host=_host,
                 user=_user, passwd=_password, db=_db, compress=1,
-                cursorclass=MySQLdb.cursors.DictCursor)
+                cursorclass=MySQLdb.cursors.DictCursor,
+                charset='utf8')
     mysql_cur = mysql_con.cursor()
 
     # init Trac environment
 
 def datetime2epoch(dt) :
     import time
-    return time.mktime(dt.timetuple())
+    return time.mktime(dt.timetuple()) * 1000000
 
 def usage():
     print """bugzilla2trac - Imports a bug database from Bugzilla into Trac.

contrib/trac-svn-hook

 #  Purpose:: this script is meant to be called from the Subversion hooks 
 #            for notifying Trac when changesets are added or modified.
 #
-#  Scope:: The http://trac.edgewall.org/wiki/0.12/TracRepositoryAdmin page
+#  Scope:: The http://trac.edgewall.org/wiki/TracRepositoryAdmin page
 #          describes how to directly call the relevant trac-admin commands
 #          from the Subversion hooks. In most cases this should be enough,
 #          however this script should make troubleshooting easier and 

contrib/workflow/showworkflow

 pdf=`echo "$config" | sed 's/\.ini$/.pdf/g'`
 png=`echo "$config" | sed 's/\.ini$/.png/g'`
 
-$basedir/workflow_parser.py $options "$config" > "$dot"
+$basedir/workflow_parser.py $options "$config" "$dot"
 if [ $? -ne 0 ]; then
     echo "Failed to parse \"$config\", exiting." >&2
     exit 1
     dot -T png -o "$png" "$dot"
     cmd /c start $png
 else
-    dot -T ps -o "$ps" "$dot" && ps2pdf "$ps" "$pdf" || exit 1
-    kpdf "$pdf"
+    dot -T ps -o "$ps" "$dot"
+    dot -T pdf -o "$pdf" "$dot"
+    # attempt to find a Linux pdf viewer
+    for viewer in kpdf okular evince; do
+        if which $viewer >/dev/null 2>&1; then
+            break
+        fi
+    done
+    $viewer "$pdf"
 fi

contrib/workflow/workflow_parser.py

 
 import sys
 import getopt
+import locale
 
 import pkg_resources
 pkg_resources.require('Trac')
     digraph_lines.append('}')
     return digraph_lines
 
-def main(filename, show_ops=False, show_perms=False):
+def main(filename, output, show_ops=False, show_perms=False):
     # Read in the config
     rawactions = readconfig(filename)
 
     digraph_lines = actions2graphviz(actions, show_ops, show_perms)
 
     # And output
-    sys.stdout.write('\n'.join(digraph_lines))
+    output.write(unicode.encode('\n'.join(digraph_lines), locale.getpreferredencoding()))
 
 def usage(output):
-    output.write('workflow_parser [options] configfile.ini\n'
+    output.write('workflow_parser [options] configfile.ini [output.dot]\n'
                  '-h --help shows this message\n'
                  '-o --operations include operations in the graph\n'
                  '-p --permissions include permissions in the graph\n'
         usage(sys.stderr)
         sys.stderr.flush()
         sys.exit(1)
-    main(args[0], show_ops, show_perms)
+    ini_filename = args[0]
+    if len(args) > 1:
+        output = open(args[1], 'w')
+    else:
+        output = sys.stdout
+
+    main(ini_filename, output, show_ops, show_perms)
 
 # General substitutions.
 project = 'Trac'
-copyright = '2008, Edgewall Software'
+copyright = '2010, Edgewall Software'
 
 # The default replacements for |version| and |release|, also used in various
 # other places throughout the built documents.
 #
 # The short X.Y version.
-version = '0.12'
+version = '0.12.3'
 # The full version, including alpha/beta/rc tags.
-release = '0.12-dev'
+release = '0.12.3'
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
 [javascript: **.js]
 
 [extractors]
-javascript_script = trac.util.dist:extract_javascript_script
+javascript_script = trac.dist:extract_javascript_script
 
-[javascript_script: **.html]
+[javascript_script: **.html]

sample-plugins/permissions/debug_perm.py

+from trac.core import *
+from trac.perm import IPermissionPolicy, PermissionCache
+from trac.resource import Resource
+
+revision = "$Rev$"
+url = "$URL$"
+
+class DebugPolicy(Component):
+    """Verify the well-formedness of the permission checks.
+    
+    **This plugin is only useful for Trac Development.**
+    
+    Once this plugin is enabled, you'll have to insert it at the appropriate
+    place in your list of permission policies, e.g.
+    {{{
+    [trac]
+    permission_policies = DebugPolicy, SecurityTicketsPolicy, AuthzPolicy, 
+                          DefaultPermissionPolicy, LegacyAttachmentPolicy
+    }}}
+    """
+    
+    implements(IPermissionPolicy)
+
+    # IPermissionPolicy methods
+
+    def check_permission(self, action, username, resource, perm):
+        if resource:
+            assert resource is None or isinstance(resource, Resource)
+        assert isinstance(perm, PermissionCache)
+        self.log.info("does '%s' have %s on %r?", username, action, resource)

File contents unchanged.

 # individuals. For the exact contribution history, see the revision
 # history and logs, available at http://trac.edgewall.org/log/.
 
+import sys
+
 from setuptools import setup, find_packages
 
+min_python = (2, 4)
+if sys.version_info < min_python:
+    print "Trac requires Python %d.%d or later" % min_python
+    sys.exit(1)
+if sys.version_info >= (3,):
+    print "Trac doesn't support Python 3 (yet)"
+    sys.exit(1)
+
 extra = {}
 
 try:
         'tracopt': extractors,
     }
 
-    from trac.util.dist import get_l10n_js_cmdclass
+    from trac.dist import get_l10n_js_cmdclass
     extra['cmdclass'] = get_l10n_js_cmdclass()
 
-except ImportError, e:
+except ImportError:
     pass
 
+try:
+    import genshi
+except ImportError:
+    print "Genshi is needed by Trac setup, pre-installing"
+    # give some context to the warnings we might get when installing Genshi
+
+
 setup(
     name = 'Trac',
-    version = '0.13',
+    version = '0.12.3',
     description = 'Integrated SCM, wiki, issue tracker and project environment',
     long_description = """
 Trac is a minimalistic web-based software project management and bug/issue
     test_suite = 'trac.test.suite',
     zip_safe = True,
 
+    setup_requires = [
+        'Genshi>=0.6',
+    ],
     install_requires = [
         'setuptools>=0.6b1',
         'Genshi>=0.6',
 try:
     __version__ = get_distribution('Trac').version
 except DistributionNotFound:
-    __version__ = '0.13dev'
+    __version__ = '0.12.3'

trac/admin/api.py

     def complete(self, text):
         return list(set(a for a in self if a.startswith(text)))
 
-        
+
+def path_startswith(path, prefix):
+    return os.path.normcase(path).startswith(os.path.normcase(prefix))
+
+
 class PathList(list):
     """A list of paths for command argument auto-completion."""
     def complete(self, text):
         """Return the items in the list matching text."""
-        matches = list(set(a for a in self if a.startswith(text)))
+        matches = list(set(a for a in self if path_startswith(a, text)))
         if len(matches) == 1 and not os.path.isdir(matches[0]):
             matches[0] += ' '
         return matches
     except OSError:
         return result
     for entry in dlist:
-        path = os.path.join(dname, entry)
+        path = os.path.normpath(os.path.join(dname, entry))
         try:
             if os.path.isdir(path):
                 result.append(os.path.join(path, ''))

trac/admin/console.py

 import locale
 import os.path
 import pkg_resources
-import shlex
+from shlex import shlex
 import StringIO
 import sys
 import traceback
 from trac.util.html import html
 from trac.util.text import console_print, exception_to_unicode, printout, \
                            printerr, raw_input, to_unicode
-from trac.util.translation import _
+from trac.util.translation import _, get_negotiated_locale, has_babel
 from trac.versioncontrol.api import RepositoryManager
 from trac.wiki.admin import WikiAdmin
 from trac.wiki.macros import WikiMacroBase
 
 TRAC_VERSION = pkg_resources.get_distribution('Trac').version
 rl_completion_suppress_append = None
-
+LANG = os.environ.get('LANG')
 
 def find_readline_lib():
     """Return the name (and possibly the full path) of the readline library
         try:
             import readline
             delims = readline.get_completer_delims()
-            for c in '-/:()':
+            for c in '-/:()\\':
                 delims = delims.replace(c, '')
             readline.set_completer_delims(delims)
             
         except SystemExit:
             raise
         except AdminCommandError, e:
-            printerr(_("Error:"), to_unicode(e))
+            printerr(_("Error: %(msg)s", msg=to_unicode(e)))
             if e.show_usage:
                 print
                 self.do_help(e.cmd or self.arg_tokenize(line)[0])
         self.interactive = True
         printout(_("""Welcome to trac-admin %(version)s
 Interactive Trac administration console.
-Copyright (c) 2003-2010 Edgewall Software
+Copyright (C) 2003-2011 Edgewall Software
 
 Type:  '?' or 'help' for help on commands.
         """, version=TRAC_VERSION))
     def env_check(self):
         if not self.__env:
             try:
-                self.__env = Environment(self.envname)
+                self._init_env()
             except:
                 return False
         return True
     def env(self):
         try:
             if not self.__env:
-                self.__env = Environment(self.envname)
+                self._init_env()
             return self.__env
         except Exception, e:
             printerr(_("Failed to open environment: %(err)s",
                        err=exception_to_unicode(e, traceback=True)))
             sys.exit(1)
 
+    def _init_env(self):
+        self.__env = env = Environment(self.envname)
+        # fixup language according to env settings
+        if has_babel:
+            default = env.config.get('trac', 'default_language', '')
+            negotiated = get_negotiated_locale([LANG, default])
+            if negotiated:
+                translation.activate(negotiated)
+        
     ##
     ## Utility methods
     ##
 
         ... but shlex is not unicode friendly.
         """
-        return [unicode(token, 'utf-8')
-                for token in shlex.split(argstr.encode('utf-8'))] or ['']
+        lex = shlex(argstr.encode('utf-8'), posix=True)
+        lex.whitespace_split = True
+        lex.commenters = ''
+        if os.name == 'nt':
+            lex.escape = ''
+        return [unicode(token, 'utf-8') for token in lex] or ['']
 
     def word_complete(self, text, words):
         words = list(set(a for a in words if a.startswith(text)))
         return self.complete_line(text, line)
         
     def default(self, line):
-        if not self.env_check():
-            raise AdminCommandError(_("Command not found"))
+        try:
+            if not self.__env:
+                self._init_env()
+        except TracError, e:
+            raise AdminCommandError(to_unicode(e))
+        except Exception, e:
+            raise AdminCommandError(exception_to_unicode(e))
         args = self.arg_tokenize(line)
         cmd_mgr = AdminCommandManager(self.env)
         return cmd_mgr.execute_command(*args)
             initenv_error(_("Directory exists and is not empty."))
             return 2
 
+        if not os.path.exists(os.path.dirname(self.envname)):
+            initenv_error(_("Base directory '%(env)s' does not exist. Please "
+                            "create it manually and retry.",
+                            env=os.path.dirname(self.envname)))
+            return 2            
+
         arg = self.arg_tokenize(line)
         inherit_paths = []
         i = 0
     if args is None:
         args = sys.argv[1:]
     locale = None
-    try:
+    if has_babel:
         import babel
         try:
-            locale = babel.Locale.default()
+            locale = get_negotiated_locale([LANG]) or babel.Locale.default()
         except babel.UnknownLocaleError:
             pass
-    except ImportError:
-        pass
-    translation.activate(locale)
+        translation.activate(locale)
     admin = TracAdmin()
     if len(args) > 0:
         if args[0] in ('-h', '--help', 'help'):
                 unicode(env_path, 'ascii')
             except UnicodeDecodeError:
                 printerr(_("Non-ascii environment path '%(path)s' not "
-                           "supported.", path=env_path))
+                           "supported.", path=to_unicode(env_path)))
                 sys.exit(2)
             admin.env_set(env_path)
             if len(args) > 1:

trac/admin/templates/admin_enums.html

               You can remove all items from this list to completely hide this
               field from the user interface.
             </p>
+            <p class="help" py:if="type=='priority'">
+              <b>Note:</b> The order of priorities determines the coloring of entries
+              in the ticket queries and reports.
+            </p>
           </form>
 
           <p py:otherwise="" class="help">

trac/admin/templates/admin_milestones.html

           </div>
           <div class="field">
             <label>Due:<br />
-              <input type="text" id="duedate" name="duedate" size="${len(date_hint)}"
-                     value="${milestone.due and format_date(milestone.due)}" readonly="${readonly}"
-                     title="${_('Format: %(datehint)s', datehint=date_hint)}"/>
-              <em i18n:msg="datehint">Format: $date_hint</em>
+              <input type="text" id="duedate" name="duedate" size="${len(datetime_hint)}"
+                     value="${milestone.due and format_datetime(milestone.due)}" readonly="${readonly}"
+                     title="${_('Format: %(datehint)s', datehint=datetime_hint)}"/>
+              <em i18n:msg="datehint">Format: $datetime_hint</em>
             </label>
           </div>
           <div class="field">
       </form>
 
       <py:otherwise>
-
         <form class="addnew" id="addmilestone" method="post" action="" py:if="'MILESTONE_CREATE' in req.perm">
           <fieldset>
             <legend>Add Milestone:</legend>
             <div class="field">
-              <label>Name:<br />
-                <input type="text" name="name" id="name" size="20" />
-              </label>
+              <label>Name:<br /><input type="text" name="name" id="name" size="22" /></label>
             </div>
             <div class="field">
               <label>
                 Due:<br />
-                <input type="text" name="duedate" size="${len(date_hint)}"
-                       title="${_('Format: %(datehint)s', datehint=date_hint)}" />
-                <em i18n:msg="datehint">Format: $date_hint</em>
+                <input type="text" id="duedate" name="duedate" size="${len(datetime_hint)}"
+                       title="${_('Format: %(datehint)s', datehint=datetime_hint)}" /><br/>
+                <em i18n:msg="datetimehint">Format: $datetime_hint</em>
               </label>
             </div>
             <div class="buttons">
                   <a href="${panel_href(milestone.name)}">${milestone.name}</a>
                 </td>
                 <td><py:if test="milestone.due">
-                  ${format_date(milestone.due)}
+                  ${format_datetime(milestone.due)}
                 </py:if></td>
                 <td><py:if test="milestone.completed">
                   ${format_datetime(milestone.completed)}
               <input type="submit" name="remove" value="${_('Remove selected items')}" py:if="can_remove" />
               <input type="submit" name="apply" value="${_('Apply changes')}" />
             </div>
-            <p class="hint">
+            <p class="help">
               You can remove all items from this list to completely hide this
               field from the user interface.
             </p>

trac/admin/templates/admin_plugins.html

     <script type="text/javascript" src="${chrome.htdocs_location}js/folding.js"></script>
     <script type="text/javascript">
       jQuery(document).ready(function($){
-        $(".foldable").enableFolding(true, true);
-        $("tbody .trac-toggler a").each(function() {
-          $(this).attr("href", "").click(function() {
-            var a = $(this);
-            a.closest("td").toggleClass("collapsed");
-            a.text(a.text() == "+"? "&ndash;": "+");
-            return false;
-          });
-        });
+        $("h3.foldable").enableFolding(true, true);
+        $("p.foldable").enableFolding(true, false);
         $("thead .trac-toggler a").each(function() {
           $(this).attr("href", "").click(function() {
             var td = $(this).closest("table").find("tbody td");
           <thead>
             <tr>
               <th>
+                Component
                 <span class="trac-toggler">
                   [<a title="Show all descriptions" href="${href.admin('general', 'plugin', show=plugin.name)
                                                             + '#trac-plugin-' + plugin.name}">+</a>]
                   [<a title="Hide all descriptions" href="${href.admin('general', 'plugin')
                                                             + '#trac-plugin-' + plugin.name}">&ndash;</a>]
                 </span>
-                Component
               </th>
               <th class="sel">Enabled</th>
             </tr>
             <tr>
               <td py:with="show_doc = show == plugin.name or show == module_name" id="trac-mod-${module_name}"
                   class="trac-module${not show_doc and ' collapsed' or None}">
-                <p class="trac-heading">
-                  <span py:if="module.description" class="trac-toggler">
-                    [<a title="Toggle the module description"
-                        href="${href.admin('general', 'plugin', show=not show_doc and module_name or None)
-                                + '#trac-mod-' + module_name}">${show_doc and '&ndash;' or '+'}</a>]
-                  </span>
-                  <span class="trac-name">${module_name}.*</span>
+                <p class="trac-heading${module.description and ' foldable' or None}">
+                  <py:choose>
+                    <a py:when="module.description" class="trac-name"
+                       href="${href.admin('general', 'plugin', show=not show_doc and module_name or None)                                                     
+                               + '#trac-mod-' + module_name}">${module_name}.*</a>
+                    <span py:otherwise="" class="trac-name">${module_name}.*</span>
+                  </py:choose>
                   <span py:if="module.summary" class="trac-summary"> &mdash; ${module.summary}</span>
                 </p>
                 <div py:if="module.description" xml:space="preserve">${safe_wiki_to_html(context, module.description)}</div>
             <tr py:for="component_name, component in sorted(module.components.iteritems())">
               <td py:with="show_doc = show == plugin.name or show == component.full_name" id="trac-comp-${component.full_name}"
                   class="trac-component${not show_doc and ' collapsed' or None}">
-                <p class="trac-heading">
-                  <span py:if="component.description" class="trac-toggler">
-                    [<a title="Toggle the component description"
-                        href="${href.admin('general', 'plugin', show=not show_doc and component.full_name or None)
-                                + '#trac-comp-' + component.full_name}">${show_doc and '&ndash;' or '+'}</a>]
-                  </span>
-                  <span class="trac-name">${component_name}</span>
+                <p class="trac-heading${component.description and ' foldable' or None}">
+                  <py:choose>
+                    <a py:when="component.description" class="trac-name"
+                       href="${href.admin('general', 'plugin', show=not show_doc and component.full_name or None)                                             
+                               + '#trac-comp-' + component.full_name}">${component_name}</a>
+                    <span py:otherwise="" class="trac-name">${component_name}</span>
+                  </py:choose>
                   <span py:if="component.summary" class="trac-summary"> &mdash; ${component.summary}</span>
                 </p>
                 <div py:if="component.description" xml:space="preserve">${safe_wiki_to_html(context, component.description)}</div>

trac/admin/templates/admin_versions.html

           </div>
           <div class="field">
             <label>Date:<br />
-              <input type="text" name="time" size="${len(datetime_hint)}"
+              <input type="text" id="versiondate" name="time" size="${len(datetime_hint)}"
                      value="${format_datetime(version.time)}"
                      title="${_('Format: %(datehint)s', datehint=datetime_hint)}" />
               <em i18n:msg="datehint">Format: $datetime_hint</em>
           <fieldset>
             <legend>Add Version:</legend>
             <div class="field">
-              <label>Name:<br /><input type="text" name="name" id="name" /></label>
+              <label>Name:<br /><input type="text" name="name" id="name" size="22" /></label>
             </div>
             <div class="field">
-              <label>Released:<br />
-                <input type="text" name="time" size="${len(datetime_hint)}"
+              <label>
+                Released:<br />
+                <input type="text" id="releaseddate" name="time" size="${len(datetime_hint)}"
                        title="${_('Format: %(datehint)s', datehint=datetime_hint)}"
                        value="${format_datetime()}" /><br />
                 <em i18n:msg="datehint">Format: $datetime_hint</em>

trac/admin/tests/console-tests.txt

 resolution remove    Remove a resolution value
 session add          Create a session for the given sid
 session delete       Delete the session of the specified sid
-session list         List the name and email for the sids specified
+session list         List the name and email for the given sids
 session purge        Purge all anonymous sessions older than the given age
 session set          Set the name or email attribute of the given sid
 severity add         Add a severity value option
 
 Name        Due         Completed
 ---------------------------------
-état_final  2004-01-11
+©tat_final  2004-01-11
 milestone1
 milestone2
 milestone3
 1.0       
 
 ===== test_session_list_no_sessions =====
-No SID found
+
+SID  Auth  Last Visit  Name  Email
+----------------------------------
+
 ===== test_session_list_authenticated =====
 
-SID     Name   Email
---------------------
-name00  val00  val00
-name01  val01  val01
-name02  val02  val02
-name03  val03  val03
-name04  val04  val04
-name05  val05  val05
-name06  val06  val06
-name07  val07  val07
-name08  val08  val08
-name09  val09  val09
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name00  1     2010-01-01  val00  val00
+name01  1     2010-01-01  val01  val01
+name02  1     2010-01-01  val02  val02
+name03  1     2010-01-01  val03  val03
+name04  1     2010-01-01  val04  val04
+name05  1     2010-01-01  val05  val05
+name06  1     2010-01-01  val06  val06
+name07  1     2010-01-01  val07  val07
+name08  1     2010-01-01  val08  val08
+name09  1     2010-01-01  val09  val09
 
 ===== test_session_list_anonymous =====
 
-SID     Name   Email
---------------------
-name10  val10  val10
-name11  val11  val11
-name12  val12  val12
-name13  val13  val13
-name14  val14  val14
-name15  val15  val15
-name16  val16  val16
-name17  val17  val17
-name18  val18  val18
-name19  val19  val19
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name10  0     2010-01-01  val10  val10
+name11  0     2010-01-01  val11  val11
+name12  0     2010-01-01  val12  val12
+name13  0     2010-01-01  val13  val13
+name14  0     2010-01-01  val14  val14
+name15  0     2010-01-01  val15  val15
+name16  0     2010-01-01  val16  val16
+name17  0     2010-01-01  val17  val17
+name18  0     2010-01-01  val18  val18
+name19  0     2010-01-01  val19  val19
 
 ===== test_session_list_all =====
 
-SID     Name   Email
---------------------
-name00  val00  val00
-name01  val01  val01
-name02  val02  val02
-name03  val03  val03
-name04  val04  val04
-name05  val05  val05
-name06  val06  val06
-name07  val07  val07
-name08  val08  val08
-name09  val09  val09
-name10  val10  val10
-name11  val11  val11
-name12  val12  val12
-name13  val13  val13
-name14  val14  val14
-name15  val15  val15
-name16  val16  val16
-name17  val17  val17
-name18  val18  val18
-name19  val19  val19
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name00  1     2010-01-01  val00  val00
+name01  1     2010-01-01  val01  val01
+name02  1     2010-01-01  val02  val02
+name03  1     2010-01-01  val03  val03
+name04  1     2010-01-01  val04  val04
+name05  1     2010-01-01  val05  val05
+name06  1     2010-01-01  val06  val06
+name07  1     2010-01-01  val07  val07
+name08  1     2010-01-01  val08  val08
+name09  1     2010-01-01  val09  val09
+name10  0     2010-01-01  val10  val10
+name11  0     2010-01-01  val11  val11
+name12  0     2010-01-01  val12  val12
+name13  0     2010-01-01  val13  val13
+name14  0     2010-01-01  val14  val14
+name15  0     2010-01-01  val15  val15
+name16  0     2010-01-01  val16  val16
+name17  0     2010-01-01  val17  val17
+name18  0     2010-01-01  val18  val18
+name19  0     2010-01-01  val19  val19
 
 ===== test_session_list_authenticated_sid =====
 
-SID     Name   Email
---------------------
-name00  val00  val00
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name00  1     2010-01-01  val00  val00
 
 ===== test_session_list_anonymous_sid =====
 
-SID     Name   Email
---------------------
-name10  val10  val10
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name10  0     2010-01-01  val10  val10
 
 ===== test_session_list_missing_sid =====
-No SID found
+
+SID  Auth  Last Visit  Name  Email
+----------------------------------
+
 ===== test_session_add_missing_sid =====
 Error: Invalid arguments
 
-session add <sid> [name] [email]
+session add <sid[:0|1]> [name] [email]
 
     Create a session for the given sid
 
-    Populates the name and email attributes and sets the session as
-    authenticated.
+    Populates the name and email attributes for the given session. Adding a
+    suffix ':0' to the sid makes the session unauthenticated, and a suffix ':1'
+    makes it authenticated (the default if no suffix is specified).
 
 ===== test_session_add_duplicate_sid =====
-Error: Session already exists. Unable to add a duplicate session.
+Error: Session 'name00' already exists
 ===== test_session_add_sid_all =====
 
-SID   Name  Email
-----------------------------
-john  John  john@example.org
+SID   Auth  Last Visit  Name  Email
+----------------------------------------------
+john  1     %(today)s  John  john@example.org
 
 ===== test_session_add_sid =====
 
-SID   Name  Email
------------------
-john
+SID   Auth  Last Visit  Name  Email
+-----------------------------------
+john  1     %(today)s
 
 ===== test_session_add_sid_name =====
 
-SID   Name  Email
------------------
-john  John
+SID   Auth  Last Visit  Name  Email
+-----------------------------------
+john  1     %(today)s  John
 
 ===== test_session_set_attr_name =====
 
-SID   Name  Email
-----------------------------
-john  JOHN  john@example.org
+SID     Auth  Last Visit  Name  Email
+-------------------------------------
+name00  1     2010-01-01  JOHN  val00
 
 ===== test_session_set_attr_email =====
 
-SID   Name  Email
-----------------------------
-john  John  JOHN@EXAMPLE.ORG
+SID     Auth  Last Visit  Name   Email
+-------------------------------------------------
+name00  1     2010-01-01  val00  JOHN@EXAMPLE.ORG
 
 ===== test_session_set_attr_missing_attr =====
 Error: Invalid arguments
 
-session set <name|email> <sid> <value>
+session set <name|email> <sid[:0|1]> <value>
 
     Set the name or email attribute of the given sid
+    
+    An sid suffix ':0' operates on an unauthenticated session with the given
+    sid, and a suffix ':1' on an authenticated session (the default).
 
 ===== test_session_set_attr_missing_value =====
 Error: Invalid arguments
 
-session set <name|email> <sid> <value>
+session set <name|email> <sid[:0|1]> <value>
 
     Set the name or email attribute of the given sid
+    
+    An sid suffix ':0' operates on an unauthenticated session with the given
+    sid, and a suffix ':1' on an authenticated session (the default).
 
 ===== test_session_set_attr_missing_sid =====
 Error: Invalid arguments
 
-session set <name|email> <sid> <value>
+session set <name|email> <sid[:0|1]> <value>
 
     Set the name or email attribute of the given sid
+    
+    An sid suffix ':0' operates on an unauthenticated session with the given
+    sid, and a suffix ':1' on an authenticated session (the default).
 
 ===== test_session_set_attr_nonexistent_sid =====
-Error: Unable to set session attribute on a non-existent SID
+Error: Session 'john' not found
 ===== test_session_delete_sid =====
-No SID found
+
+SID  Auth  Last Visit  Name  Email
+----------------------------------
+
 ===== test_session_delete_missing_params =====
 ===== test_session_delete_anonymous =====
 
-SID     Name   Email
---------------------
-name00  val00  val00
-name01  val01  val01
-name02  val02  val02
-name03  val03  val03
-name04  val04  val04
-name05  val05  val05
-name06  val06  val06
-name07  val07  val07
-name08  val08  val08
-name09  val09  val09
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name00  1     2010-01-01  val00  val00
+name01  1     2010-01-01  val01  val01
+name02  1     2010-01-01  val02  val02
+name03  1     2010-01-01  val03  val03
+name04  1     2010-01-01  val04  val04
+name05  1     2010-01-01  val05  val05
+name06  1     2010-01-01  val06  val06
+name07  1     2010-01-01  val07  val07
+name08  1     2010-01-01  val08  val08
+name09  1     2010-01-01  val09  val09
 
-===== test_session_delete_all =====
-No SID found
 ===== test_session_delete_multiple_sids =====
 
-SID     Name   Email
---------------------
-name04  val04  val04
-name05  val05  val05
-name06  val06  val06
-name07  val07  val07
-name08  val08  val08
-name09  val09  val09
-name10  val10  val10
-name11  val11  val11
-name12  val12  val12
-name13  val13  val13
-name14  val14  val14
-name15  val15  val15
-name16  val16  val16
-name17  val17  val17
-name18  val18  val18
-name19  val19  val19
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name04  1     2010-01-01  val04  val04
+name05  1     2010-01-01  val05  val05
+name06  1     2010-01-01  val06  val06
+name07  1     2010-01-01  val07  val07
+name08  1     2010-01-01  val08  val08
+name09  1     2010-01-01  val09  val09
+name10  0     2010-01-01  val10  val10
+name11  0     2010-01-01  val11  val11
+name12  0     2010-01-01  val12  val12
+name13  0     2010-01-01  val13  val13
+name14  0     2010-01-01  val14  val14
+name15  0     2010-01-01  val15  val15
+name16  0     2010-01-01  val16  val16
+name17  0     2010-01-01  val17  val17
+name18  0     2010-01-01  val18  val18
+name19  0     2010-01-01  val19  val19
 
 ===== test_session_purge_all =====
 
 
 ===== test_session_purge_age =====
 
-SID     Name   Email
---------------------
-name00  val00  val00
-name01  val01  val01
-name02  val02  val02
-name03  val03  val03
-name04  val04  val04
-name05  val05  val05
-name06  val06  val06
-name07  val07  val07
-name08  val08  val08
-name09  val09  val09
-name11  val11  val11
-name12  val12  val12
-name13  val13  val13
-name14  val14  val14
-name15  val15  val15
-name16  val16  val16
-name17  val17  val17
-name18  val18  val18
-name19  val19  val19
+SID     Auth  Last Visit  Name   Email
+--------------------------------------
+name00  1     2010-01-01  val00  val00
+name01  1     2010-01-02  val01  val01
+name02  1     2010-01-03  val02  val02
+name03  1     2010-01-04  val03  val03
+name04  1     2010-01-05  val04  val04
+name05  1     2010-01-06  val05  val05
+name06  1     2010-01-07  val06  val06
+name07  1     2010-01-08  val07  val07
+name08  1     2010-01-09  val08  val08
+name09  1     2010-01-10  val09  val09
+name11  0     2010-01-12  val11  val11
+name12  0     2010-01-13  val12  val12
+name13  0     2010-01-14  val13  val13
+name14  0     2010-01-15  val14  val14
+name15  0     2010-01-16  val15  val15
+name16  0     2010-01-17  val16  val16
+name17  0     2010-01-18  val17  val17
+name18  0     2010-01-19  val18  val18
+name19  0     2010-01-20  val19  val19
 

trac/admin/tests/console.py

 import unittest
 from StringIO import StringIO
 
+# IAdminCommandProvider implementations
+import trac.admin.api
+import trac.attachment
+import trac.perm
+import trac.ticket.admin
+import trac.versioncontrol.admin
+import trac.versioncontrol.api
+import trac.versioncontrol.web_ui
+import trac.wiki.admin
+
+# IPermissionRequestor implementations (for 'permission' related tests)
+import trac.about
+import trac.admin.web_ui
+import trac.config
+import trac.ticket.api
+import trac.ticket.report
+import trac.ticket.roadmap
+import trac.ticket.web_ui
+import trac.search.web_ui
+import trac.timeline.web_ui
+import trac.wiki.web_ui
+
 from trac.config import Configuration
 from trac.env import Environment
-from trac.admin import console
+from trac.admin import console, console_date_format
 from trac.test import InMemoryDatabase
-from trac.util.datefmt import get_date_format_hint
+from trac.util.datefmt import format_date, get_date_format_hint
 from trac.web.tests.session import _prep_session_table
 
 STRIP_TRAILING_SPACE = re.compile(r'( +)$', re.MULTILINE)
         test passes valid arguments and checks for success.
         """
         test_name = sys._getframe().f_code.co_name
-        self._execute(u'milestone add \xe9tat_final "%s"'  #\xc3\xa9
+        self._execute(u'milestone add \xa9tat_final "%s"'  #\xc2\xa9
                               % self._test_date)
         rv, output = self._execute('milestone list')
         self.assertEqual(0, rv)
     def test_session_list_anonymous_sid(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env.get_db_cnx())
-        rv, output = self._execute('session list name10')
+        rv, output = self._execute('session list name10:0')
         self.assertEqual(0, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
         rv, output = self._execute('session add john John john@example.org')
         self.assertEqual(0, rv)
         rv, output = self._execute('session list john')
-        self.assertEqual(self.expected_results[test_name], output)
+        self.assertEqual(self.expected_results[test_name]
+                         % {'today': format_date(None, console_date_format)},
+                         output)
 
     def  test_session_add_sid(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session add john')
         self.assertEqual(0, rv)
         rv, output = self._execute('session list john')
-        self.assertEqual(self.expected_results[test_name], output)
+        self.assertEqual(self.expected_results[test_name]
+                         % {'today': format_date(None, console_date_format)},
+                         output)
 
     def  test_session_add_sid_name(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session add john John')
         self.assertEqual(0, rv)
         rv, output = self._execute('session list john')
-        self.assertEqual(self.expected_results[test_name], output)
+        self.assertEqual(self.expected_results[test_name]
+                         % {'today': format_date(None, console_date_format)},
+                         output)
 
     def  test_session_set_attr_name(self):
         test_name = sys._getframe().f_code.co_name
-        self._execute('session add john John john@example.org')
-        rv, output = self._execute('session set name john JOHN')
+        _prep_session_table(self.env.get_db_cnx())
+        rv, output = self._execute('session set name name00 JOHN')
         self.assertEqual(0, rv)
-        rv, output = self._execute('session list john')
+        rv, output = self._execute('session list name00')
         self.assertEqual(self.expected_results[test_name], output)
 
     def  test_session_set_attr_email(self):
         test_name = sys._getframe().f_code.co_name
-        self._execute('session add john John john@example.org')
-        rv, output = self._execute('session set email john JOHN@EXAMPLE.ORG')
+        _prep_session_table(self.env.get_db_cnx())
+        rv, output = self._execute('session set email name00 JOHN@EXAMPLE.ORG')
         self.assertEqual(0, rv)
-        rv, output = self._execute('session list john')
+        rv, output = self._execute('session list name00')
         self.assertEqual(self.expected_results[test_name], output)
 
     def  test_session_set_attr_missing_attr(self):
         rv, output = self._execute('session list *')
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_delete_all(self):
-        test_name = sys._getframe().f_code.co_name
-        _prep_session_table(self.env.get_db_cnx())
-        if self._admin.interactive:
-            rv, output = self._execute("session delete *")
-        else:
-            rv, output = self._execute("session delete '*'")
-        self.assertEqual(0, rv)
-        rv, output = self._execute('session list *')
-        self.assertEqual(self.expected_results[test_name], output)
-
     def  test_session_purge_age(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env.get_db_cnx(), spread_visits=True)

trac/admin/web_ui.py

         all_actions = perm.get_actions()
 
         if req.method == 'POST':
-            subject = req.args.get('subject', '')
+            subject = req.args.get('subject', '').strip()
             action = req.args.get('action')
-            group = req.args.get('group', '')
+            group = req.args.get('group', '').strip()
 
             if subject and subject.isupper() or \
                    group and group.isupper():

trac/attachment.py

 from trac.util import get_reporter_id, create_unique_file
 from trac.util.datefmt import format_datetime, from_utimestamp, \
                               to_datetime, to_utimestamp, utc
-from trac.util.text import exception_to_unicode, pretty_size, print_table, \
-                           unicode_quote, unicode_unquote
+from trac.util.text import exception_to_unicode, path_to_unicode, \
+                           pretty_size, print_table, unicode_quote, \
+                           unicode_unquote
 from trac.util.translation import _, tag_
 from trac.web import HTTPBadRequest, IRequestHandler
 from trac.web.chrome import add_link, add_stylesheet, add_ctxtnav, \
         def do_reparent(db):
             cursor = db.cursor()
             new_path = self._get_path(new_realm, new_id, self.filename)
+
+            # Make sure the path to the attachment is inside the environment
+            # attachments directory
+            attachments_dir = os.path.join(os.path.normpath(self.env.path),
+                                           'attachments')
+            commonprefix = os.path.commonprefix([attachments_dir, new_path])
+            if commonprefix != attachments_dir:
+                raise TracError(_('Cannot reparent attachment "%(att)s" as '
+                                  '%(realm)s:%(id)s is invalid', 
+                                  att=self.filename, realm=new_realm,
+                                  id=new_id))
+
             if os.path.exists(new_path):
                 raise TracError(_('Cannot reparent attachment "%(att)s" as '
                                   'it already exists in %(realm)s:%(id)s', 
                 listener.attachment_reparented(self, old_realm, old_id)
 
     def insert(self, filename, fileobj, size, t=None, db=None):
+        self.filename = None
         self.size = size and int(size) or 0
         if t is None:
             t = datetime.now(utc)
         attachments_dir = os.path.join(os.path.normpath(self.env.path),
                                        'attachments')
         commonprefix = os.path.commonprefix([attachments_dir, self.path])
-        assert commonprefix == attachments_dir
+        if commonprefix != attachments_dir:
+            raise TracError(_('Cannot create attachment "%(att)s" as '
+                              '%(realm)s:%(id)s is invalid', 
+                              att=filename, realm=self.parent_realm,
+                              id=self.parent_id))
 
         if not os.access(self.path, os.F_OK):
             os.makedirs(self.path)
         if not filename:
             raise TracError(_('No file uploaded'))
         # Now the filename is known, update the attachment resource
-        # attachment.filename = filename
+        attachment.filename = filename
         attachment.description = req.args.get('description', '')
         attachment.author = get_reporter_id(req, 'author')
         attachment.ipnr = req.remote_addr
                 old_attachment.delete()
             except TracError:
                 pass # don't worry if there's nothing to replace
-            attachment.filename = None
         attachment.insert(filename, upload.file, size)
 
         req.redirect(get_resource_url(self.env, attachment.resource(id=None),
                 destination = os.path.join(destination, name)
             if os.path.isfile(destination):
                 raise AdminCommandError(_("File '%(name)s' exists",
-                                          name=destination))
+                                          name=path_to_unicode(destination)))
         input = attachment.open()
         try:
             output = (destination is None) and sys.stdout \
     
     def __init__(self, retriever, id_attr=None):
         self.retriever = retriever
+        self.__doc__ = retriever.__doc__
         self.id_attr = id_attr
-        self.__doc__ = retriever.__doc__
+        self.id = None
         
     def __get__(self, instance, owner):
         if instance is None:
         if self.id_attr is not None:
             id = getattr(instance, self.id_attr)
         else:
-            id = "%s.%s.%s" % (owner.__module__,
-                               owner.__name__,
-                               self.retriever.__name__)
+            id = self.id
+            if id is None:
+                id = self.id = self.make_id(owner)
         return CacheManager(instance.env).get(id, self.retriever, instance)
         
     def __delete__(self, instance):
         if self.id_attr is not None:
             id = getattr(instance, self.id_attr)
         else:
-            id = '%s.%s.%s' % (instance.__class__.__module__,
-                               instance.__class__.__name__,
-                               self.retriever.__name__)
+            id = self.id
+            if id is None:
+                id = self.id = self.make_id(instance.__class__)
         CacheManager(instance.env).invalidate(id)
 
+    def make_id(self, cls):
+        attr = self.retriever.__name__
+        for base in cls.mro():
+            if base.__dict__.get(attr) is self:
+                cls = base
+                break
+        return '%s.%s.%s' % (cls.__module__, cls.__name__, attr)
+
 
 def cached(fn_or_id=None):
     """Method decorator creating a cached attribute from a data retrieval
 from copy import deepcopy
 import os.path
 
-from trac.admin import IAdminCommandProvider
+from trac.admin import AdminCommandError, IAdminCommandProvider
 from trac.core import *
-from trac.util import AtomicFile
+from trac.util import AtomicFile, as_bool
+from trac.util.compat import any
 from trac.util.text import printout, to_unicode, CRLF
 from trac.util.translation import _, N_
 
            'ListOption', 'ChoiceOption', 'PathOption', 'ExtensionOption',
            'OrderedExtensionsOption', 'ConfigurationError']
 
+# Retained for backward-compatibility, use as_bool() instead
 _TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True)
 
 _use_default = object()
         if force or modtime > self._lastmtime:
             self._sections = {}
             self.parser._sections = {}
-            self.parser.read(self.filename)
+            if not self.parser.read(self.filename):
+                raise TracError(_("Error reading '%(file)s', make sure it is "
+                                  "readable.", file=self.filename))
             self._lastmtime = modtime
             self._old_sections = deepcopy(self.parser._sections)
             changed = True
         """Return the value of the specified option as boolean.
         
         This method returns `True` if the option value is one of "yes", "true",
-        "enabled", "on", or "1", ignoring case. Otherwise `False` is returned.
+        "enabled", "on", or non-zero numbers, ignoring case. Otherwise `False`
+        is returned.
 
         Valid default input is a string or a bool. Returns a bool.
         """
-        value = self.get(key, default)
-        if isinstance(value, basestring):
-            value = value.lower() in _TRUE_VALUES
-        return bool(value)
+        return as_bool(self.get(key, default))
 
     def getint(self, key, default=''):
         """Return the value of the specified option as integer.
         yield ('config set', '<section> <option> <value>',
                'Set the value for the given option in "trac.ini"',
                self._complete_config, self._do_set)
-    
+
     def _complete_config(self, args):
         if len(args) == 1:
             return self.config.sections()
             return [name for (name, value) in self.config[args[0]].options()]
 
     def _do_get(self, section, option):
+        if not self.config.has_option(section, option):
+            raise AdminCommandError(
+                _("Option '%(option)s' doesn't exist in section '%(section)s'",
+                  option=option, section=section))
         printout(self.config.get(section, option))
-        
+
     def _do_set(self, section, option, value):
         self.config.set(section, option, value)
         self.config.save()
             self.config.parse_if_needed(force=True) # Full reload
 
     def _do_remove(self, section, option):
+        if not self.config.has_option(section, option):
+            raise AdminCommandError(
+                _("Option '%(option)s' doesn't exist in section '%(section)s'",
+                  option=option, section=section))
         self.config.remove(section, option)
         self.config.save()
         if section == 'inherit' and option == 'file':

File contents unchanged.

         connector, args = self.get_connector()
         if not dest:
             backup_dir = self.backup_dir
-            if backup_dir[0] != "/":
+            if not os.path.isabs(backup_dir):
                 backup_dir = os.path.join(self.env.path, backup_dir)
             db_str = self.config.get('trac', 'database')
             db_name, db_path = db_str.split(":", 1)

trac/db/mysql_backend.py

 from trac.db.util import ConnectionWrapper, IterableCursor
 from trac.util import get_pkginfo
 from trac.util.compat import close_fds
-from trac.util.text import to_unicode
+from trac.util.text import exception_to_unicode, to_unicode
 from trac.util.translation import _
 
 _like_escape_re = re.compile(r'([/_%])')
                           for each in alterations))
 
     def backup(self, dest_file):
-        try:
-            from subprocess import Popen, PIPE
-        except ImportError:
-            raise TracError('Python >= 2.4 or the subprocess module '
-                            'is required for pre-upgrade backup support')
+        from subprocess import Popen, PIPE
         db_url = self.env.config.get('trac', 'database')
         scheme, db_prop = _parse_db_str(db_url)
         db_name = os.path.basename(db_prop['path'])
         environ = os.environ.copy()
         if 'password' in db_prop:
             environ['MYSQL_PWD'] = str(db_prop['password'])
-        p = Popen(args, env=environ, stderr=PIPE, close_fds=close_fds)
+        try:
+            p = Popen(args, env=environ, stderr=PIPE, close_fds=close_fds)
+        except OSError, e:
+            raise TracError(_("Unable to run %(path)s: %(msg)s",
+                              path=self.pg_dump_path,
+                              msg=exception_to_unicode(e)))
         errmsg = p.communicate()[1]
         if p.returncode != 0:
-            raise TracError("Backup attempt failed (%s)" % to_unicode(errmsg))
+            raise TracError(_("mysqldump failed: %(msg)s",
+                              msg=to_unicode(errmsg.strip())))
         if not os.path.exists(dest_file):
-            raise TracError("Backup attempt failed")
+            raise TracError(_("No destination file created"))
         return dest_file
 
 
         self._is_closed = False
 
     def cast(self, column, type):
-        if type == 'int'or type == 'int64':
+        if type == 'int' or type == 'int64':
             type = 'signed'
         elif type == 'text':
             type = 'char'
 
     def quote(self, identifier):
         """Return the quoted identifier."""
-        return "`%s`" % identifier
+        return "`%s`" % identifier.replace('`', '``')
 
     def get_last_id(self, cursor, table, column='id'):
         return cursor.lastrowid
 
+    def update_sequence(self, cursor, table, column='id'):
+        # MySQL handles sequence updates automagically
+        pass
+
     def rollback(self):
         self.cnx.ping()
         try:
 
 from trac.db.util import ConnectionWrapper
 from trac.util.concurrency import threading
+from trac.util.text import exception_to_unicode
 from trac.util.translation import _
 
 
         self._pool = []
         self._pool_key = []
         self._pool_time = []
+        self._waiters = 0
 
     def get_cnx(self, connector, kwargs, timeout=None):
-        num = 1
         cnx = None
         log = kwargs.get('log')
         key = unicode(kwargs)
         start = time.time()
         tid = threading._get_ident()
+        # Get a Connection, either directly or a deferred one
         self._available.acquire()
         try:
-            while True:
-                # First choice: Return the same cnx already used by the thread
-                if (tid, key) in self._active:
-                    cnx, num = self._active[(tid, key)]
-                    num += 1
-                # Second best option: Reuse a live pooled connection
-                elif key in self._pool_key:
-                    idx = self._pool_key.index(key)
-                    self._pool_key.pop(idx)
-                    self._pool_time.pop(idx)
-                    cnx = self._pool.pop(idx)
-                    # If possible, verify that the pooled connection is
-                    # still available and working.
-                    if hasattr(cnx, 'ping'):
-                        try:
-                            cnx.ping()
-                        except:
-                            continue
-                # Third best option: Create a new connection
-                elif len(self._active) + len(self._pool) < self._maxsize:
-                    cnx = connector.get_connection(**kwargs)
-                # Forth best option: Replace a pooled connection with a new one
-                elif len(self._active)  < self._maxsize:
-                    # Remove the LRU connection in the pool
-                    self._pool.pop(0).close()
-                    self._pool_key.pop(0)
-                    self._pool_time.pop(0)
-                    cnx = connector.get_connection(**kwargs)
-                if cnx:
-                    self._active[(tid, key)] = (cnx, num)
-                    return PooledConnection(self, cnx, key, tid, log)
-                # Worst option: wait until a connection pool slot is available
-                if timeout and (time.time() - start) > timeout:
-                    raise TimeoutError(_('Unable to get database '
-                                         'connection within %(time)d '
-                                         'seconds', time=timeout))
-                elif timeout:
-                    self._available.wait(timeout)
-                else:
+            # First choice: Return the same cnx already used by the thread
+            if (tid, key) in self._active:
+                cnx, num = self._active[(tid, key)]
+                num += 1
+            else:
+                if self._waiters == 0:
+                    cnx = self._take_cnx(connector, kwargs, key, tid)
+                if not cnx:
+                    self._waiters += 1
                     self._available.wait()
+                    self._waiters -= 1
+                    cnx = self._take_cnx(connector, kwargs, key, tid)
+                num = 1
+            if cnx:
+                self._active[(tid, key)] = (cnx, num)
         finally:
             self._available.release()
 
+        deferred = num == 1 and isinstance(cnx, tuple)
+        err = None
+        if deferred:
+            # Potentially lenghty operations must be done without lock held
+            op, cnx = cnx
+            try:
+                if op == 'ping':
+                    cnx.ping()
+                elif op == 'close':
+                    cnx.close()
+                if op in ('close', 'create'):
+                    cnx = connector.get_connection(**kwargs)
+            except Exception, e:
+                err = e
+                cnx = None
+        
+        if cnx:
+            if deferred:
+                # replace placeholder with real Connection
+                self._available.acquire()
+                try:
+                    self._active[(tid, key)] = (cnx, num)
+                finally:
+                    self._available.release()
+            return PooledConnection(self, cnx, key, tid, log)
+
+        if deferred:
+            # cnx couldn't be reused, clear placeholder
+            self._available.acquire()
+            try:
+                del self._active[(tid, key)]
+            finally:
+                self._available.release()
+            if op == 'ping': # retry
+                return self.get_cnx(connector, kwargs)
+
+        # if we didn't get a cnx after wait(), something's fishy...
+        timeout = time.time() - start
+        errmsg = _("Unable to get database connection within %(time)d seconds.",
+                   time=timeout)
+        if err:
+            errmsg += " (%s)" % exception_to_unicode(err)
+        raise TimeoutError(errmsg)
+
+    def _take_cnx(self, connector, kwargs, key, tid):
+        """Note: _available lock must be held when calling this method."""
+        # Second best option: Reuse a live pooled connection
+        if key in self._pool_key:
+            idx = self._pool_key.index(key)
+            self._pool_key.pop(idx)
+            self._pool_time.pop(idx)
+            cnx = self._pool.pop(idx)
+            # If possible, verify that the pooled connection is
+            # still available and working.
+            if hasattr(cnx, 'ping'):
+                return ('ping', cnx)
+            return cnx
+        # Third best option: Create a new connection
+        elif len(self._active) + len(self._pool) < self._maxsize:
+            return ('create', None)
+        # Forth best option: Replace a pooled connection with a new one
+        elif len(self._active) < self._maxsize: