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:"<blink>"
</p>
------------------------------
+============================== Bracketed links
+See <http://en.wikipedia.org/wiki/Mornington_Crescent_(game)>
+------------------------------
+<p>
+See <<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>>
+</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
------------------------------
[â¦]
> 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))