Christian Boos avatar Christian Boos committed 0184464

1.1.2dev: merge changes from [11757:11778/branches/1.0-stable]

... leaving the l10n ones for another day.

Comments (0)

Files changed (24)

trac/admin/templates/admin_plugins.html

                 </p>
                 <div py:if="module.description" xml:space="preserve">${safe_wiki_to_html(context, module.description)}</div>
               </td>
-              <td class="sel"></td>
+              <td class="sel trac-module"></td>
             </tr>
             <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}"
                 </p>
                 <div py:if="component.description" xml:space="preserve">${safe_wiki_to_html(context, component.description)}</div>
               </td>
-              <td class="sel">
+              <td class="sel trac-component">
                 <input py:if="not component.required" type="hidden" name="component"
                        value="${module_name}.${component_name}" />
                 <input type="checkbox" name="enable"
         try:
             with AtomicFile(self.filename, 'w') as fileobj:
                 fileobj.write('# -*- coding: utf-8 -*-\n\n')
-                for section, options in sections:
-                    fileobj.write('[%s]\n' % section)
+                for section_str, options in sections:
+                    fileobj.write('[%s]\n' % section_str)
+                    section = to_unicode(section_str)
                     for key_str, val_str in options:
                         if to_unicode(key_str) in self[section].overridden:
                             fileobj.write('# %s = <inherited>\n' % key_str)
 
         Values already set in the configuration are not overridden.
         """
-        for section, default_options in self.defaults(compmgr).items():
-            for name, value in default_options.items():
-                if not self.parser.has_option(_to_utf8(section),
-                                              _to_utf8(name)):
-                    if any(parent[section].contains(name, defaults=False)
-                           for parent in self.parents):
-                        value = None
-                    self.set(section, name, value)
+        for (section, name), option in Option.get_registry(compmgr).items():
+            if not self.parser.has_option(_to_utf8(section), _to_utf8(name)):
+                value = option.default
+                if any(parent[section].contains(name, defaults=False)
+                       for parent in self.parents):
+                    value = None
+                if value is not None:
+                    value = option.dumps(value)
+                self.set(section, name, value)
 
 
 class Section(object):
         return '<%s [%s] "%s">' % (self.__class__.__name__, self.section,
                                    self.name)
 
+    def dumps(self, value):
+        """Return the value as a string to write to a trac.ini file"""
+        if value is None:
+            return ''
+        if value is True:
+            return 'enabled'
+        if value is False:
+            return 'disabled'
+        if isinstance(value, unicode):
+            return value
+        return to_unicode(value)
+
 
 class BoolOption(Option):
     """Descriptor for boolean configuration options."""
     def accessor(self, section, name, default):
         return section.getlist(name, default, self.sep, self.keep_empty)
 
+    def dumps(self, value):
+        if isinstance(value, (list, tuple)):
+            return self.sep.join(Option.dumps(self, v) or '' for v in value)
+        return Option.dumps(self, value)
+
 
 class ChoiceOption(Option):
     """Descriptor for configuration options providing a choice among a list
         """Activate the component instance for the given class, or
         return the existing instance if the component has already been
         activated.
+
+        Note that `ComponentManager` components can't be activated
+        that way.
         """
         if not self.is_enabled(cls):
             return None
         component = self.components.get(cls)
-        if not component:
+        if not component and not issubclass(cls, ComponentManager):
             if cls not in ComponentMeta._components:
                 raise TracError('Component "%s" not registered' % cls.__name__)
             try:

trac/htdocs/css/admin.css

 .plugin .info dd { padding: 0; margin: 0; }
 .plugin .listing { width: 100%; }
 .plugin .listing th.sel input { margin-right: 0.5em; vertical-align: bottom; }
