You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by gj...@apache.org on 2012/10/16 22:06:19 UTC

svn commit: r1398968 [24/28] - in /incubator/bloodhound/trunk/trac: ./ contrib/ doc/ doc/api/ doc/utils/ sample-plugins/ sample-plugins/permissions/ sample-plugins/workflow/ trac/ trac/admin/ trac/admin/templates/ trac/admin/tests/ trac/db/ trac/db/tes...

Modified: incubator/bloodhound/trunk/trac/trac/ticket/templates/ticket_change.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/templates/ticket_change.html?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/templates/ticket_change.html (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/templates/ticket_change.html Tue Oct 16 20:06:09 2012
@@ -62,21 +62,21 @@ Arguments:
     <form py:if="'cnum' in change and can_edit_comment" method="get" action="#comment:${cnum}">
       <div class="inlinebuttons">
         <input type="hidden" name="cnum_edit" value="${cnum}"/>
-        <input type="submit" value="${_('Edit')}" title="${_('Edit comment %(cnum)s', cnum=cnum)}"/>
+        <input type="submit" value="${captioned_button('✎', _('Edit'))}" title="${_('Edit comment %(cnum)s', cnum=cnum)}" />
       </div>
     </form>
     <form py:if="'cnum' in change and can_append" id="reply-to-comment-${cnum}"
           method="get" action="#comment">
       <div class="inlinebuttons">
         <input type="hidden" name="replyto" value="${cnum}"/>
-        <input type="submit" value="${_('Reply')}" title="${_('Reply to comment %(cnum)s', cnum=cnum)}"/>
+        <input type="submit" value="${captioned_button('↳', _('Reply'))}" title="${_('Reply to comment %(cnum)s', cnum=cnum)}" />
       </div>
     </form>
   </div>
   <ul py:if="change.fields" class="changes">
     <li py:for="field_name, field in sorted(change.fields.iteritems(), key=lambda item: item[1].label.lower())"
-        class="${'trac-conflict' if preview and field_name in conflicts else None}">
-      <strong>${field.label}</strong>
+        class="trac-field-${field_name}${' trac-conflict' if preview and field_name in conflicts else None}">
+      <strong class="trac-field-${field_name}">${field.label}</strong>
       <py:choose>
         <py:when test="field_name == 'attachment'"><i18n:msg params="name">
           <a href="${href.attachment('ticket', ticket.id, field.new)}"><em>${field.new
@@ -94,6 +94,12 @@ Arguments:
         <py:otherwise><i18n:msg params="value">
           <em>${field.old}</em> deleted
         </i18n:msg></py:otherwise>
+        <py:if test="preview and field.by == 'user'">
+          (<button py:with="old = ticket.get_default(field_name) if field.old is empty else field.old"
+                   type="submit" name="revert_$field_name" class="trac-revert"
+                   title="Revert this change">revert<div id="revert-$field_name">${
+            '0' if 'cc_update' in field else old}</div></button>)
+        </py:if>
       </py:choose>
     </li>
   </ul>

Modified: incubator/bloodhound/trunk/trac/trac/ticket/tests/functional.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/tests/functional.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/tests/functional.py (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/tests/functional.py Tue Oct 16 20:06:09 2012
@@ -1,16 +1,11 @@
 #!/usr/bin/python
+# -*- coding: utf-8 -*-
 import os
 import re
 
 from datetime import datetime, timedelta
 
-try:
-    import babel
-    locale_en = babel.Locale('en_US')
-except ImportError:
-    babel = None
-    locale_en = None
-
+from trac.test import locale_en
 from trac.tests.functional import *
 from trac.util.datefmt import utc, localtz, format_date, format_datetime
 
@@ -200,6 +195,7 @@ class TestTicketQueryLinks(FunctionalTwi
         tc.formvalue('query', '0_summary', 'TestTicketQueryLinks')
         tc.submit('update')
         query_url = b.get_url()
+        tc.find(r'\(%d matches\)' % count)
         for i in range(count):
             tc.find('TestTicketQueryLinks%s' % i)
 
@@ -1576,7 +1572,8 @@ class RegressionTestTicket8247(Functiona
         tc.formvalue('milestone_table', 'sel', name)
         tc.submit('remove')
         tc.go(ticket_url)
-        tc.find('<strong>Milestone</strong>[ \n\t]*<em>%s</em> deleted' % name)
+        tc.find('<strong class="trac-field-milestone">Milestone</strong>'
+                '[ \n\t]*<em>%s</em> deleted' % name)
         tc.find('Changed <a.* ago</a> by admin')
         tc.notfind('anonymous')
 
@@ -1607,7 +1604,7 @@ class RegressionTestTicket9084(Functiona
         ticketid = self._tester.create_ticket()
         self._tester.add_comment(ticketid)
         self._tester.go_to_ticket(ticketid)
-        tc.submit('Reply', formname='reply-to-comment-1')
+        tc.submit('2', formname='reply-to-comment-1') # '1' hidden, '2' submit
         tc.formvalue('propertyform', 'comment', random_sentence(3))
         tc.submit('Submit changes')
         tc.notfind('AssertionError')

Modified: incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py Tue Oct 16 20:06:09 2012
@@ -154,6 +154,22 @@ class TicketTestCase(unittest.TestCase):
         for change in ticket.get_changelog():
             self.assertEqual(None, change[1])
 
+    def test_comment_with_whitespace_only_is_not_saved(self):
+        ticket = Ticket(self.env)
+        ticket.insert()
+
+        ticket.save_changes(comment='\n \n ')
+        self.assertEqual(0, len(ticket.get_changelog()))
+
+    def test_prop_whitespace_change_is_not_saved(self):
+        ticket = Ticket(self.env)
+        ticket.populate({'summary': 'ticket summary'})
+        ticket.insert()
+
+        ticket['summary'] = ' ticket summary '
+        ticket.save_changes()
+        self.assertEqual(0, len(ticket.get_changelog()))
+
     def test_ticket_default_values(self):
         """
         Verify that a ticket uses default values specified in the configuration

Modified: incubator/bloodhound/trunk/trac/trac/ticket/tests/query.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/tests/query.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/tests/query.py (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/tests/query.py Tue Oct 16 20:06:09 2012
@@ -1,15 +1,10 @@
-from trac.test import Mock, EnvironmentStub, MockPerm
+from trac.test import Mock, EnvironmentStub, MockPerm, locale_en
 from trac.ticket.query import Query, QueryModule, TicketQueryMacro
 from trac.util.datefmt import utc
 from trac.web.chrome import web_context
 from trac.web.href import Href
 from trac.wiki.formatter import LinkFormatter
 
-try:
-    from babel import Locale
-except ImportError:
-    Locale = None
-
 import unittest
 import difflib
 
@@ -37,9 +32,8 @@ class QueryTestCase(unittest.TestCase):
 
     def setUp(self):
         self.env = EnvironmentStub(default_data=True)
-        locale = Locale.parse('en_US') if Locale else None
         self.req = Mock(href=self.env.href, authname='anonymous', tz=utc,
-                        locale=locale, lc_time=locale)
+                        locale=locale_en, lc_time=locale_en)
         
     def tearDown(self):
         self.env.reset_db()

Modified: incubator/bloodhound/trunk/trac/trac/ticket/tests/report.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/tests/report.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/tests/report.py (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/tests/report.py Tue Oct 16 20:06:09 2012
@@ -1,9 +1,12 @@
 # -*- coding: utf-8 -*-
 
+import doctest
+
 from trac.db.mysql_backend import MySQLConnection
 from trac.ticket.report import ReportModule
 from trac.test import EnvironmentStub, Mock
 from trac.web.api import Request, RequestDone
+import trac
 
 import unittest
 from StringIO import StringIO
@@ -98,7 +101,10 @@ class ReportTestCase(unittest.TestCase):
 
 
 def suite():
-    return unittest.makeSuite(ReportTestCase, 'test')
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocTestSuite(trac.ticket.report))
+    suite.addTest(unittest.makeSuite(ReportTestCase, 'test'))
+    return suite
 
 if __name__ == '__main__':
-    unittest.main()
+    unittest.main(defaultTest='suite')

Modified: incubator/bloodhound/trunk/trac/trac/ticket/tests/wikisyntax.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/tests/wikisyntax.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/tests/wikisyntax.py (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/tests/wikisyntax.py Tue Oct 16 20:06:09 2012
@@ -32,8 +32,8 @@ ticket:12-14,33
 ticket:12,33?order=created
 ------------------------------
 <p>
-<a href="/query?id=12-14%2C33" title="Tickets 12-14,33">ticket:12-14,33</a>
-<a href="/query?id=12%2C33&amp;order=created" title="Tickets 12,33">ticket:12,33?order=created</a>
+<a href="/query?id=12-14%2C33" title="Tickets 12-14, 33">ticket:12-14,33</a>
+<a href="/query?id=12%2C33&amp;order=created" title="Tickets 12, 33">ticket:12,33?order=created</a>
 </p>
 ------------------------------
 ============================== ticket link shorthand form
@@ -50,8 +50,8 @@ ticket:12,33?order=created
 #1,3,5,7
 ------------------------------
 <p>
-<a href="/query?id=1-5%2C42" title="Tickets 1-5,42">#1-5,42</a>
-<a href="/query?id=1%2C3%2C5%2C7" title="Tickets 1,3,5,7">#1,3,5,7</a>
+<a href="/query?id=1-5%2C42" title="Tickets 1-5, 42">#1-5,42</a>
+<a href="/query?id=1%2C3%2C5%2C7" title="Tickets 1, 3, 5, 7">#1,3,5,7</a>
 </p>
 ------------------------------
 ============================== ticket link shorthand form with long ranges (#10111 regression)
@@ -91,6 +91,21 @@ trac:#2041
 <a class="ext-link" href="http://trac.edgewall.org/intertrac/%232041" title="#2041 in Trac's Trac"><span class="icon"></span>trac:#2041</a>
 </p>
 ------------------------------
+============================== ticket syntax with unicode digits
+#⁴²
+#1-⁵,42
+#1,³,5,7
+#T²⁰⁴¹
+#trac²⁰⁴¹
+------------------------------
+<p>
+#⁴²
+<a class="new ticket" href="/ticket/1" title="This is the summary (new)">#1</a>-⁵,42
+<a class="new ticket" href="/ticket/1" title="This is the summary (new)">#1</a>,³,5,7
+#T²⁰⁴¹
+#trac²⁰⁴¹
+</p>
+------------------------------
 """ # " 
 
 def ticket_setup(tc):
@@ -146,6 +161,17 @@ trac:report:1
 <a class="ext-link" href="http://trac.edgewall.org/intertrac/report%3A1" title="report:1 in Trac's Trac"><span class="icon"></span>{trac 1}</a>
 </p>
 ------------------------------
+============================== report syntax with unicode digits
+{⁴²} !{⁴²}
+{T⁴²}
+{trac⁴²}
+------------------------------
+<p>
+{⁴²} !{⁴²}
+{T⁴²}
+{trac⁴²}
+</p>
+------------------------------
 """ # '
 
 def report_setup(tc):

Modified: incubator/bloodhound/trunk/trac/trac/ticket/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/web_ui.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/ticket/web_ui.py (original)
+++ incubator/bloodhound/trunk/trac/trac/ticket/web_ui.py Tue Oct 16 20:06:09 2012
@@ -29,27 +29,34 @@ from trac.attachment import AttachmentMo
 from trac.config import BoolOption, Option, IntOption
 from trac.core import *
 from trac.mimeview.api import Mimeview, IContentConverter
-from trac.resource import Resource, ResourceNotFound, get_resource_url, \
-                         render_resource_link, get_resource_shortname
+from trac.resource import (
+    Resource, ResourceNotFound, get_resource_url, render_resource_link,
+    get_resource_shortname
+)
 from trac.search import ISearchSource, search_to_sql, shorten_result
 from trac.ticket.api import TicketSystem, ITicketManipulator
 from trac.ticket.model import Milestone, Ticket, group_milestones
 from trac.ticket.notification import TicketNotifyEmail
 from trac.timeline.api import ITimelineEventProvider
 from trac.util import as_bool, as_int, get_reporter_id
-from trac.util.datefmt import format_datetime, from_utimestamp, \
-                              to_utimestamp, utc
-from trac.util.text import exception_to_unicode, obfuscate_email_address, \
-                           shorten_line, to_unicode
+from trac.util.datefmt import (
+    format_datetime, from_utimestamp, to_utimestamp, utc
+)
+from trac.util.text import (
+    exception_to_unicode, empty, obfuscate_email_address, shorten_line,
+    to_unicode
+)
 from trac.util.presentation import separated
 from trac.util.translation import _, tag_, tagn_, N_, gettext, ngettext
 from trac.versioncontrol.diff import get_diff_options, diff_blocks
-from trac.web import arg_list_to_args, parse_arg_list, IRequestHandler, \
-                     RequestDone
-from trac.web.chrome import (Chrome, INavigationContributor, ITemplateProvider,
-                             add_ctxtnav, add_link, add_notice, add_script,
-                             add_script_data, add_stylesheet, add_warning,
-                             auth_link, prevnext_nav, web_context)
+from trac.web import (
+    IRequestHandler, RequestDone, arg_list_to_args, parse_arg_list
+)
+from trac.web.chrome import (
+    Chrome, INavigationContributor, ITemplateProvider,
+    add_ctxtnav, add_link, add_notice, add_script, add_script_data,
+    add_stylesheet, add_warning, auth_link, prevnext_nav, web_context
+)
 from trac.wiki.formatter import format_to, format_to_html, format_to_oneliner
 
 
@@ -106,7 +113,7 @@ class TicketModule(Component):
         """Delegate access to ticket default Options which were move to
         TicketSystem.
 
-        .. todo:: remove in 0.13
+        .. todo:: remove in 1.0
         """
         if name.startswith('default_'):
             if name not in self._warn_for_default_attr:
@@ -240,10 +247,10 @@ class TicketModule(Component):
         ts_start = to_utimestamp(start)
         ts_stop = to_utimestamp(stop)
 
-        status_map = {'new': ('newticket', N_("created")),
-                      'reopened': ('reopenedticket', N_("reopened")),
-                      'closed': ('closedticket', N_("closed")),
-                      'edit': ('editedticket', N_("updated"))}
+        status_map = {'new': ('newticket', 'created'),
+                      'reopened': ('reopenedticket', 'reopened'),
+                      'closed': ('closedticket', 'closed'),
+                      'edit': ('editedticket', 'updated')}
 
         ticket_realm = Resource('ticket')
 
@@ -381,9 +388,15 @@ class TicketModule(Component):
         elif field == 'title':
             title = TicketSystem(self.env).format_summary(summary, status,
                                                           resolution, type)
-            return tag_("Ticket %(ticketref)s (%(summary)s) %(verb)s",
+            message = {
+                'created': N_("Ticket %(ticketref)s (%(summary)s) created"),
+                'reopened': N_("Ticket %(ticketref)s (%(summary)s) reopened"),
+                'closed': N_("Ticket %(ticketref)s (%(summary)s) closed"),
+                'updated': N_("Ticket %(ticketref)s (%(summary)s) updated"),
+            }[verb]
+            return tag_(message,
                         ticketref=tag.em('#', ticket.id, title=title),
-                        summary=shorten_line(summary), verb=gettext(verb))
+                        summary=shorten_line(summary))
         elif field == 'description':
             descr = message = ''
             if status == 'new':
@@ -405,9 +418,9 @@ class TicketModule(Component):
                 description, comment, cid = event[3]
         tickets = sorted(tickets)
         if field == 'url':
-            return context.href.query(id=','.join([str(t) for t in tickets]))
+            return context.href.query(id=','.join(str(t) for t in tickets))
         elif field == 'title':
-            ticketids = ','.join([str(t) for t in tickets])
+            ticketids = u',\u200b'.join(str(t) for t in tickets)
             title = _("Tickets %(ticketids)s", ticketids=ticketids)
             return tag_("Tickets %(ticketlist)s batch updated",
                         ticketlist=tag.em('#', ticketids, title=title))
@@ -469,11 +482,6 @@ class TicketModule(Component):
 
         fields = self._prepare_fields(req, ticket)
 
-        # setup default values for the new ticket
-        
-        for field in fields:
-            ticket.values.setdefault(field['name'], field.get('value'))
-
         # position 'owner' immediately before 'cc',
         # if not already positioned after (?)
 
@@ -490,6 +498,8 @@ class TicketModule(Component):
                 del ticket.fields[curr_idx]
 
         data['fields'] = fields
+        data['fields_map'] = dict((field['name'], i)
+                                  for i, field in enumerate(fields))
 
         if req.get_header('X-Requested-With') == 'XMLHttpRequest':
             data['preview_mode'] = True
@@ -544,7 +554,7 @@ class TicketModule(Component):
                          'reassign_owner': req.authname,
                          'resolve_resolution': None,
                          'start_time': ticket['changetime']})
-        elif req.method == 'POST': # 'Preview' or 'Submit'
+        elif req.method == 'POST':
             if 'cancel_comment' in req.args:
                 req.redirect(req.href.ticket(ticket.id))
             elif 'edit_comment' in req.args:
@@ -598,7 +608,7 @@ class TicketModule(Component):
             # validates and there were no problems with the workflow side of
             # things.
             valid = self._validate_ticket(req, ticket, not valid) and valid
-            if 'preview' not in req.args:
+            if 'submit' in req.args:
                 if valid:
                     # redirected if successful
                     self._do_save(req, ticket, action)
@@ -709,13 +719,16 @@ class TicketModule(Component):
         return {'comments_order': req.session.get('ticket_comments_order',
                                                   'oldest'),
                 'comments_only': req.session.get('ticket_comments_only',
-                                                 False)}
+                                                 'false')}
         
     def _prepare_data(self, req, ticket, absurls=False):
         return {'ticket': ticket, 'to_utimestamp': to_utimestamp,
-                'context': web_context(req, ticket.resource,
-                                                absurls=absurls),
-                'preserve_newlines': self.must_preserve_newlines}
+                'context': web_context(req, ticket.resource, absurls=absurls),
+                'preserve_newlines': self.must_preserve_newlines,
+                'emtpy': empty}
+
+    def _cc_list(self, cc):
+        return Chrome(self.env).cc_list(cc)
 
     def _toggle_cc(self, req, cc):
         """Return an (action, recipient) tuple corresponding to a change
@@ -735,7 +748,7 @@ class TicketModule(Component):
                 entries.append(email)
         add = []
         remove = []
-        cc_list = Chrome(self.env).cc_list(cc)
+        cc_list = self._cc_list(cc)
         for entry in entries:
             if entry in cc_list:
                 remove.append(entry)
@@ -750,8 +763,15 @@ class TicketModule(Component):
         
     def _populate(self, req, ticket, plain_fields=False):
         if not plain_fields:
-            fields = dict([(k[6:], v) for k, v in req.args.iteritems()
-                           if k.startswith('field_')])
+            fields = dict((k[6:], v) for k, v in req.args.iteritems()
+                          if k.startswith('field_')
+                             and not 'revert_' + k[6:] in req.args)
+            # Handle revert of checkboxes (in particular, revert to 1)
+            for k in list(fields):
+                if k.startswith('checkbox_'):
+                    k = k[9:]
+                    if 'revert_' + k in req.args:
+                        fields[k] = ticket[k]
         else:
             fields = req.args.copy()
         # Prevent direct changes to protected fields (status and resolution are
@@ -761,7 +781,7 @@ class TicketModule(Component):
             fields.pop('checkbox_' + each, None)    # See Ticket.populate()
         ticket.populate(fields)
         # special case for updating the Cc: field
-        if 'cc_update' in req.args:
+        if 'cc_update' in req.args and 'revert_cc' not in req.args:
             cc_action, cc_entry, cc_list = self._toggle_cc(req, ticket['cc'])
             if cc_action == 'remove':
                 cc_list.remove(cc_entry)
@@ -1381,10 +1401,11 @@ class TicketModule(Component):
 
     def _query_link(self, req, name, value, text=None):
         """Return a link to /query with the appropriate name and value"""
-        default_query = self.ticketlink_query.startswith('?') and \
-                        self.ticketlink_query[1:] or self.ticketlink_query
+        default_query = self.ticketlink_query.lstrip('?')
         args = arg_list_to_args(parse_arg_list(default_query))
         args[name] = value
+        if name == 'resolution':
+            args['status'] = 'closed'
         return tag.a(text or value, href=req.href.query(args))
 
     def _query_link_words(self, context, name, value):
@@ -1410,7 +1431,7 @@ class TicketModule(Component):
                     items.append(rendered)
         return tag(items)
 
-    def _prepare_fields(self, req, ticket):
+    def _prepare_fields(self, req, ticket, field_changes=None):
         context = web_context(req, ticket.resource)
         fields = []
         owner_field = None
@@ -1453,18 +1474,33 @@ class TicketModule(Component):
                 field['rendered'] = self._query_link_words(context, name,
                                                            ticket[name])
             elif name == 'cc':
+                cc_changed = field_changes is not None and 'cc' in field_changes
                 field['rendered'] = self._query_link_words(context, name,
                                                            ticket[name])
                 if ticket.exists and \
                         'TICKET_EDIT_CC' not in req.perm(ticket.resource):
                     cc = ticket._old.get('cc', ticket['cc'])
                     cc_action, cc_entry, cc_list = self._toggle_cc(req, cc)
+                    cc_update = 'cc_update' in req.args \
+                                and 'revert_cc' not in req.args
                     field['edit_label'] = {
                             'add': _("Add to Cc"),
                             'remove': _("Remove from Cc"),
                             '': _("Add/Remove from Cc")}[cc_action]
                     field['cc_entry'] = cc_entry or _("<Author field>")
-                    field['cc_update'] = 'cc_update' in req.args or None
+                    field['cc_update'] = cc_update or None
+                    if cc_changed:
+                        field_changes['cc']['cc_update'] = cc_update
+                if cc_changed:
+                    # normalize the new CC: list; also remove the
+                    # change altogether if there's no real change
+                    old_cc_list = self._cc_list(field_changes['cc']['old'])
+                    new_cc_list = self._cc_list(field_changes['cc']['new']
+                                                .replace(' ', ','))
+                    if new_cc_list == old_cc_list:
+                        del field_changes['cc']
+                    else:
+                        field_changes['cc']['new'] = ','.join(new_cc_list)
 
             # per type settings
             if type_ in ('radio', 'select'):
@@ -1524,7 +1560,8 @@ class TicketModule(Component):
 
         # -- Ticket fields
 
-        fields = self._prepare_fields(req, ticket)
+        fields = self._prepare_fields(req, ticket, field_changes)
+        fields_map = dict((field['name'], i) for i, field in enumerate(fields))
 
         # -- Ticket Change History
 
@@ -1582,6 +1619,13 @@ class TicketModule(Component):
         # -- Workflow support
         
         selected_action = req.args.get('action')
+
+        # retrieve close time from changes
+        closetime = None
+        for c in changes:
+            s = c['fields'].get('status')
+            if s:
+                closetime = c['date'] if s['new'] == 'closed' else None
         
         # action_controls is an ordered list of "renders" tuples, where
         # renders is a list of (action_key, label, widgets, hints) representing
@@ -1631,13 +1675,14 @@ class TicketModule(Component):
         for user in 'reporter', 'owner':
             if chrome.format_author(req, ticket[user]) == ticket[user]:
                 data['%s_link' % user] = self._query_link(req, user,
-                                                            ticket[user])
+                                                          ticket[user])
         data.update({
             'context': context, 'conflicts': conflicts,
-            'fields': fields, 'changes': changes, 'replies': replies,
+            'fields': fields, 'fields_map': fields_map,
+            'changes': changes, 'replies': replies,
             'attachments': AttachmentModule(self.env).attachment_data(context),
             'action_controls': action_controls, 'action': selected_action,
-            'change_preview': change_preview,
+            'change_preview': change_preview, 'closetime': closetime,
         })
 
     def rendered_changelog_entries(self, req, ticket, when=None):
@@ -1692,8 +1737,7 @@ class TicketModule(Component):
         render_elt = lambda x: x
         sep = ', '
         if field == 'cc':
-            chrome = Chrome(self.env)
-            old_list, new_list = chrome.cc_list(old), chrome.cc_list(new)
+            old_list, new_list = self._cc_list(old), self._cc_list(new)
             if not (Chrome(self.env).show_email_addresses or 
                     'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)):
                 render_elt = obfuscate_email_address
@@ -1729,8 +1773,8 @@ class TicketModule(Component):
         """Iterate on changelog entries, consolidating related changes
         in a `dict` object.
 
-        :since 0.13: the `db` parameter is no longer needed and will be removed
-        in version 0.14
+        :since 1.0: the `db` parameter is no longer needed and will be removed
+        in version 1.1.1
         """
         field_labels = TicketSystem(self.env).get_ticket_field_labels()
         changelog = ticket.get_changelog(when=when)

Modified: incubator/bloodhound/trunk/trac/trac/timeline/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/timeline/api.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/timeline/api.py (original)
+++ incubator/bloodhound/trunk/trac/trac/timeline/api.py Tue Oct 16 20:06:09 2012
@@ -3,7 +3,7 @@
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jo...@edgewall.com>
 # Copyright (C) 2004-2005 Christopher Lenz <cm...@gmx.de>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which

Modified: incubator/bloodhound/trunk/trac/trac/timeline/templates/timeline.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/timeline/templates/timeline.html?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/timeline/templates/timeline.html (original)
+++ incubator/bloodhound/trunk/trac/trac/timeline/templates/timeline.html Tue Oct 16 20:06:09 2012
@@ -8,6 +8,11 @@
   <xi:include href="layout.html" />
   <head>
     <title>Timeline</title>
+    <script type="text/javascript">/*<![CDATA[*/
+      jQuery(document).ready(function($) {
+        $("#fromdate").datepicker();
+      });
+    /*]]>*/</script>
   </head>
 
   <body>
@@ -17,7 +22,7 @@
 
       <form id="prefs" method="get" action="">
        <div i18n:msg="">
-        <label>View changes from <input type="text" size="10" name="from" value="${format_date(fromdate)}" /></label> <br />
+        <label>View changes from <input type="text" id="fromdate" size="10" name="from" value="${format_date(fromdate)}" /></label> <br />
         and <label><input type="text" size="3" name="daysback" value="$daysback" /> days back</label><br />
         <label>done by <input type="text" size="16" name="authors" value="$authors" /></label>
        </div>

Modified: incubator/bloodhound/trunk/trac/trac/timeline/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/timeline/web_ui.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/timeline/web_ui.py (original)
+++ incubator/bloodhound/trunk/trac/trac/timeline/web_ui.py Tue Oct 16 20:06:09 2012
@@ -3,7 +3,7 @@
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jo...@edgewall.com>
 # Copyright (C) 2004-2005 Christopher Lenz <cm...@gmx.de>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -211,8 +211,9 @@ class TimelineModule(Component):
             data['context'] = rss_context
             return 'timeline.rss', data, 'application/rss+xml'
         else:
-            req.session['timeline.daysback'] = daysback
-            req.session['timeline.authors'] = authors
+            req.session.set('timeline.daysback', daysback,
+                            self.default_daysback)
+            req.session.set('timeline.authors', authors, '')
             # store lastvisit
             if events and not revisit:
                 lastviewed = to_utimestamp(events[0]['date'])
@@ -229,6 +230,7 @@ class TimelineModule(Component):
                                      format='rss')
         add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'),
                  'application/rss+xml', 'rss')
+        Chrome(self.env).add_jquery_ui(req)
 
         for filter_ in available_filters:
             data['filters'].append({'name': filter_[0], 'label': filter_[1],

Modified: incubator/bloodhound/trunk/trac/trac/util/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/__init__.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/__init__.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/__init__.py Tue Oct 16 20:06:09 2012
@@ -3,7 +3,7 @@
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2006 Jonas Borgström <jo...@edgewall.com>
 # Copyright (C) 2006 Matthew Good <tr...@matt-good.net>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -406,6 +406,7 @@ def fq_class_name(obj):
     m, n = c.__module__, c.__name__
     return n if m == '__builtin__' else '%s.%s' % (m, n)
 
+
 def arity(f):
     """Return the number of arguments expected by the given function, unbound
     or bound method.
@@ -524,7 +525,7 @@ def safe_repr(x):
     without risking to trigger an exception (e.g. from a buggy
     `x.__repr__`).
 
-    .. versionadded :: 0.13
+    .. versionadded :: 1.0
     """
     try:
         return to_unicode(repr(x))
@@ -546,6 +547,23 @@ def get_doc(obj):
     description = doc[1] if len(doc) > 1 else None
     return (summary, description)
 
+
+_dont_import = frozenset(['__file__', '__name__', '__package__'])
+
+def import_namespace(globals_dict, module_name):
+    """Import the namespace of a module into a globals dict.
+    
+    This function is used in stub modules to import all symbols defined in
+    another module into the global namespace of the stub, usually for
+    backward compatibility.
+    """
+    __import__(module_name)
+    module = sys.modules[module_name]
+    globals_dict.update(item for item in module.__dict__.iteritems()
+                        if item[0] not in _dont_import)
+    globals_dict.pop('import_namespace', None)
+
+
 # -- setuptools utils
 
 def get_module_path(module):
@@ -787,9 +805,16 @@ class Ranges(object):
     >>> str(Ranges("20-10", reorder=True))
     '10-20'
 
+    As rendered ranges are often using u',\u200b' (comma + Zero-width
+    space) to enable wrapping, we also support reading such ranges, as
+    they can be copy/pasted back.
+
+    >>> str(Ranges(u'1,\u200b3,\u200b5,\u200b6,\u200b7,\u200b9'))
+    '1,3,5-7,9'
+
     """
 
-    RE_STR = r"""\d+(?:[-:]\d+)?(?:,\d+(?:[-:]\d+)?)*"""
+    RE_STR = ur'[0-9]+(?:[-:][0-9]+)?(?:,\u200b?[0-9]+(?:[-:][0-9]+)?)*'
     
     def __init__(self, r=None, reorder=False):
         self.pairs = []
@@ -808,7 +833,7 @@ class Ranges(object):
             return
         p = self.pairs
         if isinstance(r, basestring):
-            r = r.split(',')
+            r = re.split(u',\u200b?', r)
         for x in r:
             try:
                 a, b = map(int, x.split('-', 1))
@@ -1059,3 +1084,4 @@ from trac.util.datefmt import pretty_tim
                               get_datetime_format_hint, http_date, \
                               parse_date
 
+__no_apidoc__ = 'compat presentation translation'

Modified: incubator/bloodhound/trunk/trac/trac/util/daemon.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/daemon.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/daemon.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/daemon.py Tue Oct 16 20:06:09 2012
@@ -22,7 +22,6 @@ import sys
 def daemonize(pidfile=None, progname=None, stdin='/dev/null',
               stdout='/dev/null', stderr='/dev/null', umask=022):
     """Fork a daemon process."""
-
     if pidfile:
         # Check whether the pid file already exists and refers to a still
         # process running
@@ -32,7 +31,8 @@ def daemonize(pidfile=None, progname=Non
                 try:
                     pid = int(fileobj.read())
                 except ValueError:
-                    sys.exit('Invalid PID in file %s' % pidfile)
+                    sys.exit('Invalid pid in file %s\nPlease remove it to '
+                             'proceed' % pidfile)
 
             try: # signal the process to see if it is still running
                 os.kill(pid, 0)
@@ -45,7 +45,7 @@ def daemonize(pidfile=None, progname=Non
 
         # The pid file must be writable
         try:
-            fileobj = open(pidfile, 'r+')
+            fileobj = open(pidfile, 'a+')
             fileobj.close()
         except IOError, e:
             from trac.util.text import exception_to_unicode

Modified: incubator/bloodhound/trunk/trac/trac/util/datefmt.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/datefmt.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/datefmt.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/datefmt.py Tue Oct 16 20:06:09 2012
@@ -3,7 +3,7 @@
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2006 Jonas Borgström <jo...@edgewall.com>
 # Copyright (C) 2006 Matthew Good <tr...@matt-good.net>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -26,12 +26,16 @@ from locale import getlocale, getpreferr
 
 try:
     import babel
-    from babel.dates import format_datetime as babel_format_datetime, \
-                            format_date as babel_format_date, \
-                            format_time as babel_format_time, \
-                            get_datetime_format, get_date_format, \
-                            get_time_format, get_month_names, \
-                            get_period_names, get_day_names
+    from babel import Locale
+    from babel.core import LOCALE_ALIASES
+    from babel.dates import (
+        format_datetime as babel_format_datetime, 
+        format_date as babel_format_date,
+        format_time as babel_format_time,
+        get_datetime_format, get_date_format,
+        get_time_format, get_month_names,
+        get_period_names, get_day_names
+    )
 except ImportError:
     babel = None
 
@@ -373,6 +377,17 @@ def get_timezone_list_jquery_ui(t=None):
             if zone == '+0000' else zone[:-2] + ':' + zone[-2:]
             for zone in sorted(zones, key=lambda tz: int(tz))]
 
+def get_first_week_day_jquery_ui(req):
+    """Get first week day for jQuery date picker"""
+    locale = req.lc_time
+    if locale == 'iso8601':
+        return 1 # Monday
+    if babel and locale:
+        if not locale.territory and locale.language in LOCALE_ALIASES:
+            locale = Locale.parse(LOCALE_ALIASES[locale.language])
+        return (locale.first_week_day + 1) % 7
+    return 0 # Sunday
+
 def is_24_hours(locale):
     """Returns `True` for 24 hour time formats."""
     if locale == 'iso8601':
@@ -396,7 +411,11 @@ def http_date(t=None):
 
 _ISO_8601_RE = re.compile(r'''
     (\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d))?)?    # date
-    (?:T(\d\d)(?::?(\d\d)(?::?(\d\d))?)?)?  # time
+    (?:
+        [T ]
+        (\d\d)(?::?(\d\d)(?::?(\d\d)        # time
+        (?:[,.](\d{1,6}))?)?)?              # microseconds
+    )?
     (Z?(?:([-+])?(\d\d):?(\d\d)?)?)?$       # timezone
     ''', re.VERBOSE)
 
@@ -408,8 +427,9 @@ def _parse_date_iso8601(text, tzinfo):
             years = g[0]
             months = g[1] or '01'
             days = g[2] or '01'
-            hours, minutes, seconds = [x or '00' for x in g[3:6]]
-            z, tzsign, tzhours, tzminutes = g[6:10]
+            hours, minutes, seconds, useconds = [x or '00' for x in g[3:7]]
+            useconds = (useconds + '000000')[:6]
+            z, tzsign, tzhours, tzminutes = g[7:11]
             if z:
                 tz = timedelta(hours=int(tzhours or '0'),
                                minutes=int(tzminutes or '0')).seconds / 60
@@ -419,10 +439,9 @@ def _parse_date_iso8601(text, tzinfo):
                     tzinfo = FixedOffset(-tz if tzsign == '-' else tz,
                                          '%s%s:%s' %
                                          (tzsign, tzhours, tzminutes))
-            tm = time.strptime('%s ' * 6 % (years, months, days,
-                                            hours, minutes, seconds),
-                               '%Y %m %d %H %M %S ')
-            t = tzinfo.localize(datetime(*tm[0:6]))
+            tm = [int(x) for x in (years, months, days,
+                                   hours, minutes, seconds, useconds)]
+            t = tzinfo.localize(datetime(*tm))
             return tzinfo.normalize(t)
         except ValueError:
             pass

Modified: incubator/bloodhound/trunk/trac/trac/util/html.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/html.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/html.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/html.py Tue Oct 16 20:06:09 2012
@@ -71,47 +71,29 @@ class TracHTMLSanitizer(HTMLSanitizer):
         self.safe_css = frozenset(safe_css)
 
     # IE6 <http://heideri.ch/jso/#80>
-    _EXPRESSION_SEARCH = re.compile(u"""
-        [eE
-         \uFF25 # FULLWIDTH LATIN CAPITAL LETTER E
-         \uFF45 # FULLWIDTH LATIN SMALL LETTER E
-        ]
-        [xX
-         \uFF38 # FULLWIDTH LATIN CAPITAL LETTER X
-         \uFF58 # FULLWIDTH LATIN SMALL LETTER X
-        ]
-        [pP
-         \uFF30 # FULLWIDTH LATIN CAPITAL LETTER P
-         \uFF50 # FULLWIDTH LATIN SMALL LETTER P
-        ]
-        [rR
-         \u0280 # LATIN LETTER SMALL CAPITAL R
-         \uFF32 # FULLWIDTH LATIN CAPITAL LETTER R
-         \uFF52 # FULLWIDTH LATIN SMALL LETTER R
-        ]
-        [eE
-         \uFF25 # FULLWIDTH LATIN CAPITAL LETTER E
-         \uFF45 # FULLWIDTH LATIN SMALL LETTER E
-        ]
-        [sS
-         \uFF33 # FULLWIDTH LATIN CAPITAL LETTER S
-         \uFF53 # FULLWIDTH LATIN SMALL LETTER S
-        ]{2}
-        [iI
-         \u026A # LATIN LETTER SMALL CAPITAL I
-         \uFF29 # FULLWIDTH LATIN CAPITAL LETTER I
-         \uFF49 # FULLWIDTH LATIN SMALL LETTER I
-        ]
-        [oO
-         \uFF2F # FULLWIDTH LATIN CAPITAL LETTER O
-         \uFF4F # FULLWIDTH LATIN SMALL LETTER O
-        ]
-        [nN
-         \u0274 # LATIN LETTER SMALL CAPITAL N
-         \uFF2E # FULLWIDTH LATIN CAPITAL LETTER N
-         \uFF4E # FULLWIDTH LATIN SMALL LETTER N
-        ]
-        """, re.VERBOSE).search
+    _EXPRESSION_SEARCH = re.compile(
+        u'[eE\uFF25\uFF45]'         # FULLWIDTH LATIN CAPITAL LETTER E
+                                    # FULLWIDTH LATIN SMALL LETTER E
+        u'[xX\uFF38\uFF58]'         # FULLWIDTH LATIN CAPITAL LETTER X
+                                    # FULLWIDTH LATIN SMALL LETTER X
+        u'[pP\uFF30\uFF50]'         # FULLWIDTH LATIN CAPITAL LETTER P
+                                    # FULLWIDTH LATIN SMALL LETTER P
+        u'[rR\u0280\uFF32\uFF52]'   # LATIN LETTER SMALL CAPITAL R
+                                    # FULLWIDTH LATIN CAPITAL LETTER R
+                                    # FULLWIDTH LATIN SMALL LETTER R
+        u'[eE\uFF25\uFF45]'         # FULLWIDTH LATIN CAPITAL LETTER E
+                                    # FULLWIDTH LATIN SMALL LETTER E
+        u'[sS\uFF33\uFF53]{2}'      # FULLWIDTH LATIN CAPITAL LETTER S
+                                    # FULLWIDTH LATIN SMALL LETTER S
+        u'[iI\u026A\uFF29\uFF49]'   # LATIN LETTER SMALL CAPITAL I
+                                    # FULLWIDTH LATIN CAPITAL LETTER I
+                                    # FULLWIDTH LATIN SMALL LETTER I
+        u'[oO\uFF2F\uFF4F]'         # FULLWIDTH LATIN CAPITAL LETTER O
+                                    # FULLWIDTH LATIN SMALL LETTER O
+        u'[nN\u0274\uFF2E\uFF4E]'   # LATIN LETTER SMALL CAPITAL N
+                                    # FULLWIDTH LATIN CAPITAL LETTER N
+                                    # FULLWIDTH LATIN SMALL LETTER N
+    ).search
 
     # IE6 <http://openmya.hacker.jp/hasegawa/security/expression.txt>
     #     7) Particular bit of Unicode characters
@@ -184,7 +166,15 @@ class TracHTMLSanitizer(HTMLSanitizer):
         def _repl(match):
             t = match.group(1)
             if t:
-                return unichr(int(t, 16))
+                code = int(t, 16)
+                chr = unichr(code)
+                if code <= 0x1f:
+                    # replace space character because IE ignores control
+                    # characters
+                    chr = ' '
+                elif chr == '\\':
+                    chr = r'\\'
+                return chr
             t = match.group(2)
             if t == '\\':
                 return r'\\'
@@ -193,6 +183,14 @@ class TracHTMLSanitizer(HTMLSanitizer):
         return self._UNICODE_ESCAPE(_repl,
                                     self._NORMALIZE_NEWLINES('\n', text))
 
+    _CSS_COMMENTS = re.compile(r'/\*.*?\*/').sub
+
+    def _strip_css_comments(self, text):
+        """Replace comments with space character instead of superclass which
+        removes comments to avoid problems when nested comments.
+        """
+        return self._CSS_COMMENTS(' ', text)
+
 
 class Deuglifier(object):
     """Help base class used for cleaning up HTML riddled with ``<FONT

Modified: incubator/bloodhound/trunk/trac/trac/util/presentation.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/presentation.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/presentation.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/presentation.py Tue Oct 16 20:06:09 2012
@@ -19,9 +19,14 @@ tasks such as grouping or pagination.
 from math import ceil
 import re
 
-__all__ = ['classes', 'first_last', 'group', 'istext', 'prepared_paginate', 
-           'paginate', 'Paginator']
-
+__all__ = ['captioned_button', 'classes', 'first_last', 'group', 'istext',
+           'prepared_paginate', 'paginate', 'Paginator']
+__no_apidoc__ = 'prepared_paginate'
+
+def captioned_button(req, symbol, text):
+    """Return symbol and text or only symbol, according to user preferences."""
+    return symbol if req.session.get('ui.use_symbols') \
+        else u'%s %s' % (symbol, text)
 
 def classes(*args, **kwargs):
     """Helper function for dynamically assembling a list of CSS class names
@@ -190,6 +195,7 @@ def paginate(items, page=0, max_per_page
 
 
 class Paginator(object):
+    """Pagination controller"""
 
     def __init__(self, items, page=0, max_per_page=10, num_items=None):
         if not page:
@@ -255,11 +261,11 @@ class Paginator(object):
         from trac.util.translation import _
         start, stop = self.span
         total = self.num_items
-        if start+1 == stop:
+        if start + 1 == stop:
             return _("%(last)d of %(total)d", last=stop, total=total)
         else:
             return _("%(start)d - %(stop)d of %(total)d",
-                    start=self.span[0]+1, stop=self.span[1], total=total)
+                    start=self.span[0] + 1, stop=self.span[1], total=total)
 
 
 def separated(items, sep=','):
@@ -298,12 +304,12 @@ try:
         return _js_quote_re.sub(replace, text)
 
 except ImportError:
-    from trac.util.text import javascript_quote
+    from trac.util.text import to_js_string
     
     def to_json(value):
         """Encode `value` to JSON."""
         if isinstance(value, basestring):
-            return '"%s"' % javascript_quote(value)
+            return to_js_string(value)
         elif value is None:
             return 'null'
         elif value is False:

Modified: incubator/bloodhound/trunk/trac/trac/util/tests/datefmt.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/tests/datefmt.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/tests/datefmt.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/tests/datefmt.py Tue Oct 16 20:06:09 2012
@@ -53,15 +53,19 @@ else:
 
         def test_parse_date(self):
             tz = datefmt.get_timezone('Europe/Zurich')
-            t = datefmt.parse_date('2009-12-01T12:00:00', tz)
             t_utc = datetime.datetime(2009, 12, 1, 11, 0, 0, 0, datefmt.utc)
-            self.assertEqual(t_utc, t)
+            self.assertEqual(t_utc,
+                    datefmt.parse_date('2009-12-01T12:00:00', tz))
+            self.assertEqual(t_utc,
+                    datefmt.parse_date('2009-12-01 12:00:00', tz))
 
         def test_parse_date_dst(self):
             tz = datefmt.get_timezone('Europe/Zurich')
-            t = datefmt.parse_date('2009-08-01T12:00:00', tz)
             t_utc = datetime.datetime(2009, 8, 1, 10, 0, 0, 0, datefmt.utc)
-            self.assertEqual(t_utc, t)
+            self.assertEqual(t_utc,
+                    datefmt.parse_date('2009-08-01T12:00:00', tz))
+            self.assertEqual(t_utc,
+                    datefmt.parse_date('2009-08-01 12:00:00', tz))
 
         def test_parse_date_across_dst_boundary(self):
             tz = datefmt.get_timezone('Europe/Zurich')
@@ -88,6 +92,7 @@ else:
             expected = '2002-03-31 00:00:00 CET+0100'
             self.assertEqual(expected, date.strftime(format))
 
+
 class DateFormatTestCase(unittest.TestCase):
 
     def test_to_datetime(self):
@@ -219,6 +224,36 @@ class ISO8601TestCase(unittest.TestCase)
         self.assertEqual('2010-08-28T11:45:56+02:00',
                          datefmt.format_datetime(t, 'iso8601', tz, 'iso8601'))
 
+    def test_parse_date_offset(self):
+        t_utc = datetime.datetime(2009, 12, 1, 11, 0, 0, 0, datefmt.utc)
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T11:00:00Z'))
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T11:00:00+00:00'))
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T11:00:00-00:00'))
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T09:00:00-02:00'))
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T11:30:00+00:30'))
+
+    def test_parse_date_usec(self):
+        tz = datefmt.get_timezone('GMT +1:00')
+        t_utc = datetime.datetime(2009, 12, 1, 11, 0, 0, 98765, datefmt.utc)
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T12:00:00.098765', tz))
+        self.assertEqual(t_utc,
+                         datefmt.parse_date('2009-12-01T12:00:00,098765', tz))
+        self.assertEqual(datetime.datetime(2009, 12, 1, 11, 0, 0, 98700,
+                                           datefmt.utc),
+                         datefmt.parse_date('2009-12-01T12:00:00.0987', tz))
+        self.assertEqual(datetime.datetime(2009, 12, 1, 11, 0, 0, 90000,
+                                           datefmt.utc),
+                         datefmt.parse_date('2009-12-01T12:00:00.09', tz))
+        self.assertEqual(datetime.datetime(2009, 12, 1, 11, 0, 0, 0,
+                                           datefmt.utc),
+                         datefmt.parse_date('2009-12-01T12:00:00.0', tz))
+
     def test_with_babel_format(self):
         tz = datefmt.timezone('GMT +2:00')
         t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, tz)

Modified: incubator/bloodhound/trunk/trac/trac/util/tests/html.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/tests/html.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/tests/html.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/tests/html.py Tue Oct 16 20:06:09 2012
@@ -17,6 +17,18 @@ class TracHTMLSanitizerTestCase(unittest
                     encoding='utf-8')
         self.assertEqual('<div>XSS</div>', unicode(html | TracHTMLSanitizer()))
 
+    def test_expression_with_comments(self):
+        html = HTML(r'<div style="top:exp/**/ression(alert())">XSS</div>')
+        self.assertEqual('<div style="top:exp ression(alert())">XSS</div>',
+                         unicode(html | TracHTMLSanitizer()))
+        html = HTML(r'<div style="top:exp//**/**/ression(alert())">XSS</div>')
+        self.assertEqual(
+            '<div style="top:exp/ **/ression(alert())">XSS</div>',
+            unicode(html | TracHTMLSanitizer()))
+        html = HTML(r'<div style="top:ex/*p*/ression(alert())">XSS</div>')
+        self.assertEqual('<div style="top:ex ression(alert())">XSS</div>',
+                         unicode(html | TracHTMLSanitizer()))
+
     def test_url_with_javascript(self):
         html = HTML('<div style="background-image:url(javascript:alert())">'
                     'XSS</div>', encoding='utf-8')
@@ -31,6 +43,18 @@ class TracHTMLSanitizerTestCase(unittest
         html = HTML(r'<div style="top:exp\72 ess\000069 on(alert())">'
                     r'XSS</div>', encoding='utf-8')
         self.assertEqual('<div>XSS</div>', unicode(html | TracHTMLSanitizer()))
+        # escaped backslash
+        html = HTML(r'<div style="top:exp\5c ression(alert())">XSS</div>')
+        self.assertEqual(r'<div style="top:exp\\ression(alert())">XSS</div>',
+                         unicode(html | TracHTMLSanitizer()))
+        html = HTML(r'<div style="top:exp\5c 72 ession(alert())">XSS</div>')
+        self.assertEqual(r'<div style="top:exp\\72 ession(alert())">XSS</div>',
+                         unicode(html | TracHTMLSanitizer()))
+        # escaped control characters
+        html = HTML(r'<div style="top:exp\000000res\1f sion(alert())">'
+                    r'XSS</div>')
+        self.assertEqual('<div style="top:exp res sion(alert())">XSS</div>',
+                         unicode(html | TracHTMLSanitizer()))
 
     def test_backslash_without_hex(self):
         html = HTML(r'<div style="top:e\xp\ression(alert())">XSS</div>',

Modified: incubator/bloodhound/trunk/trac/trac/util/tests/presentation.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/tests/presentation.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/tests/presentation.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/tests/presentation.py Tue Oct 16 20:06:09 2012
@@ -28,6 +28,8 @@ class ToJsonTestCase(unittest.TestCase):
         self.assertEqual('null', presentation.to_json(None))
         self.assertEqual('"String"', presentation.to_json('String'))
         self.assertEqual(r'"a \" quote"', presentation.to_json('a " quote'))
+        self.assertEqual('''"a ' single quote"''',
+                         presentation.to_json("a ' single quote"))
         self.assertEqual(r'"\u003cb\u003e\u0026\u003c/b\u003e"',
                          presentation.to_json('<b>&</b>'))
 
@@ -35,11 +37,11 @@ class ToJsonTestCase(unittest.TestCase):
         self.assertEqual('[1,2,[true,false]]',
                          presentation.to_json([1, 2, [True, False]]))
         self.assertEqual(r'{"one":1,"other":[null,0],'
-                         r'"three":[3,"\u0026\u003c\u003e"],'
+                         r'''"three":[3,"\u0026\u003c\u003e'"],'''
                          r'"two":2}',
                          presentation.to_json({"one": 1, "two": 2,
                                                "other": [None, 0],
-                                               "three": [3, "&<>"]}))
+                                               "three": [3, "&<>'"]}))
 
 
 def suite():

Modified: incubator/bloodhound/trunk/trac/trac/util/tests/text.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/tests/text.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/tests/text.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/tests/text.py Tue Oct 16 20:06:09 2012
@@ -4,11 +4,12 @@ import unittest
 from StringIO import StringIO
 
 from trac.util.text import empty, expandtabs, fix_eol, javascript_quote, \
-                           normalize_whitespace, to_unicode, \
+                           to_js_string, normalize_whitespace, to_unicode, \
                            text_width, print_table, unicode_quote, \
                            unicode_quote_plus, unicode_unquote, \
                            unicode_urlencode, wrap, quote_query_string, \
-                           unicode_to_base64, unicode_from_base64
+                           unicode_to_base64, unicode_from_base64, stripws, \
+                           levenshtein_distance
 
 
 class ToUnicodeTestCase(unittest.TestCase):
@@ -65,6 +66,19 @@ class JavascriptQuoteTestCase(unittest.T
         self.assertEqual(r'\u0026\u003c\u003e',
                          javascript_quote('&<>'))
 
+
+class ToJsStringTestCase(unittest.TestCase):
+    def test_(self):
+        self.assertEqual(r'"Quote \" in text"',
+                         to_js_string('Quote " in text'))
+        self.assertEqual(r'''"\\\"\b\f\n\r\t'"''',
+                         to_js_string('\\"\b\f\n\r\t\''))
+        self.assertEqual(r'"\u0002\u001e"',
+                         to_js_string('\x02\x1e'))
+        self.assertEqual(r'"\u0026\u003c\u003e"',
+                         to_js_string('&<>'))
+
+
 class UnicodeQuoteTestCase(unittest.TestCase):
     def test_unicode_quote(self):
         self.assertEqual(u'the%20%C3%9C%20thing',
@@ -289,12 +303,38 @@ class UnicodeBase64TestCase(unittest.Tes
         self.assertEqual(text, unicode_from_base64(text_base64_no_strip))
 
 
+class StripwsTestCase(unittest.TestCase):
+    def test_stripws(self):
+        self.assertEquals(u'stripws',
+                          stripws(u' \u200b\t\u3000stripws \u200b\t\u2008'))
+        self.assertEquals(u'stripws \u3000\t',
+                          stripws(u'\u200b\t\u2008 stripws \u3000\t',
+                                  trailing=False))
+        self.assertEquals(u' \t\u3000stripws',
+                          stripws(u' \t\u3000stripws \u200b\t\u2008',
+                                  leading=False))
+        self.assertEquals(u' \t\u3000stripws \u200b\t\u2008',
+                          stripws(u' \t\u3000stripws \u200b\t\u2008',
+                                  leading=False, trailing=False))
+
+
+
+class LevenshteinDistanceTestCase(unittest.TestCase):
+    def test_distance(self):
+        self.assertEqual(5, levenshtein_distance('kitten', 'sitting'))
+        self.assertEqual(1, levenshtein_distance('wii', 'wiki'))
+        self.assertEqual(2, levenshtein_distance('comfig', 'config'))
+        self.assertEqual(5, levenshtein_distance('update', 'upgrade'))
+        self.assertEqual(0, levenshtein_distance('milestone', 'milestone'))
+
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ToUnicodeTestCase, 'test'))
     suite.addTest(unittest.makeSuite(ExpandtabsTestCase, 'test'))
     suite.addTest(unittest.makeSuite(UnicodeQuoteTestCase, 'test'))
     suite.addTest(unittest.makeSuite(JavascriptQuoteTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(ToJsStringTestCase, 'test'))
     suite.addTest(unittest.makeSuite(QuoteQueryStringTestCase, 'test'))
     suite.addTest(unittest.makeSuite(WhitespaceTestCase, 'test'))
     suite.addTest(unittest.makeSuite(TextWidthTestCase, 'test'))
@@ -302,6 +342,8 @@ def suite():
     suite.addTest(unittest.makeSuite(WrapTestCase, 'test'))
     suite.addTest(unittest.makeSuite(FixEolTestCase, 'test'))
     suite.addTest(unittest.makeSuite(UnicodeBase64TestCase, 'test'))
+    suite.addTest(unittest.makeSuite(StripwsTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(LevenshteinDistanceTestCase, 'test'))
     return suite
 
 if __name__ == '__main__':

Modified: incubator/bloodhound/trunk/trac/trac/util/text.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/util/text.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/util/text.py (original)
+++ incubator/bloodhound/trunk/trac/trac/util/text.py Tue Oct 16 20:06:09 2012
@@ -3,7 +3,7 @@
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2004 Jonas Borgström <jo...@edgewall.com>
 # Copyright (C) 2006 Matthew Good <tr...@matt-good.net>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -16,7 +16,7 @@
 #
 # Author: Jonas Borgström <jo...@edgewall.com>
 #         Matthew Good <tr...@matt-good.net>
-#         Christian Boos <cb...@neuf.fr>
+#         Christian Boos <cb...@edgewall.org>
 
 import __builtin__
 import locale
@@ -95,15 +95,36 @@ def path_to_unicode(path):
     return unicode(path)
 
 
+_ws_leading_re = re.compile(ur'\A[\s\u200b]+', re.UNICODE)
+_ws_trailing_re = re.compile(ur'[\s\u200b]+\Z', re.UNICODE)
+
+def stripws(text, leading=True, trailing=True):
+    """Strips unicode white-spaces and ZWSPs from ``text``.
+
+    :param leading: strips leading spaces from ``text`` unless ``leading`` is
+                    `False`.
+    :param trailing: strips trailing spaces from ``text`` unless ``trailing``
+                     is `False`.
+    """
+    if leading:
+        text = _ws_leading_re.sub('', text)
+    if trailing:
+        text = _ws_trailing_re.sub('', text)
+    return text
+
+
 _js_quote = {'\\': '\\\\', '"': '\\"', '\b': '\\b', '\f': '\\f',
              '\n': '\\n', '\r': '\\r', '\t': '\\t', "'": "\\'"}
 for i in range(0x20) + [ord(c) for c in '&<>']:
     _js_quote.setdefault(chr(i), '\\u%04x' % i)
 _js_quote_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t\'&<>]')
+_js_string_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t&<>]')
 
 
 def javascript_quote(text):
-    """Quote strings for inclusion in javascript"""
+    """Quote strings for inclusion in single or double quote delimited
+    Javascript strings
+    """
     if not text:
         return ''
     def replace(match):
@@ -111,6 +132,17 @@ def javascript_quote(text):
     return _js_quote_re.sub(replace, text)
 
 
+def to_js_string(text):
+    """Embed the given string in a double quote delimited Javascript string
+    (conform to the JSON spec)
+    """
+    if not text:
+        return ''
+    def replace(match):
+        return _js_quote[match.group(0)]
+    return '"%s"' % _js_string_re.sub(replace, text)
+
+
 def unicode_quote(value, safe='/'):
     """A unicode aware version of `urllib.quote`
 
@@ -541,7 +573,7 @@ def normalize_whitespace(text, to_space=
 def unquote_label(txt):
     """Remove (one level of) enclosing single or double quotes.
 
-    .. versionadded :: 0.13
+    .. versionadded :: 1.0
     """
     return txt[1:-1] if txt and txt[0] in "'\"" and txt[0] == txt[-1] else txt
 
@@ -626,3 +658,22 @@ def unicode_to_base64(text, strip_newlin
 def unicode_from_base64(text):
     """Safe conversion of ``text`` to unicode based on utf-8 bytes."""
     return text.decode('base64').decode('utf-8')
+
+
+def levenshtein_distance(lhs, rhs):
+    """Return the Levenshtein distance between two strings."""
+    if len(lhs) > len(rhs):
+        rhs, lhs = lhs, rhs
+    if not lhs:
+        return len(rhs)
+
+    prev = range(len(rhs) + 1)
+    for lidx, lch in enumerate(lhs):
+        curr = [lidx + 1]
+        for ridx, rch in enumerate(rhs):
+            cost = (lch != rch) * 2
+            curr.append(min(prev[ridx + 1] + 1, # deletion
+                            curr[ridx] + 1,     # insertion
+                            prev[ridx] + cost)) # substitution
+        prev = curr
+    return prev[-1]

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/api.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/api.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/api.py Tue Oct 16 20:06:09 2012
@@ -663,7 +663,11 @@ class RepositoryManager(Component):
                 try:
                     changeset = repos.get_changeset(rev)
                 except NoSuchChangeset:
-                    continue
+                    try:
+                        repos.sync_changeset(rev)
+                        changeset = repos.get_changeset(rev)
+                    except NoSuchChangeset:
+                        continue
                 self.log.debug("Event %s on %s for revision %s",
                                event, repos.reponame or '(default)', rev)
                 for listener in self.change_listeners:
@@ -1139,7 +1143,7 @@ class Changeset(object):
     def get_tags(self):
         """Yield tags associated with this changeset.
 
-        .. versionadded :: 0.13
+        .. versionadded :: 1.0
         """
         return []
 

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/cache.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/cache.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/cache.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/cache.py Tue Oct 16 20:06:09 2012
@@ -30,6 +30,12 @@ _actionmap = {'A': Changeset.ADD, 'C': C
               'D': Changeset.DELETE, 'E': Changeset.EDIT,
               'M': Changeset.MOVE}
 
+def _invert_dict(d):
+    return dict(zip(d.values(), d.keys()))
+
+_inverted_kindmap = _invert_dict(_kindmap)
+_inverted_actionmap = _invert_dict(_actionmap)
+
 CACHE_REPOSITORY_DIR = 'repository_dir'
 CACHE_YOUNGEST_REV = 'youngest_rev'
 
@@ -89,10 +95,13 @@ class CachedRepository(Repository):
                     """, (self.id, srev)):
                 old_cset = Changeset(self.repos, cset.rev, message, author,
                                      from_utimestamp(time))
-            db("""UPDATE revision SET time=%s, author=%s, message=%s
-                  WHERE repos=%s AND rev=%s
-                  """, (to_utimestamp(cset.date), cset.author, cset.message,
-                        self.id, srev))
+            if old_cset:
+                db("""UPDATE revision SET time=%s, author=%s, message=%s
+                      WHERE repos=%s AND rev=%s
+                      """, (to_utimestamp(cset.date), cset.author,
+                            cset.message, self.id, srev))
+            else:
+                self._insert_changeset(db, rev, cset)
         return old_cset
 
     @cached('_metadata_id')
@@ -215,69 +224,69 @@ class CachedRepository(Repository):
                     self.repos.clear(youngest_rev=youngest)
                     return
 
-            # 1. prepare for resyncing
-            #    (there still might be a race condition at this point)
-
-            kindmap = dict(zip(_kindmap.values(), _kindmap.keys()))
-            actionmap = dict(zip(_actionmap.values(), _actionmap.keys()))
-
+            # prepare for resyncing (there might still be a race
+            # condition at this point)
             while next_youngest is not None:
                 srev = self.db_rev(next_youngest)
-                
+
                 with self.env.db_transaction as db:
-                    
-                    # 1.1 Attempt to resync the 'revision' table
                     self.log.info("Trying to sync revision [%s]",
                                   next_youngest)
                     cset = self.repos.get_changeset(next_youngest)
                     try:
-                        db("""INSERT INTO revision
-                                (repos, rev, time, author, message)
-                              VALUES (%s, %s, %s, %s, %s)
-                              """, (self.id, srev, to_utimestamp(cset.date),
-                                    cset.author, cset.message))
-                    except Exception, e: # *another* 1.1. resync attempt won 
+                        # steps 1. and 2.
+                        self._insert_changeset(db, next_youngest, cset)
+                    except Exception, e: # *another* 1.1. resync attempt won
                         self.log.warning('Revision %s already cached: %r',
                                          next_youngest, e)
-                        # also potentially in progress, so keep ''previous''
-                        # notion of 'youngest'
+                        # the other resync attempts is also
+                        # potentially still in progress, so for our
+                        # process/thread, keep ''previous'' notion of
+                        # 'youngest'
                         self.repos.clear(youngest_rev=youngest)
                         # FIXME: This aborts a containing transaction
                         db.rollback()
                         return
-    
-                    # 1.2. now *only* one process was able to get there
-                    #      (i.e. there *shouldn't* be any race condition here)
-    
-                    for path, kind, action, bpath, brev in cset.get_changes():
-                        self.log.debug("Caching node change in [%s]: %r",
-                                       next_youngest,
-                                       (path, kind, action, bpath, brev))
-                        kind = kindmap[kind]
-                        action = actionmap[action]
-                        db("""INSERT INTO node_change
-                                (repos, rev, path, node_type, change_type,
-                                 base_path, base_rev)
-                              VALUES (%s, %s, %s, %s, %s, %s, %s)
-                              """, (self.id, srev, path, kind, action, bpath,
-                                    brev))
-    
-                    # 1.3. update 'youngest_rev' metadata 
-                    #      (minimize possibility of failures at point 0.)
-                    db("""UPDATE repository SET value=%s
-                          WHERE id=%s AND name=%s
-                          """, (str(next_youngest), 
-                                self.id, CACHE_YOUNGEST_REV))
+
+                    # 3. update 'youngest_rev' metadata (minimize
+                    # possibility of failures at point 0.)
+                    db("""
+                        UPDATE repository SET value=%s WHERE id=%s AND name=%s
+                        """, (str(next_youngest), self.id, CACHE_YOUNGEST_REV))
                     del self.metadata
 
-                # 1.4. iterate (1.1 should always succeed now)
+                # 4. iterate (1. should always succeed now)
                 youngest = next_youngest
                 next_youngest = self.repos.next_rev(next_youngest)
 
-                # 1.5. provide some feedback
+                # 5. provide some feedback
                 if feedback:
                     feedback(youngest)
 
+    def _insert_changeset(self, db, rev, cset):
+        srev = self.db_rev(rev)
+        # 1. Attempt to resync the 'revision' table.  In case of
+        # concurrent syncs, only such insert into the `revision` table
+        # will succeed, the others will fail and raise an exception.
+        db("""
+            INSERT INTO revision (repos,rev,time,author,message)
+            VALUES (%s,%s,%s,%s,%s)
+            """, (self.id, srev, to_utimestamp(cset.date),
+                  cset.author, cset.message))
+        # 2. now *only* one process was able to get there (i.e. there
+        # *shouldn't* be any race condition here)
+        for path, kind, action, bpath, brev in cset.get_changes():
+            self.log.debug("Caching node change in [%s]: %r", rev,
+                           (path, kind, action, bpath, brev))
+            kind = _inverted_kindmap[kind]
+            action = _inverted_actionmap[action]
+            db("""
+                INSERT INTO node_change
+                    (repos,rev,path,node_type,change_type,base_path,
+                     base_rev)
+                VALUES (%s,%s,%s,%s,%s,%s,%s)
+                """, (self.id, srev, path, kind, action, bpath, brev))
+
     def get_node(self, path, rev=None):
         return self.repos.get_node(path, self.normalize_rev(rev))
 

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/diff.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/diff.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/diff.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/diff.py Tue Oct 16 20:06:09 2012
@@ -177,7 +177,7 @@ def filter_ignorable_lines(hunks, fromli
 
 
 def hdf_diff(*args, **kwargs):
-    """:deprecated: use `diff_blocks` (will be removed in 0.14)"""
+    """:deprecated: use `diff_blocks` (will be removed in 1.1.1)"""
     return diff_blocks(*args, **kwargs)
 
 def diff_blocks(fromlines, tolines, context=None, tabwidth=8,
@@ -309,7 +309,7 @@ def get_diff_options(req):
         pref = int(req.session.get('diff_' + name, default))
         arg = int(name in req.args)
         if 'update' in req.args and arg != pref:
-            req.session['diff_' + name] = arg
+            req.session.set('diff_' + name, arg, default)
         else:
             arg = pref
         return arg
@@ -317,7 +317,7 @@ def get_diff_options(req):
     pref = req.session.get('diff_style', 'inline')
     style = req.args.get('style', pref)
     if 'update' in req.args and style != pref:
-        req.session['diff_style'] = style
+        req.session.set('diff_style', style, 'inline')
     data['style'] = style
 
     pref = int(req.session.get('diff_contextlines', 2))
@@ -326,7 +326,7 @@ def get_diff_options(req):
     except ValueError:
         context = -1
     if 'update' in req.args and context != pref:
-        req.session['diff_contextlines'] = context
+        req.session.set('diff_contextlines', context, 2)
     options_data['contextlines'] = context
     
     arg = int(req.args.get('contextall', 0))