You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by rj...@apache.org on 2014/11/15 02:14:53 UTC

svn commit: r1639823 [27/29] - in /bloodhound/branches/trac-1.0.2-integration/trac: ./ contrib/ contrib/cgi-bin/ contrib/workflow/ doc/ doc/dev/ doc/utils/ sample-plugins/ sample-plugins/permissions/ sample-plugins/workflow/ trac/ trac/admin/ trac/admi...

Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/macros.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/macros.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/macros.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/macros.py Sat Nov 15 01:14:46 2014
@@ -1,15 +1,54 @@
 # -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-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/.
+
+from StringIO import StringIO
 from datetime import datetime
+import os
+import shutil
+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
 from trac.wiki.tests import formatter
 
+
+def add_pages(tc, names):
+    now = datetime.now(utc)
+    for name in names:
+        w = WikiPage(tc.env)
+        w.name = name
+        w.text = '--'
+        w.save('joe', 'the page ' + name, '::1', now)
+
+
 # == [[Image]]
 
+def image_setup(tc):
+    add_pages(tc, ['page:fr'])
+    from trac.attachment import Attachment
+    tc.env.path = tempfile.mkdtemp(prefix='trac-tempenv-')
+    attachment = Attachment(tc.env, 'wiki', 'page:fr')
+    attachment.description = "image in page:fr"
+    attachment.insert('img.png', StringIO(''), 0, 2)
+
+def image_teardown(tc):
+    shutil.rmtree(os.path.join(tc.env.path, 'files'))
+    os.rmdir(tc.env.path)
+    tc.env.reset_db()
+
 # Note: using `« test »` string in the following tests for checking
 #       unicode robustness and whitespace support (first space is
 #       normal ASCII SPACE, second is Unicode NO-BREAK SPACE).
@@ -91,13 +130,39 @@ IMAGE_MACRO_TEST_CASES = u"""
 ------------------------------
 <a style="padding:0; border:none" href="/wiki/WikiStart"><img src="/browser/%C2%AB%20test%C2%A0%C2%BB?format=raw" alt="/browser/« test »" title="/browser/« test »" /></a>
 ============================== Strip unicode white-spaces and ZWSPs (#10668)
-[[Image(  ​source:« test ».png  ​, nolink)]]
+[[Image(  ​source:« test ».png  ​, nolink, 100%  ​)]]
 ------------------------------
 <p>
-<img src="/browser/%C2%AB%20test%C2%A0%C2%BB.png?format=raw" alt="source:« test ».png" title="source:« test ».png" />
+<img width="100%" alt="source:« test ».png" title="source:« test ».png" src="/browser/%C2%AB%20test%C2%A0%C2%BB.png?format=raw" />
 </p>
 ------------------------------
-<img src="/browser/%C2%AB%20test%C2%A0%C2%BB.png?format=raw" alt="source:« test ».png" title="source:« test ».png" />
+<img width="100%" alt="source:« test ».png" title="source:« test ».png" src="/browser/%C2%AB%20test%C2%A0%C2%BB.png?format=raw" />
+------------------------------
+============================== Attachments on page with ':' characters (#10562)
+[[Image("page:fr":img.png​,nolink)]]
+------------------------------
+<p>
+<img src="/raw-attachment/wiki/page%3Afr/img.png" alt="image in page:fr" title="image in page:fr" />
+</p>
+------------------------------
+<img src="/raw-attachment/wiki/page%3Afr/img.png" alt="image in page:fr" title="image in page:fr" />
+------------------------------
+============================== htdocs: Image, nolink
+[[Image(htdocs:trac_logo.png, nolink)]]
+------------------------------
+<p>
+<img src="/chrome/site/trac_logo.png" alt="trac_logo.png" title="trac_logo.png" />
+</p>
+------------------------------
+<img src="/chrome/site/trac_logo.png" alt="trac_logo.png" title="trac_logo.png" />
+============================== shared: Image, nolink
+[[Image(shared:trac_logo.png, nolink)]]
+------------------------------
+<p>
+<img src="/chrome/shared/trac_logo.png" alt="trac_logo.png" title="trac_logo.png" />
+</p>
+------------------------------
+<img src="/chrome/shared/trac_logo.png" alt="trac_logo.png" title="trac_logo.png" />
 ------------------------------
 """
 
@@ -110,14 +175,6 @@ IMAGE_MACRO_TEST_CASES = u"""
 
 # == [[TitleIndex]]
 
-def add_pages(tc, names):
-    now = datetime.now(utc)
-    for name in names:
-        w = WikiPage(tc.env)
-        w.name = name
-        w.text = '--'
-        w.save('joe', 'the page ' + name, '::1', now)
-
 def titleindex_teardown(tc):
     tc.env.reset_db()
 
@@ -391,8 +448,36 @@ TRACINI_MACRO_TEST_CASES = u"""\
 </p><div class="tracini">\
 <h3 id="section-42-section"><code>[section-42]</code></h3>\
 <table class="wiki"><tbody>\
-<tr><td><tt>option1</tt></td><td></td><td class="default"><code>value</code></td></tr>\
-<tr><td><tt>option2</tt></td><td>blah</td><td class="default"><code>value</code></td></tr>\
+<tr class="even"><td><tt>option1</tt></td><td></td><td class="default"><code>value</code></td></tr>\
+<tr class="odd"><td><tt>option2</tt></td><td>blah</td><td class="default"><code>value</code></td></tr>\
+</tbody></table>\
+</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 class="even"><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 class="even"><td><tt>option1</tt></td><td></td><td class="nodefault">(no default)</td></tr>\
+<tr class="odd"><td><tt>option2</tt></td><td></td><td class="nodefault">(no default)</td></tr>\
+<tr class="even"><td><tt>option3</tt></td><td></td><td class="default"><code>0</code></td></tr>\
+<tr class="odd"><td><tt>option4</tt></td><td></td><td class="default"><code>disabled</code></td></tr>\
+<tr class="even"><td><tt>option5</tt></td><td></td><td class="default"><code></code></td></tr>\
 </tbody></table>\
 </div><p>
 </p>