+.plugin .listing td.trac-module { background: #fcfcfc; }
 .plugin .listing td { background: #fff; }
 .trac-heading { margin: 0; }
 .trac-name { font-family: monospace; }

trac/htdocs/css/trac.css

 div.compact > p:first-child { margin-top: 0 }
 div.compact > p:last-child { margin-bottom: 0 }
 
+/* Styles related to RTL support */
+.rtl { direction: rtl; }
+.rtl div.wiki-toc { float: left; }
+.rtl .wiki-toc ul ul, .wiki-toc ol ol { padding-right: 1.2em }
+
 a.missing:link, a.missing:visited, a.missing, span.missing,
 a.forbidden, span.forbidden { color: #998 }
 a.missing:hover { color: #000 }
 table.listing tbody tr { border-top: 1px solid #ddd }
 table.listing tbody tr.even { background-color: #fcfcfc }
 table.listing tbody tr.odd { background-color: #f7f7f7 }
-table.listing tbody tr:hover { background: #eed !important }
+table.listing tbody tr:hover td { background: #eed !important }
 table.listing tbody tr.focus { background: #ddf !important }
 
 table.listing pre { white-space: pre-wrap }

trac/htdocs/css/wiki.css

  div.trac-modifiedby span.trac-print { display: block; }
 }
 
-/* Styles related to RTL support */
-.rtl { direction: rtl; }
-.rtl div.wiki-toc { float: left; }
-.rtl .wiki-toc ul ul, .wiki-toc ol ol { padding-right: 1.2em }
-
 /* TracIni default value */
 div.tracini td.default { font-size: 90% }
 div.tracini td.nodefault {

trac/tests/config.py

             os.remove(site1)
             os.rmdir(os.path.dirname(site1))
 
+    def test_option_with_raw_default(self):
+        class Foo(object):
+            # enclose in parentheses to avoid messages extraction
+            option_none = (Option)('a', 'none', None)
+            option_blah = (Option)('a', 'blah', u'Blàh!')
+            option_true = (BoolOption)('a', 'true', True)
+            option_false = (BoolOption)('a', 'false', False)
+            option_list = (ListOption)('a', 'list', ['#cc0', 4.2, 42L, 0, None,
+                                                     True, False, None],
+                                       sep='|')
+            option_choice = (ChoiceOption)('a', 'choice', [-42, 42])
+
+        config = self._read()
+        config.set_defaults()
+        config.save()
+        with open(self.filename, 'r') as f:
+            self.assertEquals('# -*- coding: utf-8 -*-\n',            f.next())
+            self.assertEquals('\n',                                   f.next())
+            self.assertEquals('[a]\n',                                f.next())
+            self.assertEquals('blah = Blàh!\n',                       f.next())
+            self.assertEquals('choice = -42\n',                       f.next())
+            self.assertEquals('false = disabled\n',                   f.next())
+            self.assertEquals('list = #cc0|4.2|42|0||enabled|disabled|\n',
+                              f.next())
+            self.assertEquals('# none = <inherited>\n',               f.next())
+            self.assertEquals('true = enabled\n',                     f.next())
+            self.assertEquals('\n',                                   f.next())
+            self.assertRaises(StopIteration, f.next)
+
+    def test_unicode_option_with_raw_default(self):
+        class Foo(object):
+            # enclose in parentheses to avoid messages extraction
+            option_none = (Option)(u'résumé', u'nöné', None)
+            option_blah = (Option)(u'résumé', u'bláh', u'Blàh!')
+            option_true = (BoolOption)(u'résumé', u'trüé', True)
+            option_false = (BoolOption)(u'résumé', u'fálsé', False)
+            option_list = (ListOption)(u'résumé', u'liśt',
+                                       [u'#ccö', 4.2, 42L, 0, None, True,
+                                        False, None],
+                                       sep='|')
+            option_choice = (ChoiceOption)(u'résumé', u'chöicé', [-42, 42])
+
+        config = self._read()
+        config.set_defaults()
+        config.save()
+        with open(self.filename, 'r') as f:
+            self.assertEquals('# -*- coding: utf-8 -*-\n',            f.next())
+            self.assertEquals('\n',                                   f.next())
+            self.assertEquals('[résumé]\n',                           f.next())
+            self.assertEquals('bláh = Blàh!\n',                       f.next())
+            self.assertEquals('chöicé = -42\n',                       f.next())
+            self.assertEquals('fálsé = disabled\n',                   f.next())
+            self.assertEquals('liśt = #ccö|4.2|42|0||enabled|disabled|\n',
+                              f.next())
+            self.assertEquals('# nöné = <inherited>\n',               f.next())
+            self.assertEquals('trüé = enabled\n',                     f.next())
+            self.assertEquals('\n',                                   f.next())
+            self.assertRaises(StopIteration, f.next)
+
     def _test_with_inherit(self, testcb):
         sitename = os.path.join(tempfile.gettempdir(), 'trac-site.ini')
         try:

trac/tests/core.py

 # Author: Christopher Lenz <cmlenz@gmx.de>
 
 from trac.core import *
+from trac.core import ComponentManager
 
 import unittest
 
         self.assertEquals('x', tests.next().test())
         self.assertRaises(StopIteration, tests.next)
 
+    def test_component_manager_component_isolation(self):
+        """
+        Verify that a component manager that is also a component will only
+        be listed in extension points for components instantiated in
+        its scope.
+
+        See bh:comment:5:ticket:438 and #11121
+        """
+        class ManagerComponentA(ComponentManager, Component):
+            implements(ITest)
+            def test(self):
+                pass
+
+        class ManagerComponentB(ManagerComponentA):
+            pass
+
+        class Tester(Component):
+            tests = ExtensionPoint(ITest)
+
+        mgrA = ManagerComponentA()
+        mgrB = ManagerComponentB()
+
+        self.assertEquals([mgrA], Tester(mgrA).tests)
+        self.assertEquals([mgrB], Tester(mgrB).tests)
+
     def test_instantiation_doesnt_enable(self):
         """
         Make sure that a component disabled by the ComponentManager is not
         implicitly enabled by instantiating it directly.
         """
-        from trac.core import ComponentManager
         class DisablingComponentManager(ComponentManager):
             def is_component_enabled(self, cls):
                 return False

trac/tests/functional/svntestenv.py

+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2003-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
 import os
 import re
 from subprocess import call
             raise Exception(*args)
         return int(revision)
 
+    def call_in_workdir(self, args, environ=None):
+        return self.call_in_dir(self.work_dir(), args, environ)

trac/tests/functional/testenv.py

-#!/usr/bin/python
 # -*- coding: utf-8 -*-
 #
+# Copyright (C) 2003-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
 """Object for creating and destroying a Trac environment for testing purposes.
 Provides some Trac environment-wide utility functions, and a way to call
 :command:`trac-admin` without it being on the path."""
 from trac.tests.functional.compat import rmtree
 from trac.tests.functional import logfile
 from trac.tests.functional.better_twill import tc, ConnectError
+from trac.util import terminate
 from trac.util.compat import close_fds
 from trac.util.text import to_utf8
 
                  cwd=self.command_cwd):
             raise Exception('Unable to setup admin password')
         self.adduser('user')
-        self._tracadmin('permission', 'add', 'admin', 'TRAC_ADMIN')
+        self.grant_perm('admin', 'TRAC_ADMIN')
         # Setup Trac logging
         env = self.get_trac_environment()
         env.config.set('logging', 'log_type', 'file')
                  user, user], close_fds=close_fds, cwd=self.command_cwd):
             raise Exception('Unable to setup password for user "%s"' % user)
 
+    def grant_perm(self, user, perm):
+        """Grant permission(s) to specified user. A single permission may
+        be specified as a string, or multiple permissions may be
+        specified as a list or tuple of strings."""
+        if isinstance(perm, (list, tuple)):
+            self._tracadmin('permission', 'add', user, *perm)
+        else:
+            self._tracadmin('permission', 'add', user, perm)
+        # We need to force an environment reset, as this is necessary
+        # for the permission change to take effect: grant only
+        # invalidates the `DefaultPermissionStore._all_permissions`
+        # cache, but the `DefaultPermissionPolicy.permission_cache` is
+        # unaffected.
+        self.get_trac_environment().config.touch()
+
+    def revoke_perm(self, user, perm):
+        """Revoke permission(s) from specified user. A single permission
+        may be specified as a string, or multiple permissions may be
+        specified as a list or tuple of strings."""
+        if isinstance(perm, (list, tuple)):
+            self._tracadmin('permission', 'remove', user, *perm)
+        else:
+            self._tracadmin('permission', 'remove', user, perm)
+        # Force an environment reset (see grant_perm above)
+        self.get_trac_environment().config.touch()
+
     def _tracadmin(self, *args):
         """Internal utility method for calling trac-admin"""
         proc = Popen([sys.executable, os.path.join(self.trac_src, 'trac',
         if proc.returncode:
             print(out)
             logfile.write(out)
-            raise Exception('Failed with exitcode %s running trac-admin ' \
-                            'with %r' % (proc.returncode, args))
+            raise Exception("Failed while running trac-admin with arguments %r.\n"
+                            "Exitcode: %s \n%s"
+                            % (args, proc.returncode, out))
 
     def start(self):
         """Starts the webserver, and waits for it to come up."""
         FIXME: probably needs a nicer way to exit for coverage to work
         """
         if self.pid:
-            if os.name == 'nt':
-                # Untested
-                res = call(["taskkill", "/f", "/pid", str(self.pid)],
-                     stdin=PIPE, stdout=PIPE, stderr=PIPE)
-            else:
-                os.kill(self.pid, signal.SIGTERM)
-                try:
-                    os.waitpid(self.pid, 0)
-                except OSError, e:
-                    if e.errno != errno.ESRCH:
-                        raise
+            terminate(self)
 
     def restart(self):
         """Restarts the webserver"""
         """Default to no repository"""
         return "''" # needed for Python 2.3 and 2.4 on win32
 
-    def call_in_workdir(self, args, environ=None):
+    def call_in_dir(self, dir, args, environ=None):
         proc = Popen(args, stdout=PIPE, stderr=logfile,
-                     close_fds=close_fds, cwd=self.work_dir(), env=environ)
+            close_fds=close_fds, cwd=dir, env=environ)
         (data, _) = proc.communicate()
         if proc.wait():
             raise Exception('Unable to run command %s in %s' %
-                            (args, self.work_dir()))
-
+                            (args, dir))
         logfile.write(data)
         return data

trac/tests/functional/tester.py

-#!/usr/bin/python
 # -*- coding: utf-8 -*-
 #
 # Copyright (C) 2003-2013 Edgewall Software
         tc.submit()
         tc.notfind(internal_error)
 
+    def go_to_url(self, url):
+        tc.go(url)
+        tc.url(url)
+        tc.notfind(internal_error)
+
     def go_to_front(self):
         """Go to the Trac front page"""
-        tc.go(self.url)
-        tc.url(self.url)
-        tc.notfind(internal_error)
+        self.go_to_url(self.url)
 
     def go_to_ticket(self, ticketid):
         """Surf to the page for the given ticket ID.  Assumes ticket
         exists."""
         ticket_url = self.url + "/ticket/%s" % ticketid
-        tc.go(ticket_url)
-        tc.url(ticket_url)
+        self.go_to_url(ticket_url)
 
     def go_to_wiki(self, name):
         """Surf to the page for the given wiki page."""
         # Used to go based on a quickjump, but if the wiki pagename isn't
         # camel case, that won't work.
         wiki_url = self.url + '/wiki/%s' % name
-        tc.go(wiki_url)
-        tc.url(wiki_url)
+        self.go_to_url(wiki_url)
 
     def go_to_timeline(self):
         """Surf to the timeline page."""
         tc.url(self.url + "/ticket/%s" % self.ticketcount)
         return self.ticketcount
 
-    def create_wiki_page(self, page, content=None):
-        """Creates the specified wiki page, with random content if none is
-        provided.
+    def create_wiki_page(self, name=None, content=None):
+        """Creates a wiki page, with a random unique CamelCase name if none
+        is provided, and random content if none is provided.  Returns the
+        name of the wiki page.
         """
-        if content == None:
+        if name is None:
+            name = random_unique_camel()
+        if content is None:
             content = random_page()
-        page_url = self.url + "/wiki/" + page
-        tc.go(page_url)
-        tc.url(page_url)
-        tc.find("The page %s does not exist." % page)
-        tc.formvalue('modifypage', 'action', 'edit')
-        tc.submit()
-        tc.url(page_url + '\\?action=edit')
+        self.go_to_wiki(name)
+        tc.find("The page %s does not exist." % name)
 
-        tc.formvalue('edit', 'text', content)
-        tc.submit('save')
-        tc.url(page_url+'$')
+        self.edit_wiki_page(name, content)
 
         # verify the event shows up in the timeline
         self.go_to_timeline()
         tc.formvalue('prefs', 'wiki', True)
         tc.submit()
-        tc.find(page + ".*created")
+        tc.find(name + ".*created")
+
+        self.go_to_wiki(name)
+
+        return name
+
+    def edit_wiki_page(self, name, content=None):
+        """Edits a wiki page, with random content is none is provided.
+        Returns the content.
+        """
+        if content is None:
+            content = random_page()
+        self.go_to_wiki(name)
+        tc.formvalue('modifypage', 'action', 'edit')
+        tc.submit()
+        tc.formvalue('edit', 'text', content)
+        tc.submit('save')
+        page_url = self.url + '/wiki/%s' % name
+        tc.url(page_url+'$')
+
+        return content
 
     def attach_file_to_wiki(self, name, data=None, tempfilename=None):
         """Attaches a file to the given wiki page, with random content if none

trac/ticket/default_workflow.py

         this_action = self.actions[action]
         status = this_action['newstate']
         operations = this_action['operations']
-        current_owner = ticket._old.get('owner', ticket['owner'] or '(none)')
+        current_owner_or_empty = ticket._old.get('owner', ticket['owner'])
+        current_owner = current_owner_or_empty or '(none)'
         if not (Chrome(self.env).show_email_addresses
                 or 'EMAIL_VIEW' in req.perm(ticket.resource)):
             format_user = obfuscate_email_address
                                     owner=tag.input(type='text', id=id,
                                                     name=id, value=owner)))
                 hints.append(_("The owner will be changed from "
-                               "%(current_owner)s",
+                               "%(current_owner)s to the specified user",
                                current_owner=current_owner))
             elif len(owners) == 1:
                 owner = tag.input(type='hidden', id=id, name=id,
             control.append(_('as %(status)s ',
                              status= ticket._old.get('status',
                                                      ticket['status'])))
+            if len(operations) == 1:
+                hints.append(_("The owner will remain %(current_owner)s",
+                               current_owner=current_owner)
+                             if current_owner_or_empty else
+                             _("The ticket will remain with no owner"))
         else:
             if status != '*':
                 hints.append(_("Next status will be '%(name)s'", name=status))

trac/ticket/roadmap.py

         return req.path_info == '/roadmap'
 
     def process_request(self, req):
-        req.perm.require('MILESTONE_VIEW')
+        req.perm.require('ROADMAP_VIEW')
 
         show = req.args.getlist('show')
         if 'all' in show:

trac/ticket/tests/functional.py

         tc.find('class="closed ticket".*ticket/%s#comment:1"' % ticketid)
 
 
+class RegressionTestTicket11028(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test for regression of http://trac.edgewall.org/ticket/11028"""
+        self._tester.go_to_roadmap()
+
+        try:
+            # Check that a milestone is found on the roadmap,
+            # even for anonymous
+            tc.find('<a href="/milestone/milestone1">[ \n\t]*'
+                    'Milestone: <em>milestone1</em>[ \n\t]*</a>')
+            self._tester.logout()
+            tc.find('<a href="/milestone/milestone1">[ \n\t]*'
+                    'Milestone: <em>milestone1</em>[ \n\t]*</a>')
+
+            # Check that no milestones are found on the roadmap when
+            # MILESTONE_VIEW is revoked
+            self._testenv.revoke_perm('anonymous', 'MILESTONE_VIEW')
+            tc.reload()
+            tc.notfind('Milestone: <em>milestone\d+</em>')
+
+            # Check that roadmap can't be viewed without ROADMAP_VIEW
+
+            self._testenv.revoke_perm('anonymous', 'ROADMAP_VIEW')
+            self._tester.go_to_url(self._tester.url + '/roadmap')
+            tc.find('<h1>Error: Forbidden</h1>')
+        finally:
+            # Restore state prior to test execution
+            self._tester.login('admin')
+            self._testenv.grant_perm('anonymous',
+                                     ('ROADMAP_VIEW', 'MILESTONE_VIEW'))
+
+
 def functionalSuite(suite=None):
     if not suite:
         import trac.tests.functional.testcases
     suite.addTest(RegressionTestTicket8861())
     suite.addTest(RegressionTestTicket9084())
     suite.addTest(RegressionTestTicket9981())
+    suite.addTest(RegressionTestTicket11028())
 
     return suite
 

trac/util/__init__.py

                 os.remove(errfile)
 
 
+def terminate(process):
+    """Python 2.5 compatibility method.
+    os.kill is not available on Windows before Python 2.7.
+    In Python 2.6 subprocess.Popen has a terminate method.
+    (It also seems to have some issues on Windows though.)
+    """
+
+    def terminate_win(process):
+        import ctypes
+        PROCESS_TERMINATE = 1
+        handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE,
+                                                    False,
+                                                    process.pid)
+        ctypes.windll.kernel32.TerminateProcess(handle, -1)
+        ctypes.windll.kernel32.CloseHandle(handle)
+
+    def terminate_nix(process):
+        import os
+        import signal
+        try:
+            os.kill(process.pid, signal.SIGTERM)
+        except OSError, e:
+            # If the process has already finished and has not been
+            # waited for, killing it raises an ESRCH error on Cygwin
+            import errno
+            if e.errno != errno.ESRCH:
+                raise
+
+    if sys.platform == 'win32':
+        return terminate_win(process)
+    return terminate_nix(process)
+
+
 def makedirs(path, overwrite=False):
     """Create as many directories as necessary to make `path` exist.
 

trac/util/datefmt.py

 
 try:
     import babel
+except ImportError:
+    babel = None
+    def get_known_locales():
+        return []
+else:
     from babel import Locale
-    from babel.core import LOCALE_ALIASES
+    from babel.core import LOCALE_ALIASES, UnknownLocaleError
     from babel.dates import (
         format_datetime as babel_format_datetime,
         format_date as babel_format_date,
         get_time_format, get_month_names,
         get_period_names, get_day_names
     )
-    from babel.localedata import list as get_known_locales
-
-except ImportError:
-    babel = None
-    def get_known_locales():
-        return []
+    try:
+        from babel.localedata import list as get_known_locales
+    except ImportError:
+        from babel.localedata import locale_identifiers as get_known_locales
 
 from trac.core import TracError
 from trac.util.text import to_unicode, getpreferredencoding
     'date': {'short': '%x', 'medium': '%x', 'long': '%x', 'full': '%x'},
     'time': {'short': '%H:%M', 'medium': '%X', 'long': '%X', 'full': '%X'},
 }
-_ISO8601_FORMATS = {
-    'datetime': {
-        '%x %X': 'iso8601', '%x': 'iso8601date', '%X': 'iso8601time',
-        'short': '%Y-%m-%dT%H:%M', 'medium': '%Y-%m-%dT%H:%M:%S',
-        'long': 'iso8601', 'full': 'iso8601',
-        'iso8601': 'iso8601', None: 'iso8601'},
-    'date': {
-        '%x %X': 'iso8601', '%x': 'iso8601date', '%X': 'iso8601time',
-        'short': 'iso8601date', 'medium': 'iso8601date',
-        'long': 'iso8601date', 'full': 'iso8601date',
-        'iso8601': 'iso8601date', None: 'iso8601date'},
-    'time': {
-        '%x %X': 'iso8601', '%x': 'iso8601date', '%X': 'iso8601time',
-        'short': '%H:%M', 'medium': '%H:%M:%S',
-        'long': 'iso8601time', 'full': 'iso8601time',
-        'iso8601': 'iso8601time', None: 'iso8601time'},
-}
 _STRFTIME_HINTS = {'%x %X': 'datetime', '%x': 'date', '%X': 'time'}
 
 def _format_datetime_without_babel(t, format):
-    normalize_Z = False
-    if format.lower().startswith('iso8601'):
-        if 'date' in format:
-            format = '%Y-%m-%d'
-        elif 'time' in format:
-            format = '%H:%M:%S%z'
-            normalize_Z = True
-        else:
-            format = '%Y-%m-%dT%H:%M:%S%z'
-            normalize_Z = True
     text = t.strftime(str(format))
-    if normalize_Z:
-        text = text.replace('+0000', 'Z')
-        if not text.endswith('Z'):
-            text = text[:-2] + ":" + text[-2:]
     encoding = getlocale(LC_TIME)[1] or getpreferredencoding() \
                or sys.getdefaultencoding()
     return unicode(text, encoding, 'replace')
 
+def _format_datetime_iso8601(t, format, hint):
+    if format != 'full':
+        t = t.replace(microsecond=0)
+    text = t.isoformat()  # YYYY-MM-DDThh:mm:ss.SSSSSS±hh:mm
+    if format == 'short':
+        text = text[:16]  # YYYY-MM-DDThh:mm
+    elif format == 'medium':
+        text = text[:19]  # YYYY-MM-DDThh:mm:ss
+    elif text.endswith('+00:00'):
+        text = text[:-6] + 'Z'
+    if hint == 'date':
+        text = text.split('T', 1)[0]
+    elif hint == 'time':
+        text = text.split('T', 1)[1]
+    return unicode(text, 'ascii')
+
 def _format_datetime(t, format, tzinfo, locale, hint):
     t = to_datetime(t, tzinfo or localtz)
 
-    if (format in ('iso8601', 'iso8601date', 'iso8601time') or
-        locale == 'iso8601'):
-        format = _ISO8601_FORMATS[hint].get(format, format)
+    if format == 'iso8601':
+        return _format_datetime_iso8601(t, 'long', hint)
+    if format in ('iso8601date', 'iso8601time'):
+        return _format_datetime_iso8601(t, 'long', format[7:])
+    if locale == 'iso8601':
+        if format is None:
+            format = 'long'
+        elif format in _STRFTIME_HINTS:
+            hint = _STRFTIME_HINTS[format]
+            format = 'long'
+        if format in ('short', 'medium', 'long', 'full'):
+            return _format_datetime_iso8601(t, format, hint)
         return _format_datetime_without_babel(t, format)
 
     if babel and locale:
     if locale == 'iso8601':
         return 1 # Monday
     if babel and locale:
+        if not locale.territory:
+            # search first locale which has the same `langauge` and territory
+            # in preferred languages
+            for l in req.languages:
+                l = l.replace('-', '_').lower()
+                if l.startswith(locale.language.lower() + '_'):
+                    try:
+                        l = Locale.parse(l)
+                        if l.territory:
+                            locale = l
+                            break
+                    except UnknownLocaleError:
+                        pass
         if not locale.territory and locale.language in LOCALE_ALIASES:
             locale = Locale.parse(LOCALE_ALIASES[locale.language])
         return (locale.first_week_day + 1) % 7
     def fromutc(self, dt):
         if dt.tzinfo is None or dt.tzinfo is not self:
             raise ValueError('fromutc: dt.tzinfo is not self')
-        tt = time.localtime(to_timestamp(dt.replace(tzinfo=utc)))
+        try:
+            tt = time.localtime(to_timestamp(dt.replace(tzinfo=utc)))
+        except ValueError:
+            return dt.replace(tzinfo=self._std_tz) + self._std_offset
         if tt.tm_isdst > 0:
             tz = self._dst_tz
         else:
             tz = self._std_tz
-        return datetime(microsecond=dt.microsecond, tzinfo=tz, *tt[0:6])
+        return datetime(*(tt[:6] + (dt.microsecond, tz)))
 
 
 utc = FixedOffset(0, 'UTC')

trac/util/tests/datefmt.py

         self.assertEqual(datefmt.format_time(t, 'iso8601', gmt01),
                          expected.split('T')[1])
 
+    def test_format_iso8601_before_1900(self):
+        t = datetime.datetime(1899, 12, 30, 23, 58, 59, 123456, datefmt.utc)
+        self.assertEqual('1899-12-30T23:58:59Z',
+                         datefmt.format_datetime(t, 'iso8601', datefmt.utc))
+        self.assertEqual('1899-12-30',
+                         datefmt.format_datetime(t, 'iso8601date',
+                                                 datefmt.utc))
+        self.assertEqual('1899-12-30',
+                         datefmt.format_date(t, 'iso8601', datefmt.utc))
+        self.assertEqual('23:58:59Z',
+                         datefmt.format_datetime(t, 'iso8601time',
+                                                 datefmt.utc))
+        self.assertEqual('23:58:59Z',
+                         datefmt.format_time(t, 'iso8601', datefmt.utc))
+
     def test_format_date_accepts_date_instances(self):
         a_date = datetime.date(2009, 8, 20)
         self.assertEqual('2009-08-20',
                          datefmt.format_time(t, 'medium', tz, 'iso8601'))
         self.assertEqual('2010-08-28T11:45:56',
                          datefmt.format_datetime(t, 'medium', tz, 'iso8601'))
-        for f in ('long', 'full'):
-            self.assertEqual('11:45:56+02:00',
-                             datefmt.format_time(t, f, tz, 'iso8601'))
-            self.assertEqual('2010-08-28T11:45:56+02:00',
-                             datefmt.format_datetime(t, f, tz, 'iso8601'))
+        self.assertEqual('11:45:56+02:00',
+                         datefmt.format_time(t, 'long', tz, 'iso8601'))
+        self.assertEqual('2010-08-28T11:45:56+02:00',
+                         datefmt.format_datetime(t, 'long', tz, 'iso8601'))
+        self.assertEqual('11:45:56.123456+02:00',
+                         datefmt.format_time(t, 'full', tz, 'iso8601'))
+        self.assertEqual('2010-08-28T11:45:56.123456+02:00',
+                         datefmt.format_datetime(t, 'full', tz, 'iso8601'))
+
+    def test_with_babel_format_before_1900(self):
+        tz = datefmt.timezone('GMT +2:00')
+        t = datetime.datetime(1899, 8, 28, 11, 45, 56, 123456, tz)
+        for f in ('short', 'medium', 'long', 'full'):
+            self.assertEqual('1899-08-28',
+                             datefmt.format_date(t, f, tz, 'iso8601'))
+        self.assertEqual('11:45',
+                         datefmt.format_time(t, 'short', tz, 'iso8601'))
+        self.assertEqual('1899-08-28T11:45',
+                         datefmt.format_datetime(t, 'short', tz, 'iso8601'))
+        self.assertEqual('11:45:56',
+                         datefmt.format_time(t, 'medium', tz, 'iso8601'))
+        self.assertEqual('1899-08-28T11:45:56',
+                         datefmt.format_datetime(t, 'medium', tz, 'iso8601'))
+        self.assertEqual('11:45:56+02:00',
+                         datefmt.format_time(t, 'long', tz, 'iso8601'))
+        self.assertEqual('1899-08-28T11:45:56+02:00',
+                         datefmt.format_datetime(t, 'long', tz, 'iso8601'))
+        self.assertEqual('11:45:56.123456+02:00',
+                         datefmt.format_time(t, 'full', tz, 'iso8601'))
+        self.assertEqual('1899-08-28T11:45:56.123456+02:00',
+                         datefmt.format_datetime(t, 'full', tz, 'iso8601'))
 
     def test_hint(self):
         try:
         self.assertEqual('2011-10-30T02:45:42.123456+01:00',
                          dt.astimezone(datefmt.localtz).isoformat())
 
+    def test_astimezone_invalid_range_on_gmt01(self):
+        self._tzset('GMT-1')
+
+        # 1899-12-30T23:59:58+00:00 is -0x83ac4e92 for time_t, out of range
+        # for 32-bit signed integer
+        dt = datetime.datetime(1899, 12, 30, 23, 59, 58, 123456, datefmt.utc)
+        self.assertEqual('1899-12-31T00:59:58.123456+01:00',
+                         dt.astimezone(datefmt.localtz).isoformat())
+        dt = datetime.datetime(1899, 12, 30, 23, 59, 58, 123456,
+                               datefmt.localtz)
+        self.assertEqual('1899-12-30T22:59:58.123456+00:00',
+                         dt.astimezone(datefmt.utc).isoformat())
+
+        # 2040-12-31T23:59:58+00:00 is 0x858c84ee for time_t, out of range for
+        # 32-bit signed integer
+        dt = datetime.datetime(2040, 12, 31, 23, 59, 58, 123456, datefmt.utc)
+        self.assertEqual('2041-01-01T00:59:58.123456+01:00',
+                         dt.astimezone(datefmt.localtz).isoformat())
+        dt = datetime.datetime(2040, 12, 31, 23, 59, 58, 123456,
+                               datefmt.localtz)
+        self.assertEqual('2040-12-31T22:59:58.123456+00:00',
+                         dt.astimezone(datefmt.utc).isoformat())
+
     def test_arithmetic_localized_non_existent_time(self):
         self._tzset('Europe/Paris')
         t = datetime.datetime(2012, 3, 25, 1, 15, 42, 123456)

trac/web/chrome.py

         """Make `<textarea>` fields resizable. Requires !JavaScript.
         (''since 0.12'')""")
 
+    wiki_toolbars = BoolOption('trac', 'wiki_toolbars', 'true',
+        """Add a simple toolbar on top of Wiki `<textarea>`s.
+        (''since 1.0.2'')""")
+
     auto_preview_timeout = FloatOption('trac', 'auto_preview_timeout', 2.0,
         """Inactivity timeout in seconds after which the automatic wiki preview
         triggers an update. This option can contain floating-point values. The
 
     def add_wiki_toolbars(self, req):
         """Add wiki toolbars to `<textarea class="wikitext">` fields."""
-        add_script(req, 'common/js/wikitoolbar.js')
+        if self.wiki_toolbars:
+            add_script(req, 'common/js/wikitoolbar.js')
         self.add_textarea_grips(req)
 
     def add_auto_preview(self, req):
     def _stream_location(self, stream):
         for kind, data, pos in stream:
             return pos
-

trac/wiki/macros.py

 
         def default_cell(option):
             default = option.default
-            if default is True:
-                default = 'true'
-            elif default is False:
-                default = 'false'
-            elif default == 0:
-                default = '0.0' if isinstance(default, float) else '0'
-            elif default:
-                default = ', '.join(to_unicode(val) for val in default) \
-                          if isinstance(default, (list, tuple)) \
-                          else to_unicode(default)
+            if default is not None and default != '':
+                return tag.td(tag.code(option.dumps(default)),
+                              class_='default')
             else:
                 return tag.td(_("(no default)"), class_='nodefault')
-            return tag.td(tag.code(default), class_='default')
 
         return tag.div(class_='tracini')(
             (tag.h3(tag.code('[%s]' % section), id='%s-section' % section),

trac/wiki/templates/wiki_view.html

       xmlns:i18n="http://genshi.edgewall.org/i18n"
       xmlns:xi="http://www.w3.org/2001/XInclude"
       py:with="modify_perm = 'WIKI_MODIFY' in perm(page.resource);
+               create_perm = 'WIKI_CREATE' in perm(page.resource);
                admin_perm = 'WIKI_ADMIN' in perm(page.resource);
                is_not_latest = page.exists and page.version != latest_version">
   <xi:include href="layout.html" />
 
       <py:with vars="delete_perm = 'WIKI_DELETE' in perm(page.resource);
                      rename_perm = 'WIKI_RENAME' in perm(page.resource)">
-        <py:if test="admin_perm or (not page.readonly and (modify_perm or delete_perm))">
+        <py:if test="admin_perm or (not page.readonly and (modify_perm or create_perm or delete_perm))">
           <div class="buttons">
-            <py:if test="modify_perm">
+            <py:if test="modify_perm or create_perm">
               <form method="get" action="${href.wiki(page.name)}" id="modifypage">
                 <div>
                   <input type="hidden" name="action" value="edit" />
                   <py:choose>
-                    <py:when test="is_not_latest">
+                    <py:when test="is_not_latest and modify_perm">
                       <input type="hidden" name="version" value="${page.version}"/>
                       <input type="submit" value="${_('Revert to this version')}"/>
                     </py:when>
-                    <py:when test="page.exists">
+                    <py:when test="page.exists and modify_perm">
                       <input type="submit" value="${_('Edit this page')}" accesskey="e" />
                     </py:when>
-                    <py:otherwise>
+                    <py:when test="not page.exists and create_perm">
                       <input type="submit" value="${_('Create this page')}" accesskey="e" />
                       <div py:if="templates" id="template">
                         <label for="template">Using the template:</label>
                                   selected="${t == default_template or None}">$t</option>
                         </select>
                       </div>
-                    </py:otherwise>
+                    </py:when>
                   </py:choose>
                 </div>
               </form>

trac/wiki/tests/functional.py

 
 
 class RegressionTestTicket10850(FunctionalTwillTestCaseSetup):
-
     def runTest(self):
         """Test for regression of http://trac.edgewall.org/ticket/10850"""
         pagename = random_unique_camel()
         tc.notfind('Error: Invalid Attachment')
 
 
+class RegressionTestTicket10957(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test for regression of http://trac.edgewall.org/ticket/10957"""
+
+        try:
+            self._tester.logout()
+
+            # Check that page can't be created without WIKI_CREATE
+            page_name = random_unique_camel()
+            self._tester.go_to_wiki(page_name)
+            tc.find("Trac Error")
+            tc.find("Page %s not found" % page_name)
+            tc.notfind("Create this page")
+            tc.go(self._tester.url + '/wiki/%s?action=edit' % page_name)
+            tc.find("Error: Forbidden")
+            tc.find("WIKI_CREATE privileges are required to perform this "
+                    "operation on %s. You don't have the required permissions."
+                    % page_name)
+
+            # Check that page can be created when user has WIKI_CREATE
+            self._testenv.grant_perm('anonymous', 'WIKI_CREATE')
+            content_v1 = random_sentence()
+            self._tester.create_wiki_page(page_name, content_v1)
+            tc.find(content_v1)
+
+            # Check that page can't be edited without WIKI_MODIFY
+            tc.notfind("Edit this page")
+            tc.notfind("Attach file")
+            tc.go(self._tester.url + '/wiki/%s?action=edit' % page_name)
+            tc.find("Error: Forbidden")
+            tc.find("WIKI_MODIFY privileges are required to perform this "
+                    "operation on %s. You don't have the required permissions."
+                    % page_name)
+
+            # Check that page can be edited when user has WIKI_MODIFY
+            self._testenv.grant_perm('anonymous', 'WIKI_MODIFY')
+            self._tester.go_to_wiki(page_name)
+            tc.find("Edit this page")
+            tc.find("Attach file")
+            content_v2 = random_sentence()
+            self._tester.edit_wiki_page(page_name, content_v2)
+            tc.find(content_v2)
+
+            # Check that page can be reverted to a previous revision
+            tc.go(self._tester.url + '/wiki/%s?version=1' % page_name)
+            tc.find("Revert to this version")
+            tc.formvalue('modifypage', 'action', 'edit')
+            tc.submit()
+            tc.find(content_v1)
+
+            # Check that page can't be reverted without WIKI_MODIFY
+            self._tester.edit_wiki_page(page_name)
+            self._testenv.revoke_perm('anonymous', 'WIKI_MODIFY')
+            tc.go(self._tester.url + '/wiki/%s?version=1' % page_name)
+            tc.notfind("Revert to this version")
+            tc.go(self._tester.url + '/wiki/%s?action=edit&version=1' % page_name)
+            tc.find("WIKI_MODIFY privileges are required to perform this "
+                    "operation on %s. You don't have the required permissions."
+                    % page_name)
+
+        finally:
+            # Restore pre-test state.
+            self._tester.login('admin')
+            self._testenv.revoke_perm('anonymous', 'WIKI_CREATE')
+
+
 def functionalSuite(suite=None):
     if not suite:
         import trac.tests.functional.testcases
     suite.addTest(RegressionTestTicket4812())
     suite.addTest(RegressionTestTicket10274())
     suite.addTest(RegressionTestTicket10850())
+    suite.addTest(RegressionTestTicket10957())
     if has_docutils:
         import docutils
         if get_pkginfo(docutils):

trac/wiki/tests/macros.py

 import tempfile
 import unittest
 
-from trac.config import Option
+from trac.config import Option, ListOption, IntOption, BoolOption
 from trac.test import locale_en
 from trac.util.datefmt import format_date, utc
 from trac.wiki.model import WikiPage
 </div><p>
 </p>
 ------------------------------
+============================== TracIni, list option with sep=| (#11074)
+[[TracIni(section-list)]]
+------------------------------
+<p>
+</p><div class="tracini">\
+<h3 id="section-list-section"><code>[section-list]</code></h3>\
+<table class="wiki"><tbody>\
+<tr><td><tt>option1</tt></td><td></td><td class="default"><code>4.2|42|42||0|enabled</code></td></tr>\
+</tbody></table>\
+</div><p>
+</p>
+------------------------------
+============================== TracIni, option with "false" value as default
+[[TracIni(section-def)]]
+------------------------------
+<p>
+</p><div class="tracini">\
+<h3 id="section-def-section"><code>[section-def]</code></h3>\
+<table class="wiki"><tbody>\
+<tr><td><tt>option1</tt></td><td></td><td class="nodefault">(no default)</td></tr>\
+<tr><td><tt>option2</tt></td><td></td><td class="nodefault">(no default)</td></tr>\
+<tr><td><tt>option3</tt></td><td></td><td class="default"><code>0</code></td></tr>\
+<tr><td><tt>option4</tt></td><td></td><td class="default"><code>disabled</code></td></tr>\
+<tr><td><tt>option5</tt></td><td></td><td class="default"><code></code></td></tr>\
+</tbody></table>\
+</div><p>
+</p>
+------------------------------
 """
 
 def tracini_setup(tc):
     class Foo(object):
         option_a1 = (Option)('section-42', 'option1', 'value', doc='')
         option_a2 = (Option)('section-42', 'option2', 'value', doc='blah')
+        option_l1 = (ListOption)('section-list', 'option1',
+                                 [4.2, '42', 42, None, 0, True], sep='|')
+        option_d1 = (Option)('section-def', 'option1', None)
+        option_d2 = (Option)('section-def', 'option2', '')
+        option_d3 = (IntOption)('section-def', 'option3', 0)
+        option_d4 = (BoolOption)('section-def', 'option4', False)
+        option_d5 = (ListOption)('section-def', 'option5', [])
 
 def tracini_teardown(tc):
     Option.registry = tc._orig_registry

trac/wiki/web_ui.py

 
         if page.readonly:
             req.perm(page.resource).require('WIKI_ADMIN')
+        elif not page.exists:
+            req.perm(page.resource).require('WIKI_CREATE')
         else:
             req.perm(page.resource).require('WIKI_MODIFY')
         original_text = page.text

tracopt/versioncontrol/git/PyGIT.py

 import time
 import weakref
 
+from trac.util import terminate
 
 __all__ = ['GitError', 'GitErrorSha', 'Storage', 'StorageFactory']
 
 
-def terminate(process):
-    """Python 2.5 compatibility method.
-    os.kill is not available on Windows before Python 2.7.
-    In Python 2.6 subprocess.Popen has a terminate method.
-    (It also seems to have some issues on Windows though.)
-    """
-
-    def terminate_win(process):
-        import ctypes
-        PROCESS_TERMINATE = 1
-        handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE,
-                                                    False,
-                                                    process.pid)
-        ctypes.windll.kernel32.TerminateProcess(handle, -1)
-        ctypes.windll.kernel32.CloseHandle(handle)
-
-    def terminate_nix(process):
-        import os
-        import signal
-        try:
-            os.kill(process.pid, signal.SIGTERM)
-        except OSError, e:
-            # If the process has already finished and has not been
-            # waited for, killing it raises an ESRCH error on Cygwin
-            import errno
-            if e.errno != errno.ESRCH:
-                raise
-
-    if sys.platform == 'win32':
-        return terminate_win(process)
-    return terminate_nix(process)
-
-
 class GitError(Exception):
     pass
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.