@@ -404,6 +489,13 @@ 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
@@ -411,7 +503,9 @@ def tracini_teardown(tc):
 
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(formatter.suite(IMAGE_MACRO_TEST_CASES, file=__file__))
+    suite.addTest(formatter.suite(IMAGE_MACRO_TEST_CASES, file=__file__,
+                                  setup=image_setup,
+                                  teardown=image_teardown))
     suite.addTest(formatter.suite(TITLEINDEX1_MACRO_TEST_CASES, file=__file__))
     suite.addTest(formatter.suite(TITLEINDEX2_MACRO_TEST_CASES, file=__file__,
                                   setup=titleindex2_setup,

Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/model.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/model.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/model.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/model.py Sat Nov 15 01:14:46 2014
@@ -1,16 +1,28 @@
 # -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-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/.
 
 from __future__ import with_statement
 
 from datetime import datetime
-import os.path
 import shutil
 from StringIO import StringIO
 import tempfile
 import unittest
 
+import trac.tests.compat
 from trac.attachment import Attachment
 from trac.core import *
+from trac.resource import Resource
 from trac.test import EnvironmentStub
 from trac.tests.resource import TestResourceChangeListener
 from trac.util.datefmt import utc, to_utimestamp
@@ -48,8 +60,7 @@ class WikiPageTestCase(unittest.TestCase
 
     def setUp(self):
         self.env = EnvironmentStub()
-        self.env.path = os.path.join(tempfile.gettempdir(), 'trac-tempenv')
-        os.mkdir(self.env.path)
+        self.env.path = tempfile.mkdtemp(prefix='trac-tempenv-')
 
     def tearDown(self):
         shutil.rmtree(self.env.path)
@@ -57,14 +68,14 @@ class WikiPageTestCase(unittest.TestCase
 
     def test_new_page(self):
         page = WikiPage(self.env)
-        self.assertEqual(False, page.exists)
-        self.assertEqual(None, page.name)
+        self.assertFalse(page.exists)
+        self.assertIsNone(page.name)
         self.assertEqual(0, page.version)
         self.assertEqual('', page.text)
         self.assertEqual(0, page.readonly)
         self.assertEqual('', page.author)
         self.assertEqual('', page.comment)
-        self.assertEqual(None, page.time)
+        self.assertIsNone(page.time)
 
     def test_existing_page(self):
         t = datetime(2001, 1, 1, 1, 1, 1, 0, utc)
@@ -74,10 +85,10 @@ class WikiPageTestCase(unittest.TestCase
              'Testing', 0))
 
         page = WikiPage(self.env, 'TestPage')
-        self.assertEqual(True, page.exists)
+        self.assertTrue(page.exists)
         self.assertEqual('TestPage', page.name)
         self.assertEqual(1, page.version)
-        self.assertEqual(None, page.resource.version)   # FIXME: Intentional?
+        self.assertIsNone(page.resource.version)   # FIXME: Intentional?
         self.assertEqual('Bla bla', page.text)
         self.assertEqual(0, page.readonly)
         self.assertEqual('joe', page.author)
@@ -90,6 +101,11 @@ class WikiPageTestCase(unittest.TestCase
 
         page = WikiPage(self.env, 'TestPage', 1)
         self.assertEqual(1, page.resource.version)
+        self.assertEqual(1, page.version)
+
+        resource = Resource('wiki', 'TestPage')
+        page = WikiPage(self.env, resource, 1)
+        self.assertEqual(1, page.version)
 
     def test_create_page(self):
         page = WikiPage(self.env)
@@ -98,7 +114,7 @@ class WikiPageTestCase(unittest.TestCase
         t = datetime(2001, 1, 1, 1, 1, 1, 0, utc)
         page.save('joe', 'Testing', '::1', t)
 
-        self.assertEqual(True, page.exists)
+        self.assertTrue(page.exists)
         self.assertEqual(1, page.version)
         self.assertEqual(1, page.resource.version)
         self.assertEqual(0, page.readonly)
@@ -165,7 +181,7 @@ class WikiPageTestCase(unittest.TestCase
         page = WikiPage(self.env, 'TestPage')
         page.delete()
 
-        self.assertEqual(False, page.exists)
+        self.assertFalse(page.exists)
 
         self.assertEqual([], self.env.db_query("""
             SELECT version, time, author, ipnr, text, comment, readonly
@@ -184,7 +200,7 @@ class WikiPageTestCase(unittest.TestCase
         page = WikiPage(self.env, 'TestPage')
         page.delete(version=2)
 
-        self.assertEqual(True, page.exists)
+        self.assertTrue(page.exists)
         self.assertEqual(
             [(1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0)],
             self.env.db_query("""
@@ -203,7 +219,7 @@ class WikiPageTestCase(unittest.TestCase
         page = WikiPage(self.env, 'TestPage')
         page.delete(version=1)
 
-        self.assertEqual(False, page.exists)
+        self.assertFalse(page.exists)
 
         self.assertEqual([], self.env.db_query("""
             SELECT version, time, author, ipnr, text, comment, readonly
@@ -224,6 +240,7 @@ class WikiPageTestCase(unittest.TestCase
         page = WikiPage(self.env, 'TestPage')
         page.rename('PageRenamed')
         self.assertEqual('PageRenamed', page.name)
+        self.assertEqual('PageRenamed', page.resource.id)
 
         self.assertEqual([data], self.env.db_query("""
             SELECT version, time, author, ipnr, text, comment, readonly
@@ -236,8 +253,7 @@ class WikiPageTestCase(unittest.TestCase
         Attachment.delete_all(self.env, 'wiki', 'PageRenamed')
 
         old_page = WikiPage(self.env, 'TestPage')
-        self.assertEqual(False, old_page.exists)
-
+        self.assertFalse(old_page.exists)
 
         self.assertEqual([], self.env.db_query("""
             SELECT version, time, author, ipnr, text, comment, readonly
@@ -267,6 +283,24 @@ class WikiPageTestCase(unittest.TestCase
             page = WikiPage(self.env, 'TestPage')
             self.assertRaises(TracError, page.rename, name)
 
+    def test_invalid_version(self):
+        data = (1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0)
+        self.env.db_transaction(
+            "INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
+            ('TestPage',) + data)
+
+        self.assertRaises(ValueError, WikiPage, self.env,
+                          'TestPage', '1abc')
+
+        resource = Resource('wiki', 'TestPage')
+        self.assertRaises(ValueError, WikiPage, self.env,
+                          resource, '1abc')
+
+        resource = Resource('wiki', 'TestPage', '1abc')
+        page = WikiPage(self.env, resource)
+        self.assertEqual(1, page.version)
+
+
 class WikiResourceChangeListenerTestCase(unittest.TestCase):
     INITIAL_NAME = "Wiki page 1"
     INITIAL_TEXT = "some text"
@@ -332,12 +366,13 @@ class WikiResourceChangeListenerTestCase
         self.wiki_name = resource.name
         self.wiki_text = resource.text
 
+
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(WikiPageTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(
-        WikiResourceChangeListenerTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(WikiPageTestCase))
+    suite.addTest(unittest.makeSuite(WikiResourceChangeListenerTestCase))
     return suite
 
+
 if __name__ == '__main__':
     unittest.main(defaultTest='suite')

Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wiki-tests.txt
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wiki-tests.txt?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wiki-tests.txt (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wiki-tests.txt Sat Nov 15 01:14:46 2014
@@ -316,6 +316,13 @@ nolink:"<blink>"
 nolink:&#34;&lt;blink&gt;&#34;
 </p>
 ------------------------------
+============================== Bracketed links
+See <http://en.wikipedia.org/wiki/Mornington_Crescent_(game)>
+------------------------------
+<p>
+See &lt;<a class="ext-link" href="http://en.wikipedia.org/wiki/Mornington_Crescent_(game)"><span class="icon"></span>http://en.wikipedia.org/wiki/Mornington_Crescent_(game)</a>&gt;
+</p>
+------------------------------
 ============================================================
 
         Other Links
@@ -1177,6 +1184,35 @@ Inline  comment
 </p>
 ------------------------------
 Inline  comment
+============================== Exception with ascii bytes
+[[ValueErrorWithUtf8(error)]]
+------------------------------
+<p>
+<div class="system-message"><strong>Error: Macro ValueErrorWithUtf8(error) failed</strong><pre>error</pre></div>
+</p>
+------------------------------
+============================== Exception with utf-8 bytes
+{{{#!ValueErrorWithUtf8
+Érrör
+}}}
+[[ValueErrorWithUtf8(érrör)]]
+------------------------------
+<div class="system-message"><strong>Error: Processor ValueErrorWithUtf8 failed</strong><pre>Érrör
+</pre></div><p>
+<div class="system-message"><strong>Error: Macro ValueErrorWithUtf8(érrör) failed</strong><pre>érrör</pre></div>
+</p>
+------------------------------
+============================== TracError with unicode
+{{{#!TracErrorWithUnicode
+Érrör
+}}}
+[[TracErrorWithUnicode(érrör)]]
+------------------------------
+<div class="system-message"><strong>Error: Processor TracErrorWithUnicode failed</strong><pre>Érrör
+</pre></div><p>
+<div class="system-message"><strong>Error: Macro TracErrorWithUnicode(érrör) failed</strong><pre>érrör</pre></div>
+</p>
+------------------------------
 ============================================================
 
            Headings
@@ -2573,3 +2609,96 @@ c
 ------------------------------
  […]
 &gt; c
+============================== List immediately followed by binary inline markup, #11009
+***
+* list
+***
+------------------------------
+<p>
+<strong>*
+</strong></p>
+<ul><li>list
+</li></ul><p>
+<strong>*
+</strong></p>
+------------------------------
+============================== List immediately followed by binary inline markup 1, #11373
+ 1. normal in list
+''italic
+in paragraph
+
+ 1. normal in list
+//italic
+in paragraph
+------------------------------
+<ol><li>normal in list
+</li></ol><p>
+<em>italic
+in paragraph
+</em></p>
+<ol><li>normal in list
+</li></ol><p>
+<em>italic
+in paragraph
+</em></p>
+------------------------------
+============================== List immediately followed by binary inline markup 2, #11373
+ 1. //italic in list
+''italic
+in paragraph
+
+ 1. ''italic in list
+//italic
+in paragraph
+
+1. ''italic in list
+''italic
+in paragraph
+
+1. //italic in list
+//italic
+in paragraph
+------------------------------
+<ol><li><em>italic in list
+</em></li></ol><p>
+<em>italic
+in paragraph
+</em></p>
+<ol><li><em>italic in list
+</em></li></ol><p>
+<em>italic
+in paragraph
+</em></p>
+<ol><li><em>italic in list
+</em></li></ol><p>
+<em>italic
+in paragraph
+</em></p>
+<ol><li><em>italic in list
+</em></li></ol><p>
+<em>italic
+in paragraph
+</em></p>
+------------------------------
+============================== List immediately followed by binary inline markup 3, #11373
+ 1. ''italic in list
+'''''bolditalic
+in paragraph'''''
+------------------------------
+<ol><li><em>italic in list
+</em></li></ol><p>
+<strong><em>bolditalic
+in paragraph</em></strong>
+</p>
+------------------------------
+============================== List immediately followed by binary inline markup 4, #11373
+ 1. '''bold in list
+'''''bolditalic
+in paragraph'''''
+------------------------------
+<ol><li><strong>bold in list
+</strong></li></ol><p>
+<strong><em>bolditalic
+in paragraph</em></strong>
+</p>
+------------------------------

Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wikisyntax.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wikisyntax.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wikisyntax.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/tests/wikisyntax.py Sat Nov 15 01:14:46 2014
@@ -1,4 +1,15 @@
 # -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-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/.
 
 from datetime import datetime
 import unittest
@@ -7,6 +18,7 @@ from trac.util.datefmt import utc
 from trac.wiki.model import WikiPage
 from trac.wiki.tests import formatter
 
+
 TEST_CASES = u"""
 ============================== wiki: link resolver
 wiki:TestPage
@@ -711,6 +723,8 @@ nolink          http://noweb
     w.text = '--'
     w.save('joe', 'other third level of hierarchy', '::1', now)
 
+    tc.env.db_transaction("INSERT INTO ticket (id) VALUES ('123')")
+
 
 def wiki_teardown(tc):
     tc.env.reset_db()

Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/web_ui.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/web_ui.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/web_ui.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/wiki/web_ui.py Sat Nov 15 01:14:46 2014
@@ -37,10 +37,10 @@ from trac.util.text import shorten_line
 from trac.util.translation import _, tag_
 from trac.versioncontrol.diff import get_diff_options, diff_blocks
 from trac.web.api import IRequestHandler
-from trac.web.chrome import (Chrome, INavigationContributor, ITemplateProvider,
-                             add_ctxtnav, add_link, add_notice, add_script,
-                             add_stylesheet, add_warning, prevnext_nav,
-                             web_context)
+from trac.web.chrome import (Chrome, INavigationContributor,
+                             ITemplateProvider, add_ctxtnav, add_link,
+                             add_notice, add_script, add_stylesheet,
+                             add_warning, prevnext_nav, web_context)
 from trac.wiki.api import IWikiPageManipulator, WikiSystem, validate_page_name
 from trac.wiki.formatter import format_to, OneLinerFormatter
 from trac.wiki.model import WikiPage
@@ -55,14 +55,14 @@ class InvalidWikiPage(TracError):
 
 class WikiModule(Component):
 
-    implements(IContentConverter, INavigationContributor, IPermissionRequestor,
-               IRequestHandler, ITimelineEventProvider, ISearchSource,
-               ITemplateProvider)
+    implements(IContentConverter, INavigationContributor,
+               IPermissionRequestor, IRequestHandler, ITimelineEventProvider,
+               ISearchSource, ITemplateProvider)
 
     page_manipulators = ExtensionPoint(IWikiPageManipulator)
 
     max_size = IntOption('wiki', 'max_size', 262144,
-        """Maximum allowed wiki page size in bytes. (''since 0.11.2'')""")
+        """Maximum allowed wiki page size in characters. (''since 0.11.2'')""")
 
     PAGE_TEMPLATES_PREFIX = 'PageTemplates/'
     DEFAULT_PAGE_TEMPLATE = 'DefaultPage'
@@ -70,11 +70,11 @@ class WikiModule(Component):
     # IContentConverter methods
 
     def get_supported_conversions(self):
-        yield ('txt', _('Plain Text'), 'txt', 'text/x-trac-wiki', 'text/plain',
-               9)
+        yield ('txt', _("Plain Text"), 'txt', 'text/x-trac-wiki',
+               'text/plain', 9)
 
     def convert_content(self, req, mimetype, content, key):
-        return (content, 'text/plain;charset=utf-8')
+        return content, 'text/plain;charset=utf-8'
 
     # INavigationContributor methods
 
@@ -82,11 +82,12 @@ class WikiModule(Component):
         return 'wiki'
 
     def get_navigation_items(self, req):
-        if 'WIKI_VIEW' in req.perm('wiki'):
+        if 'WIKI_VIEW' in req.perm('wiki', 'WikiStart'):
             yield ('mainnav', 'wiki',
-                   tag.a(_('Wiki'), href=req.href.wiki(), accesskey=1))
+                   tag.a(_("Wiki"), href=req.href.wiki(), accesskey=1))
+        if 'WIKI_VIEW' in req.perm('wiki', 'TracGuide'):
             yield ('metanav', 'help',
-                   tag.a(_('Help/Guide'), href=req.href.wiki('TracGuide'),
+                   tag.a(_("Help/Guide"), href=req.href.wiki('TracGuide'),
                          accesskey=6))
 
     # IPermissionRequestor methods
@@ -119,10 +120,17 @@ class WikiModule(Component):
             raise TracError(_("Invalid Wiki page name '%(name)s'",
                               name=pagename))
 
+        if version is not None:
+            try:
+                version = int(version)
+            except (ValueError, TypeError):
+                raise ResourceNotFound(
+                    _('No version "%(num)s" for Wiki page "%(name)s"',
+                      num=version, name=pagename))
+
         page = WikiPage(self.env, pagename)
         versioned_page = WikiPage(self.env, pagename, version=version)
 
-        req.perm(page.resource).require('WIKI_VIEW')
         req.perm(versioned_page.resource).require('WIKI_VIEW')
 
         if version and versioned_page.version != int(version):
@@ -147,7 +155,8 @@ class WikiModule(Component):
                 if action == 'edit' and not has_collision and valid:
                     return self._do_save(req, versioned_page)
                 else:
-                    return self._render_editor(req, page, action, has_collision)
+                    return self._render_editor(req, page, action,
+                                               has_collision)
             elif action == 'delete':
                 self._do_delete(req, versioned_page)
             elif action == 'rename':
@@ -192,8 +201,8 @@ class WikiModule(Component):
 
         # Validate page size
         if len(req.args.get('text', '')) > self.max_size:
-            add_warning(req, _('The wiki page is too long (must be less '
-                               'than %(num)s characters)',
+            add_warning(req, _("The wiki page is too long (must be less "
+                               "than %(num)s characters)",
                                num=self.max_size))
             valid = False
 
@@ -202,12 +211,12 @@ class WikiModule(Component):
             for field, message in manipulator.validate_wiki_page(req, page):
                 valid = False
                 if field:
-                    add_warning(req, _("The Wiki page field '%(field)s' is "
-                                       "invalid: %(message)s",
-                                       field=field, message=message))
+                    add_warning(req, tag_("The Wiki page field '%(field)s'"
+                                          " is invalid: %(message)s",
+                                          field=field, message=message))
                 else:
-                    add_warning(req, _("Invalid Wiki page: %(message)s",
-                                       message=message))
+                    add_warning(req, tag_("Invalid Wiki page: %(message)s",
+                                          message=message))
         return valid
 
     def _page_data(self, req, page, action=''):
@@ -233,9 +242,10 @@ class WikiModule(Component):
         def version_info(v, last=0):
             return {'path': get_resource_name(self.env, page.resource),
                     # TRANSLATOR: wiki page
-                    'rev': v or _('currently edited'),
+                    'rev': v or _("currently edited"),
                     'shortrev': v or last + 1,
-                    'href': req.href.wiki(page.name, version=v) if v else None}
+                    'href': req.href.wiki(page.name, version=v)
+                            if v else None}
         changes = [{'diffs': diffs, 'props': [],
                     'new': version_info(new_version, old_version),
                     'old': version_info(old_version)}]
@@ -271,12 +281,12 @@ class WikiModule(Component):
             req.redirect(req.href.wiki())
         else:
             if version and old_version and version > old_version + 1:
-                add_notice(req, _('The versions %(from_)d to %(to)d of the '
-                                  'page %(name)s have been deleted.',
-                            from_=old_version + 1, to=version, name=page.name))
+                add_notice(req, _("The versions %(from_)d to %(to)d of the "
+                                  "page %(name)s have been deleted.",
+                           from_=old_version + 1, to=version, name=page.name))
             else:
-                add_notice(req, _('The version %(version)d of the page '
-                                  '%(name)s has been deleted.',
+                add_notice(req, _("The version %(version)d of the page "
+                                  "%(name)s has been deleted.",
                                   version=version, name=page.name))
             req.redirect(req.href.wiki(page.name))
 
@@ -319,6 +329,14 @@ class WikiModule(Component):
                           new_name, old_version, old_name, new_name)
                 redirection.save(author, comment, req.remote_addr)
 
+        add_notice(req, _("The page %(old_name)s has been renamed to "
+                          "%(new_name)s.", old_name=old_name,
+                          new_name=new_name))
+        if redirect:
+            add_notice(req, _("The page %(old_name)s has been recreated "
+                              "with a redirect to %(new_name)s.",
+                              old_name=old_name, new_name=new_name))
+
         req.redirect(req.href.wiki(old_name if redirect else new_name))
 
     def _do_save(self, req, page):
@@ -394,8 +412,8 @@ class WikiModule(Component):
 
     def _render_diff(self, req, page):
         if not page.exists:
-            raise TracError(_('Version %(num)s of page "%(name)s" does not '
-                              'exist',
+            raise TracError(_("Version %(num)s of page \"%(name)s\" does not "
+                              "exist",
                               num=req.args.get('version'), name=page.name))
 
         old_version = req.args.get('old_version')
@@ -446,13 +464,13 @@ class WikiModule(Component):
         if prev_version:
             add_link(req, 'prev', req.href.wiki(page.name, action='diff',
                                                 version=prev_version),
-                     _('Version %(num)s', num=prev_version))
+                     _("Version %(num)s", num=prev_version))
         add_link(req, 'up', req.href.wiki(page.name, action='history'),
                  _('Page history'))
         if next_version:
             add_link(req, 'next', req.href.wiki(page.name, action='diff',
                                                 version=next_version),
-                     _('Version %(num)s', num=next_version))
+                     _("Version %(num)s", num=next_version))
 
         data = self._page_data(req, page, 'diff')
         data.update({
@@ -465,8 +483,8 @@ class WikiModule(Component):
             'changes': changes,
             'diff': diff_data,
         })
-        prevnext_nav(req, _('Previous Change'), _('Next Change'),
-                     _('Wiki History'))
+        prevnext_nav(req, _("Previous Change"), _("Next Change"),
+                     _("Wiki History"))
         return 'wiki_diff.html', data, None
 
     def _render_editor(self, req, page, action='edit', has_collision=False):
@@ -479,6 +497,8 @@ class WikiModule(Component):
 
         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
@@ -489,7 +509,7 @@ class WikiModule(Component):
             template = self.PAGE_TEMPLATES_PREFIX + req.args.get('template')
             template_page = WikiPage(self.env, template)
             if template_page and template_page.exists and \
-                   'WIKI_VIEW' in req.perm(template_page.resource):
+                    'WIKI_VIEW' in req.perm(template_page.resource):
                 page.text = template_page.text
         elif 'version' in req.args:
             old_page = WikiPage(self.env, page.name,
@@ -528,9 +548,11 @@ class WikiModule(Component):
         data = self._page_data(req, page, action)
         context = web_context(req, page.resource)
         data.update({
+            'context': context,
             'author': author,
             'comment': comment,
-            'edit_rows': editrows, 'sidebyside': sidebyside,
+            'edit_rows': editrows,
+            'sidebyside': sidebyside,
             'scroll_bar_pos': req.args.get('scroll_bar_pos', ''),
             'diff': None,
             'attachments': AttachmentModule(self.env).attachment_data(context),
@@ -574,7 +596,7 @@ class WikiModule(Component):
             })
         data.update({'history': history, 'resource': page.resource})
         add_ctxtnav(req, _("Back to %(wikipage)s", wikipage=page.name),
-                           req.href.wiki(page.name))
+                    req.href.wiki(page.name))
         return 'history_view.html', data, None
 
     def _render_view(self, req, page):
@@ -582,13 +604,10 @@ class WikiModule(Component):
 
         # Add registered converters
         if page.exists:
-            for conversion in Mimeview(self.env).get_supported_conversions(
-                                                 'text/x-trac-wiki'):
+            for conversion in Mimeview(self.env) \
+                              .get_supported_conversions('text/x-trac-wiki'):
                 conversion_href = req.href.wiki(page.name, version=version,
                                                 format=conversion[0])
-                # or...
-                conversion_href = get_resource_url(self.env, page.resource,
-                                                req.href, format=conversion[0])
                 add_link(req, 'alternate', conversion_href, conversion[1],
                          conversion[3])
 
@@ -601,7 +620,7 @@ class WikiModule(Component):
         higher, related = [], []
         if not page.exists:
             if 'WIKI_CREATE' not in req.perm(page.resource):
-                raise ResourceNotFound(_('Page %(name)s not found',
+                raise ResourceNotFound(_("Page %(name)s not found",
                                          name=page.name))
             formatter = OneLinerFormatter(self.env, context)
             if '/' in page.name:
@@ -610,7 +629,7 @@ class WikiModule(Component):
                     name = '/'.join(parts[:i] + [parts[-1]])
                     if not ws.has_page(name):
                         higher.append(ws._format_link(formatter, 'wiki',
-                                                    '/' + name, name, False))
+                                                      '/' + name, name, False))
             else:
                 name = page.name
             name = name.lower()
@@ -623,7 +642,6 @@ class WikiModule(Component):
                        for each in related]
 
         latest_page = WikiPage(self.env, page.name, version=None)
-        req.perm(latest_page.resource).require('WIKI_VIEW')
 
         prev_version = next_version = None
         if version:
@@ -650,12 +668,12 @@ class WikiModule(Component):
         if prev_version:
             add_link(req, 'prev',
                      req.href.wiki(page.name, version=prev_version),
-                     _('Version %(num)s', num=prev_version))
+                     _("Version %(num)s", num=prev_version))
 
         parent = None
         if version:
             add_link(req, 'up', req.href.wiki(page.name, version=None),
-                     _('View latest version'))
+                     _("View latest version"))
         elif '/' in page.name:
             parent = page.name[:page.name.rindex('/')]
             add_link(req, 'up', req.href.wiki(parent, version=None),
@@ -668,8 +686,8 @@ class WikiModule(Component):
 
         # Add ctxtnav entries
         if version:
-            prevnext_nav(req, _('Previous Version'), _('Next Version'),
-                         _('View Latest Version'))
+            prevnext_nav(req, _("Previous Version"), _("Next Version"),
+                         _("View Latest Version"))
         else:
             if parent:
                 add_ctxtnav(req, _('Up'), req.href.wiki(parent))
@@ -697,10 +715,10 @@ class WikiModule(Component):
 
     def _wiki_ctxtnav(self, req, page):
         """Add the normal wiki ctxtnav entries."""
-        add_ctxtnav(req, _('Start Page'), req.href.wiki('WikiStart'))
-        add_ctxtnav(req, _('Index'), req.href.wiki('TitleIndex'))
+        add_ctxtnav(req, _("Start Page"), req.href.wiki('WikiStart'))
+        add_ctxtnav(req, _("Index"), req.href.wiki('TitleIndex'))
         if page.exists:
-            add_ctxtnav(req, _('History'), req.href.wiki(page.name,
+            add_ctxtnav(req, _("History"), req.href.wiki(page.name,
                                                          action='history'))
 
     # ITimelineEventProvider methods
@@ -724,7 +742,7 @@ class WikiModule(Component):
 
             # Attachments
             for event in AttachmentModule(self.env).get_timeline_events(
-                req, wiki_realm, start, stop):
+                    req, wiki_realm, start, stop):
                 yield event
 
     def render_timeline_event(self, context, field, event):
@@ -734,9 +752,9 @@ class WikiModule(Component):
         elif field == 'title':
             name = tag.em(get_resource_name(self.env, wiki_page))
             if wiki_page.version > 1:
-                return tag_('%(page)s edited', page=name)
+                return tag_("%(page)s edited", page=name)
             else:
-                return tag_('%(page)s created', page=name)
+                return tag_("%(page)s created", page=name)
         elif field == 'description':
             markup = format_to(self.env, None,
                                context.child(resource=wiki_page), comment)
@@ -744,7 +762,7 @@ class WikiModule(Component):
                 diff_href = context.href.wiki(
                     wiki_page.id, version=wiki_page.version, action='diff')
                 markup = tag(markup,
-                             ' (', tag.a(_('diff'), href=diff_href), ')')
+                             " (", tag.a(_("diff"), href=diff_href), ")")
             return markup
 
     # ISearchSource methods
@@ -775,5 +793,5 @@ class WikiModule(Component):
 
         # Attachments
         for result in AttachmentModule(self.env).get_search_results(
-            req, wiki_realm, terms):
+                req, wiki_realm, terms):
             yield result

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/enscript.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/enscript.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/enscript.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/enscript.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2004-2009 Edgewall Software
+# Copyright (C) 2004-2013 Edgewall Software
 # Copyright (C) 2004 Daniel Lundin <da...@edgewall.com>
 # Copyright (C) 2005 Christopher Lenz <cm...@gmx.de>
 # All rights reserved.

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/php.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/php.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/php.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/php.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2005-2009 Edgewall Software
+# Copyright (C) 2005-2013 Edgewall Software
 # Copyright (C) 2005 Christian Boos <cb...@bct-technology.com>
 # Copyright (C) 2005 Christopher Lenz <cm...@gmx.de>
 # All rights reserved.

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/silvercity.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/silvercity.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/silvercity.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/silvercity.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2004-2009 Edgewall Software
+# Copyright (C) 2004-2013 Edgewall Software
 # Copyright (C) 2004 Daniel Lundin <da...@edgewall.com>
 # All rights reserved.
 #

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/__init__.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/__init__.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/__init__.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2009 Edgewall Software
+# Copyright (C) 2009-2013 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/php.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/php.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/php.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/mimeview/tests/php.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C)2006-2009 Edgewall Software
+# Copyright (C) 2006-2013 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -140,8 +140,8 @@ def suite():
     suite = unittest.TestSuite()
     php = locate("php")
     if php:
-        suite.addTest(unittest.makeSuite(PhpDeuglifierTestCase, 'test'))
-        suite.addTest(unittest.makeSuite(PhpRendererTestCase, 'test'))
+        suite.addTest(unittest.makeSuite(PhpDeuglifierTestCase))
+        suite.addTest(unittest.makeSuite(PhpRendererTestCase))
     else:
         print("SKIP: tracopt/mimeview/tests/php.py (php cli binary, 'php', "
               "not found)")

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/authz_policy.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/authz_policy.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/authz_policy.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/authz_policy.py Sat Nov 15 01:14:46 2014
@@ -14,17 +14,19 @@
 #
 # Author: Alec Thomas <al...@swapoff.org>
 
-from fnmatch import fnmatch
+from fnmatch import fnmatchcase
 from itertools import groupby
 import os
 
 from trac.core import *
-from trac.config import Option
+from trac.config import ConfigurationError, Option
 from trac.perm import PermissionSystem, IPermissionPolicy
+from trac.util import lazy
+from trac.util.text import to_unicode
 
 ConfigObj = None
 try:
-    from configobj import ConfigObj
+    from configobj import ConfigObj, ConfigObjError
 except ImportError:
     pass
 
@@ -138,12 +140,8 @@ class AuthzPolicy(Component):
     # IPermissionPolicy methods
 
     def check_permission(self, action, username, resource, perm):
-        if ConfigObj is None:
-            self.log.error('configobj package not found')
-            return None
-
-        if self.authz_file and not self.authz_mtime or \
-                os.path.getmtime(self.get_authz_file()) > self.authz_mtime:
+        if not self.authz_mtime or \
+                os.path.getmtime(self.get_authz_file) > self.authz_mtime:
             self.parse_authz()
         resource_key = self.normalise_resource(resource)
         self.log.debug('Checking %s on %s', action, resource_key)
@@ -166,19 +164,42 @@ class AuthzPolicy(Component):
 
     # Internal methods
 
+    @lazy
     def get_authz_file(self):
-        f = self.authz_file
-        return f if os.path.isabs(f) else os.path.join(self.env.path, f)
+        if not self.authz_file:
+            self.log.error('The `[authz_policy] authz_file` configuration '
+                           'option in trac.ini is empty or not defined.')
+            raise ConfigurationError()
+
+        authz_file = self.authz_file if os.path.isabs(self.authz_file) \
+                                     else os.path.join(self.env.path,
+                                                       self.authz_file)
+        try:
+            os.stat(authz_file)
+        except OSError, e:
+            self.log.error("Error parsing authz permission policy file: %s",
+                           to_unicode(e))
+            raise ConfigurationError()
+        return authz_file
 
     def parse_authz(self):
+        if ConfigObj is None:
+            self.log.error('ConfigObj package not found.')
+            raise ConfigurationError()
         self.log.debug('Parsing authz security policy %s',
-                       self.get_authz_file())
-        self.authz = ConfigObj(self.get_authz_file(), encoding='utf8')
+                       self.get_authz_file)
+        try:
+            self.authz = ConfigObj(self.get_authz_file, encoding='utf8',
+                                   raise_errors=True)
+        except ConfigObjError, e:
+            self.log.error("Error parsing authz permission policy file: %s",
+                           to_unicode(e))
+            raise ConfigurationError()
         groups = {}
         for group, users in self.authz.get('groups', {}).iteritems():
             if isinstance(users, basestring):
                 users = [users]
-            groups[group] = users
+            groups[group] = map(to_unicode, users)
 
         self.groups_by_user = {}
 
@@ -192,31 +213,32 @@ class AuthzPolicy(Component):
         for group, users in groups.iteritems():
             add_items('@' + group, users)
 
-        self.authz_mtime = os.path.getmtime(self.get_authz_file())
+        self.authz_mtime = os.path.getmtime(self.get_authz_file)
 
     def normalise_resource(self, resource):
+        def to_descriptor(resource):
+            id = resource.id
+            return '%s:%s@%s' % (resource.realm or '*',
+                                 id if id is not None else '*',
+                                 resource.version or '*')
+
         def flatten(resource):
             if not resource:
                 return ['*:*@*']
-            if not (resource.realm or resource.id):
-                return ['%s:%s@%s' % (resource.realm or '*',
-                                      resource.id or '*',
-                                      resource.version or '*')]
+            descriptor = to_descriptor(resource)
+            if not resource.realm and resource.id is None:
+                return [descriptor]
             # XXX Due to the mixed functionality in resource we can end up with
             # ticket, ticket:1, ticket:1@10. This code naively collapses all
             # subsets of the parent resource into one. eg. ticket:1@10
             parent = resource.parent
-            while parent and (resource.realm == parent.realm or
-                              (resource.realm == parent.realm and
-                               resource.id == parent.id)):
+            while parent and resource.realm == parent.realm:
                 parent = parent.parent
             if parent:
-                parent = flatten(parent)
+                return flatten(parent) + [descriptor]
             else:
-                parent = []
-            return parent + ['%s:%s@%s' % (resource.realm or '*',
-                                           resource.id or '*',
-                                           resource.version or '*')]
+                return [descriptor]
+
         return '/'.join(flatten(resource))
 
     def authz_permissions(self, resource_key, username):
@@ -227,14 +249,15 @@ class AuthzPolicy(Component):
         else:
             valid_users = ['*', 'anonymous']
         for resource_section in [a for a in self.authz.sections
-                                 if a != 'groups']:
-            resource_glob = resource_section
+                                   if a != 'groups']:
+            resource_glob = to_unicode(resource_section)
             if '@' not in resource_glob:
                 resource_glob += '@*'
 
-            if fnmatch(resource_key, resource_glob):
+            if fnmatchcase(resource_key, resource_glob):
                 section = self.authz[resource_section]
                 for who, permissions in section.iteritems():
+                    who = to_unicode(who)
                     if who in valid_users or \
                             who in self.groups_by_user.get(username, []):
                         self.log.debug('%s matched section %s for user %s',

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/config_perm_provider.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/config_perm_provider.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/config_perm_provider.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/config_perm_provider.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2009 Edgewall Software
+# Copyright (C) 2009-2013 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -17,7 +17,10 @@ from trac.perm import IPermissionRequest
 
 
 class ExtraPermissionsProvider(Component):
-    """Extra permission provider."""
+    """Define arbitrary permissions.
+
+    Documentation can be found on the [wiki:TracIni#extra-permissions-section]
+    page after enabling the component."""
 
     implements(IPermissionRequestor)
 
@@ -31,17 +34,21 @@ class ExtraPermissionsProvider(Component
         and a comma-separated list of permissions. For example:
         {{{
         [extra-permissions]
-        extra_admin = extra_view, extra_modify, extra_delete
+        EXTRA_ADMIN = EXTRA_VIEW, EXTRA_MODIFY, EXTRA_DELETE
         }}}
         This entry will define three new permissions `EXTRA_VIEW`,
         `EXTRA_MODIFY` and `EXTRA_DELETE`, as well as a meta-permissions
         `EXTRA_ADMIN` that grants all three permissions.
 
+        The permissions are created in upper-case characters regardless of
+        the casing of the definitions in `trac.ini`. For example, the
+        definition `extra_view` would create the permission `EXTRA_VIEW`.
+
         If you don't want a meta-permission, start the meta-name with an
         underscore (`_`):
         {{{
         [extra-permissions]
-        _perms = extra_view, extra_modify
+        _perms = EXTRA_VIEW, EXTRA_MODIFY
         }}}
         """)
 

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/__init__.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/__init__.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/__init__.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2012 Edgewall Software
+# Copyright (C) 2012-2013 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/authz_policy.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/authz_policy.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/authz_policy.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/perm/tests/authz_policy.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2012 Edgewall Software
+# Copyright (C) 2012-2013 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -19,9 +19,13 @@ try:
 except ImportError:
     ConfigObj = None
 
+import trac.tests.compat
+from trac.config import ConfigurationError
+from trac.perm import PermissionCache
 from trac.resource import Resource
-from trac.test import EnvironmentStub
+from trac.test import EnvironmentStub, Mock
 from trac.util import create_file
+from trac.versioncontrol.api import Repository
 from tracopt.perm.authz_policy import AuthzPolicy
 
 
@@ -45,8 +49,33 @@ administrators = éat
 änon =
 @administrators = WIKI_VIEW
 * =
+
+# Tickets
+[ticket:43]
+änon = TICKET_VIEW
+@administrators =
+* =
+
+[ticket:*]
+änon =
+@administrators = TICKET_VIEW
+* =
+
+# Default repository
+[repository:@*]
+änon =
+@administrators = BROWSER_VIEW, FILE_VIEW
+* =
+
+# Non-default repository
+[repository:bláh@*]
+änon = BROWSER_VIEW, FILE_VIEW
+@administrators = BROWSER_VIEW, FILE_VIEW
+* =
 """)
-        self.env = EnvironmentStub(enable=[AuthzPolicy])
+        self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir)
+        self.env.config.set('trac', 'permission_policies',
+                            'AuthzPolicy, DefaultPermissionPolicy')
         self.env.config.set('authz_policy', 'authz_file', self.authz_file)
         self.authz_policy = AuthzPolicy(self.env)
 
@@ -57,32 +86,168 @@ administrators = éat
     def check_permission(self, action, user, resource, perm):
         return self.authz_policy.check_permission(action, user, resource, perm)
 
+    def get_repository(self, reponame):
+        params = {'id': 1, 'name': reponame}
+        return Mock(Repository, 'mock', params, self.env.log)
+
+    def get_perm(self, username, *args):
+        perm = PermissionCache(self.env, username)
+        if args:
+            return perm(*args)
+        return perm
+
     def test_unicode_username(self):
         resource = Resource('wiki', 'WikiStart')
+
+        perm = self.get_perm('anonymous')
         self.assertEqual(
             False,
-            self.check_permission('WIKI_VIEW', 'anonymous', resource, None))
+            self.check_permission('WIKI_VIEW', 'anonymous', resource, perm))
+        self.assertNotIn('WIKI_VIEW', perm)
+        self.assertNotIn('WIKI_VIEW', perm(resource))
+
+        perm = self.get_perm(u'änon')
         self.assertEqual(
             True,
-            self.check_permission('WIKI_VIEW', u'änon', resource, None))
+            self.check_permission('WIKI_VIEW', u'änon', resource, perm))
+        self.assertNotIn('WIKI_VIEW', perm)
+        self.assertIn('WIKI_VIEW', perm(resource))
 
     def test_unicode_resource_name(self):
         resource = Resource('wiki', u'résumé')
+
+        perm = self.get_perm('anonymous')
         self.assertEqual(
             False,
-            self.check_permission('WIKI_VIEW', 'anonymous', resource, None))
+            self.check_permission('WIKI_VIEW', 'anonymous', resource, perm))
+        self.assertNotIn('WIKI_VIEW', perm)
+        self.assertNotIn('WIKI_VIEW', perm(resource))
+
+        perm = self.get_perm(u'änon')
         self.assertEqual(
             False,
-            self.check_permission('WIKI_VIEW', u'änon', resource, None))
+            self.check_permission('WIKI_VIEW', u'änon', resource, perm))
+        self.assertNotIn('WIKI_VIEW', perm)
+        self.assertNotIn('WIKI_VIEW', perm(resource))
+
+        perm = self.get_perm(u'éat')
         self.assertEqual(
             True,
-            self.check_permission('WIKI_VIEW', u'éat', resource, None))
+            self.check_permission('WIKI_VIEW', u'éat', resource, perm))
+        self.assertNotIn('WIKI_VIEW', perm)
+        self.assertIn('WIKI_VIEW', perm(resource))
+
+    def test_resource_without_id(self):
+        perm = self.get_perm('anonymous')
+        self.assertNotIn('TICKET_VIEW', perm)
+        self.assertNotIn('TICKET_VIEW', perm('ticket'))
+        self.assertNotIn('TICKET_VIEW', perm('ticket', 42))
+        self.assertNotIn('TICKET_VIEW', perm('ticket', 43))
+
+        perm = self.get_perm(u'änon')
+        self.assertNotIn('TICKET_VIEW', perm)
+        self.assertNotIn('TICKET_VIEW', perm('ticket'))
+        self.assertNotIn('TICKET_VIEW', perm('ticket', 42))
+        self.assertIn('TICKET_VIEW', perm('ticket', 43))
+
+        perm = self.get_perm(u'éat')
+        self.assertNotIn('TICKET_VIEW', perm)
+        self.assertIn('TICKET_VIEW', perm('ticket'))
+        self.assertIn('TICKET_VIEW', perm('ticket', 42))
+        self.assertNotIn('TICKET_VIEW', perm('ticket', 43))
+
+    def test_default_repository(self):
+        repos = self.get_repository('')
+        self.assertEqual(False, repos.is_viewable(self.get_perm('anonymous')))
+        self.assertEqual(False, repos.is_viewable(self.get_perm(u'änon')))
+        self.assertEqual(True, repos.is_viewable(self.get_perm(u'éat')))
+
+    def test_non_default_repository(self):
+        repos = self.get_repository(u'bláh')
+        self.assertEqual(False, repos.is_viewable(self.get_perm('anonymous')))
+        self.assertEqual(True, repos.is_viewable(self.get_perm(u'änon')))
+        self.assertEqual(True, repos.is_viewable(self.get_perm(u'éat')))
+
+    def test_case_sensitive_resource(self):
+        resource = Resource('WIKI', 'wikistart')
+        self.assertEqual(
+            None,
+            self.check_permission('WIKI_VIEW', 'anonymous', resource, None))
+        self.assertEqual(
+            None,
+            self.check_permission('WIKI_VIEW', u'änon', resource, None))
+
+    def test_get_authz_file(self):
+        """get_authz_file should resolve a relative path and lazily compute.
+        """
+        authz_file = self.authz_policy.get_authz_file
+        self.assertEqual(os.path.join(self.env.path, 'trac-authz-policy'),
+                         authz_file)
+        self.assertIs(authz_file, self.authz_policy.get_authz_file)
+
+    def test_get_authz_file_notfound_raises(self):
+        """ConfigurationError exception should be raised if file not found."""
+        authz_file = os.path.join(self.env.path, 'some-nonexistent-file')
+        self.env.config.set('authz_policy', 'authz_file', authz_file)
+        self.assertRaises(ConfigurationError, getattr, self.authz_policy,
+                          'get_authz_file')
+
+    def test_get_authz_file_notdefined_raises(self):
+        """ConfigurationError exception should be raised if the option
+        `[authz_policy] authz_file` is not specified in trac.ini."""
+        self.env.config.remove('authz_policy', 'authz_file')
+        self.assertRaises(ConfigurationError, getattr, self.authz_policy,
+                          'get_authz_file')
+
+    def test_get_authz_file_empty_raises(self):
+        """ConfigurationError exception should be raised if the option
+        `[authz_policy] authz_file` is empty."""
+        self.env.config.set('authz_policy', 'authz_file', '')
+        self.assertRaises(ConfigurationError, getattr, self.authz_policy,
+                          'get_authz_file')
+
+    def test_parse_authz_empty(self):
+        """Allow the file to be empty."""
+        create_file(self.authz_file, '')
+        self.authz_policy.parse_authz()
+        self.assertFalse(self.authz_policy.authz)
+
+    def test_parse_authz_no_settings(self):
+        """Allow the file to have no settings."""
+        create_file(self.authz_file, """\
+# [wiki:WikiStart]
+# änon = WIKI_VIEW
+# * =
+""")
+        self.authz_policy.parse_authz()
+        self.assertFalse(self.authz_policy.authz)
+
+    def test_parse_authz_malformed_raises(self):
+        """ConfigurationError should be raised if the file is malformed."""
+        create_file(self.authz_file, """\
+wiki:WikiStart]
+änon = WIKI_VIEW
+* =
+""")
+        self.assertRaises(ConfigurationError, self.authz_policy.parse_authz)
+
+    def test_parse_authz_duplicated_sections_raises(self):
+        """ConfigurationError should be raised if the file has duplicate
+        sections."""
+        create_file(self.authz_file, """\
+[wiki:WikiStart]
+änon = WIKI_VIEW
+
+[wiki:WikiStart]
+änon = WIKI_VIEW
+""")
+        self.assertRaises(ConfigurationError, self.authz_policy.parse_authz)
 
 
 def suite():
     suite = unittest.TestSuite()
     if ConfigObj:
-        suite.addTest(unittest.makeSuite(AuthzPolicyTestCase, 'test'))
+        suite.addTest(unittest.makeSuite(AuthzPolicyTestCase))
     else:
         print "SKIP: tracopt/perm/tests/authz_policy.py (no configobj " + \
               "installed)"

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/commit_updater.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/commit_updater.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/commit_updater.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/commit_updater.py Sat Nov 15 01:14:46 2014
@@ -50,7 +50,7 @@ from trac.ticket import Ticket
 from trac.ticket.notification import TicketNotifyEmail
 from trac.util.datefmt import utc
 from trac.util.text import exception_to_unicode
-from trac.util.translation import cleandoc_
+from trac.util.translation import _, cleandoc_
 from trac.versioncontrol import IRepositoryChangeListener, RepositoryManager
 from trac.versioncontrol.web_ui.changeset import ChangesetModule
 from trac.wiki.formatter import format_to_html
@@ -182,10 +182,11 @@ class CommitTicketUpdater(Component):
 
     def _parse_message(self, message):
         """Parse the commit message and return the ticket references."""
-        cmd_groups = self.command_re.findall(message)
+        cmd_groups = self.command_re.finditer(message)
         functions = self._get_functions()
         tickets = {}
-        for cmd, tkts in cmd_groups:
+        for m in cmd_groups:
+            cmd, tkts = m.group('action', 'ticket')
             func = functions.get(cmd.lower())
             if not func and self.commands_refs.strip() == '<ALL>':
                 func = self.cmd_refs
@@ -208,7 +209,8 @@ In [changeset:"%s"]:
 
     def _update_tickets(self, tickets, changeset, comment, date):
         """Update the tickets with the given comment."""
-        perm = PermissionCache(self.env, changeset.author)
+        authname = self._authname(changeset)
+        perm = PermissionCache(self.env, authname)
         for tkt_id, cmds in tickets.iteritems():
             try:
                 self.log.debug("Updating ticket #%d", tkt_id)
@@ -220,7 +222,7 @@ In [changeset:"%s"]:
                         if cmd(ticket, changeset, ticket_perm) is not False:
                             save = True
                     if save:
-                        ticket.save_changes(changeset.author, comment, date)
+                        ticket.save_changes(authname, comment, date)
                 if save:
                     self._notify(ticket, date)
             except Exception, e:
@@ -250,23 +252,29 @@ In [changeset:"%s"]:
                 functions[cmd] = func
         return functions
 
+    def _authname(self, changeset):
+        return changeset.author.lower() \
+               if self.env.config.getbool('trac', 'ignore_auth_case') \
+               else changeset.author
+
     # Command-specific behavior
     # The ticket isn't updated if all extracted commands return False.
 
     def cmd_close(self, ticket, changeset, perm):
+        authname = self._authname(changeset)
         if self.check_perms and not 'TICKET_MODIFY' in perm:
             self.log.info("%s doesn't have TICKET_MODIFY permission for #%d",
-                          changeset.author, ticket.id)
+                          authname, ticket.id)
             return False
         ticket['status'] = 'closed'
         ticket['resolution'] = 'fixed'
         if not ticket['owner']:
-            ticket['owner'] = changeset.author
+            ticket['owner'] = authname
 
     def cmd_refs(self, ticket, changeset, perm):
         if self.check_perms and not 'TICKET_APPEND' in perm:
             self.log.info("%s doesn't have TICKET_APPEND permission for #%d",
-                          changeset.author, ticket.id)
+                          self._authname(changeset), ticket.id)
             return False
 
 
@@ -302,8 +310,8 @@ class CommitTicketReferenceMacro(WikiMac
             ticket_re = CommitTicketUpdater.ticket_re
             if not any(int(tkt_id) == int(formatter.context.resource.id)
                        for tkt_id in ticket_re.findall(message)):
-                return tag.p("(The changeset message doesn't reference this "
-                             "ticket)", class_='hint')
+                return tag.p(_("(The changeset message doesn't reference this "
+                               "ticket)"), class_='hint')
         if ChangesetModule(self.env).wiki_format_messages:
             return tag.div(format_to_html(self.env,
                 formatter.context.child('changeset', rev, parent=resource),

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/templates/ticket_delete.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/templates/ticket_delete.html?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/templates/ticket_delete.html (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/ticket/templates/ticket_delete.html Sat Nov 15 01:14:46 2014
@@ -1,3 +1,13 @@
+<!--!  Copyright (C) 2010-2014 Edgewall Software
+
+  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.com/license.html.
+
+  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/.
+-->
 <!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@@ -38,8 +48,8 @@
             </p>
           </div>
           <div class="buttons">
+            <input type="submit" class="trac-disable-on-submit" value="${_('Delete ticket')}"/>
             <input type="submit" name="cancel" value="${_('Cancel')}"/>
-            <input type="submit" value="${_('Delete ticket')}"/>
           </div>
         </form>
       </py:when>
@@ -62,8 +72,8 @@
                This is an irreversible operation.</p>
           </div>
           <div class="buttons">
+            <input type="submit" class="trac-disable-on-submit" value="${_('Delete comment')}"/>
             <input type="submit" name="cancel" value="${_('Cancel')}"/>
-            <input type="submit" value="${_('Delete comment')}"/>
           </div>
         </form>
       </py:otherwise>

Modified: bloodhound/branches/trac-1.0.2-integration/trac/tracopt/versioncontrol/git/PyGIT.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/tracopt/versioncontrol/git/PyGIT.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/tracopt/versioncontrol/git/PyGIT.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/tracopt/versioncontrol/git/PyGIT.py Sat Nov 15 01:14:46 2014
@@ -28,36 +28,12 @@ from threading import Lock
 import time
 import weakref
 
+from trac.util import terminate
+from trac.util.text import to_unicode
 
 __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
-        return os.kill(process.pid, signal.SIGTERM)
-
-    if sys.platform == 'win32':
-        return terminate_win(process)
-    return terminate_nix(process)
-
-
 class GitError(Exception):
     pass
 
@@ -95,12 +71,29 @@ def parse_commit(raw):
     return '\n'.join(lines), props
 
 
+_unquote_re = re.compile(r'\\(?:[abtnvfr"\\]|[0-7]{3})')
+_unquote_chars = {'a': '\a', 'b': '\b', 't': '\t', 'n': '\n', 'v': '\v',
+                  'f': '\f', 'r': '\r', '"': '"', '\\': '\\'}
+
+
+def _unquote(path):
+    if path.startswith('"') and path.endswith('"'):
+        def replace(match):
+            s = match.group(0)[1:]
+            if len(s) == 3:
+                return chr(int(s, 8))  # \ooo
+            return _unquote_chars[s]
+        path = _unquote_re.sub(replace, path[1:-1])
+    return path
+
+
 class GitCore(object):
     """Low-level wrapper around git executable"""
 
-    def __init__(self, git_dir=None, git_bin='git'):
+    def __init__(self, git_dir=None, git_bin='git', log=None):
         self.__git_bin = git_bin
         self.__git_dir = git_dir
+        self.__log = log
 
     def __repr__(self):
         return '<GitCore bin="%s" dir="%s">' % (self.__git_bin,
@@ -132,7 +125,10 @@ class GitCore(object):
         p = self.__pipe(git_cmd, stdout=PIPE, stderr=PIPE, *cmd_args)
 
         stdout_data, stderr_data = p.communicate()
-        #TODO, do something with p.returncode, e.g. raise exception
+        if self.__log and (p.returncode != 0 or stderr_data):
+            self.__log.debug('%s exits with %d, dir: %r, args: %s %r, '
+                             'stderr: %r', self.__git_bin, p.returncode,
+                             self.__git_dir, git_cmd, cmd_args, stderr_data)
 
         return stdout_data
 
@@ -201,21 +197,23 @@ class StorageFactory(object):
         self.logger = log
 
         with StorageFactory.__dict_lock:
+            if weak:
+                # remove additional reference which is created
+                # with non-weak argument
+                try:
+                    del StorageFactory.__dict_nonweak[repo]
+                except KeyError:
+                    pass
+
             try:
                 i = StorageFactory.__dict[repo]
             except KeyError:
                 i = Storage(repo, log, git_bin, git_fs_encoding)
                 StorageFactory.__dict[repo] = i
 
-                # create or remove additional reference depending on 'weak'
-                # argument
-                if weak:
-                    try:
-                        del StorageFactory.__dict_nonweak[repo]
-                    except KeyError:
-                        pass
-                else:
-                    StorageFactory.__dict_nonweak[repo] = i
+            # create additional reference depending on 'weak' argument
+            if not weak:
+                StorageFactory.__dict_nonweak[repo] = i
 
         self.__inst = i
         self.__repo = repo
@@ -227,13 +225,19 @@ class StorageFactory(object):
                              self.__repo))
         return self.__inst
 
+    @classmethod
+    def _clean(cls):
+        """For testing purpose only"""
+        with StorageFactory.__dict_lock:
+            cls.__dict.clear()
+            cls.__dict_nonweak.clear()
+
 
 class Storage(object):
     """High-level wrapper around GitCore with in-memory caching"""
 
     __SREV_MIN = 4 # minimum short-rev length
 
-
     class RevCache(tuple):
         """RevCache(youngest_rev, oldest_rev, rev_dict, tag_set, srev_dict,
                     branch_dict)
@@ -383,18 +387,29 @@ class Storage(object):
 
         # simple sanity checking
         __git_file_path = partial(os.path.join, git_dir)
-        if not all(map(os.path.exists,
-                       map(__git_file_path,
-                           ['HEAD','objects','refs']))):
-            self.logger.error("GIT control files missing in '%s'" % git_dir)
-            if os.path.exists(__git_file_path('.git')):
-                self.logger.error("entry '.git' found in '%s'"
-                                  " -- maybe use that folder instead..."
+        control_files = ['HEAD', 'objects', 'refs']
+        control_files_exist = \
+            lambda p: all(map(os.path.exists, map(p, control_files)))
+        if not control_files_exist(__git_file_path):
+            __git_file_path = partial(os.path.join, git_dir, '.git')
+            if os.path.exists(__git_file_path()) and \
+                    control_files_exist(__git_file_path):
+                git_dir = __git_file_path()
+            else:
+                self.logger.error("GIT control files missing in '%s'"
                                   % git_dir)
-            raise GitError("GIT control files not found, maybe wrong "
-                           "directory?")
+                raise GitError("GIT control files not found, maybe wrong "
+                               "directory?")
+        # at least, check that the HEAD file is readable
+        head_file = os.path.join(git_dir, 'HEAD')
+        try:
+            with open(head_file, 'rb') as f:
+                pass
+        except IOError, e:
+            raise GitError("Make sure the Git repository '%s' is readable: %s"
+                           % (git_dir, to_unicode(e)))
 
-        self.repo = GitCore(git_dir, git_bin=git_bin)
+        self.repo = GitCore(git_dir, git_bin=git_bin, log=log)
 
         self.logger.debug("PyGIT.Storage instance %d constructed" % id(self))
 
@@ -410,25 +425,29 @@ class Storage(object):
     #
 
     # called by Storage.sync()
-    def __rev_cache_sync(self, youngest_rev=None):
+    def __rev_cache_sync(self):
         """invalidates revision db cache if necessary"""
 
+        branches = self._get_branches()
+
         with self.__rev_cache_lock:
             need_update = False
-            if self.__rev_cache:
-                last_youngest_rev = self.__rev_cache.youngest_rev
-                if last_youngest_rev != youngest_rev:
-                    self.logger.debug("invalidated caches (%s != %s)"
-                                      % (last_youngest_rev, youngest_rev))
-                    need_update = True
-            else:
+            if not self.__rev_cache:
                 need_update = True # almost NOOP
+            elif branches != self.__rev_cache.branch_dict:
+                self.logger.debug('invalidated caches for %d cause repository '
+                                  'has been changed', id(self))
+                need_update = True
 
             if need_update:
                 self.__rev_cache = None
-
             return need_update
 
+    def invalidate_rev_cache(self):
+        with self.__rev_cache_lock:
+            self.__rev_cache = None
+            self.logger.debug('invalidated caches for %d', id(self))
+
     def get_rev_cache(self):
         """Retrieve revision cache
 
@@ -463,7 +482,7 @@ class Storage(object):
                                 for k, v in self._get_branches()]
                 head_revs = set(v for _, v in new_branches)
 
-                rev = ord_rev = 0
+                rev = ord_rev = None
                 for ord_rev, revs in enumerate(
                                         self.repo.rev_list('--parents',
                                                            '--topo-order',
@@ -565,8 +584,12 @@ class Storage(object):
         """
 
         result = []
-        for e in self.repo.branch('-v', '--no-abbrev').splitlines():
-            bname, bsha = e[1:].strip().split()[:2]
+        for e in self.repo.branch('-v', '--no-abbrev').rstrip('\n') \
+                                                      .split('\n'):
+            tokens = e[1:].strip().split()[:2]
+            if len(tokens) != 2:
+                continue
+            bname, bsha = tokens
             if e.startswith('*'):
                 result.insert(0, (bname, bsha))
             else:
@@ -640,8 +663,8 @@ class Storage(object):
     def get_commit_encoding(self):
         if self.commit_encoding is None:
             self.commit_encoding = \
-                self.repo.repo_config("--get", "i18n.commitEncoding") \
-                    .strip() or 'utf-8'
+                self.repo.config('--get', 'i18n.commitEncoding').strip() or \
+                'utf-8'
 
         return self.commit_encoding
 
@@ -812,7 +835,7 @@ class Storage(object):
             raise GitErrorSha
 
         with self.__commit_msg_lock:
-            if self.__commit_msg_cache.has_key(commit_id):
+            if commit_id in self.__commit_msg_cache:
                 # cache hit
                 result = self.__commit_msg_cache[commit_id]
                 return result[0], dict(result[1])
@@ -882,15 +905,14 @@ class Storage(object):
         return self.get_commits().iterkeys()
 
     def sync(self):
-        rev = self.repo.rev_list('--max-count=1', '--topo-order', '--all') \
-                       .strip()
-        return self.__rev_cache_sync(rev)
+        return self.__rev_cache_sync()
 
     @contextmanager
     def get_historian(self, sha, base_path):
         p = []
         change = {}
         next_path = []
+        base_path = self._fs_from_unicode(base_path)
 
         def name_status_gen():
             p[:] = [self.repo.log_pipe('--pretty=format:%n%H',
@@ -904,6 +926,8 @@ class Storage(object):
                     if l == '\n':
                         break
                     _, path = l.rstrip('\n').split('\t', 1)
+                    # git-log without -z option quotes each pathname
+                    path = _unquote(path)
                     while path not in change:
                         change[path] = old_sha
                         if next_path == [path]:
@@ -921,6 +945,7 @@ class Storage(object):
         gen = name_status_gen()
 
         def historian(path):
+            path = self._fs_from_unicode(path)
             try:
                 return change[path]
             except KeyError:
@@ -936,34 +961,32 @@ class Storage(object):
     def last_change(self, sha, path, historian=None):
         if historian is not None:
             return historian(path)
-        return self.repo.rev_list('--max-count=1',
-                                  sha, '--',
-                                  self._fs_from_unicode(path)).strip() or None
+        tmp = self.history(sha, path, limit=1)
+        return tmp[0] if tmp else None
 
     def history(self, sha, path, limit=None):
         if limit is None:
             limit = -1
 
-        tmp = self.repo.rev_list('--max-count=%d' % limit, str(sha), '--',
-                                 self._fs_from_unicode(path))
-
-        return [ rev.strip() for rev in tmp.splitlines() ]
+        args = ['--max-count=%d' % limit, str(sha)]
+        if path:
+            args.extend(('--', self._fs_from_unicode(path)))
+        tmp = self.repo.rev_list(*args)
+        return [rev.strip() for rev in tmp.splitlines()]
 
     def history_timerange(self, start, stop):
+        # retrieve start <= committer-time < stop,
+        # see CachedRepository.get_changesets()
         return [ rev.strip() for rev in \
-                     self.repo.rev_list('--reverse',
+                     self.repo.rev_list('--date-order',
                                         '--max-age=%d' % start,
-                                        '--min-age=%d' % stop,
+                                        '--min-age=%d' % (stop - 1),
                                         '--all').splitlines() ]
 
     def rev_is_anchestor_of(self, rev1, rev2):
         """return True if rev2 is successor of rev1"""
 
-        rev1 = rev1.strip()
-        rev2 = rev2.strip()
-
         rev_dict = self.get_commits()
-
         return (rev2 in rev_dict and
                 rev2 in self.children_recursive(rev1, rev_dict))